From 24e40c6b70d9b4f7ebf1e02bace5794009b0c1b6 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 3 Jul 2019 03:11:27 +0700 Subject: Initial commit. --- README.md | 11 +++++++++++ bin/vyos-smoketest | 19 +++++++++++++++++++ scripts/test_module_load.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 README.md create mode 100755 bin/vyos-smoketest create mode 100755 scripts/test_module_load.py diff --git a/README.md b/README.md new file mode 100644 index 000000000..d02557393 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +vyos-smoketest +============== + +This is a set of scripts and test data for sanity checking VyOS builds. + +The main entry point is /usr/bin/vyos-smoketest + +It will try to check for common things that break such as kernel modules not loading, +and print a test report. + +It also comes with a huge reference config that has almost every feature set. diff --git a/bin/vyos-smoketest b/bin/vyos-smoketest new file mode 100755 index 000000000..b8cc36879 --- /dev/null +++ b/bin/vyos-smoketest @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +import os +import sys + +test_dir = '/usr/libexec/vyos/tests/smoke' + +tests = ['test_load_modules.py'] + +success = True + +for t in tests: + try: + os.system(os.path.join(test_dir, t)) + except Exception as e: + success = False + +if not success: + sys.exit(1) diff --git a/scripts/test_module_load.py b/scripts/test_module_load.py new file mode 100755 index 000000000..a4f3a62eb --- /dev/null +++ b/scripts/test_module_load.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 + +import sys +import os + +MODULES_INTEL = "e1000 e1000e igb ixgb ixgbe ixgbevf i40e i40evf" +MODULES_ACCEL_PPP = "ipoe" +MODULES_MISC = "wireguard" + +modules = { + "intel": ["e1000", "e1000e", "igb", "ixgb", "ixgbe", "ixgbevf", "i40e", "i40evf"], + "accel_ppp": ["ipoe"], + "misc": ["wireguard"] +} + +if __name__ == '__main__': + success = True + + print("[load modules] Test execution started") + for msk in modules: + ms = modules[msk] + for m in ms: + # We want to uncover all modules that fail, + # not fail at the first one + try: + os.system("modprobe {0}".format(m)) + except Exception as e: + print("[load modules] Test [modprobe {0}] failed: {1}".format(module, e)) + success = False + + if not success: + print("Test [load modules] failed") + sys.exit(1) + else: + print("[load modules] Test succeeded") -- cgit v1.2.3 From 630f398bd23fbfe4e927bf042a7afd3a44f47e08 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 3 Jul 2019 03:14:16 +0700 Subject: Remove unncesessary lines. --- scripts/test_module_load.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/test_module_load.py b/scripts/test_module_load.py index a4f3a62eb..c96229b92 100755 --- a/scripts/test_module_load.py +++ b/scripts/test_module_load.py @@ -3,10 +3,6 @@ import sys import os -MODULES_INTEL = "e1000 e1000e igb ixgb ixgbe ixgbevf i40e i40evf" -MODULES_ACCEL_PPP = "ipoe" -MODULES_MISC = "wireguard" - modules = { "intel": ["e1000", "e1000e", "igb", "ixgb", "ixgbe", "ixgbevf", "i40e", "i40evf"], "accel_ppp": ["ipoe"], -- cgit v1.2.3 From 35a429c4ca1923b3e87531f34ca64133d4a82efc Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 2 Jul 2019 22:34:59 +0200 Subject: Add Debian packaging and LGPL license. --- LICENSE | 502 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 4 + bin/vyos-smoketest | 2 +- debian/changelog | 5 + debian/compat | 1 + debian/control | 23 +++ debian/copyright | 35 ++++ debian/rules | 28 +++ 8 files changed, 599 insertions(+), 1 deletion(-) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100755 debian/rules diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..4362b4915 --- /dev/null +++ b/LICENSE @@ -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. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General 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. + + , 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 000000000..c99579b45 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +.PHONY: all + +all: + # Install is just xcopy diff --git a/bin/vyos-smoketest b/bin/vyos-smoketest index b8cc36879..1ae00d5b8 100755 --- a/bin/vyos-smoketest +++ b/bin/vyos-smoketest @@ -5,7 +5,7 @@ import sys test_dir = '/usr/libexec/vyos/tests/smoke' -tests = ['test_load_modules.py'] +tests = ['test_module_load.py'] success = True diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 000000000..2b24a8f9e --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +vyos-smoketest (1.0.0) unstable; urgency=medium + + * Initial release + + -- Daniil Baturin Tue, 02 Jul 2019 22:29:53 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 000000000..ec635144f --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 000000000..97f8b27ea --- /dev/null +++ b/debian/control @@ -0,0 +1,23 @@ +Source: vyos-smoketest +Section: contrib/net +Priority: extra +Maintainer: VyOS Package Maintainers +Build-Depends: debhelper (>= 9), + quilt, + python3, + python3-setuptools, + quilt, + python3-lxml, + python3-nose, + python3-coverage +Standards-Version: 3.9.6 + +Package: vyos-smoketest +Architecture: all +Depends: python3, + ${python3:Depends}, + ${shlibs:Depends}, + ${misc:Depends}, + vyos-1x +Description: VyOS build sanity checking toolkit + VyOS build sanity checking toolkit diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 000000000..a3260a7a1 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,35 @@ +This package was debianized by Daniil Baturin on +Tue, 02 Jul 2019 22:24:20 +0200 + +It's original content from the GIT repository + +Upstream Author: + + + +Copyright: + + Copyright (C) 2019 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 Lesser 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/lgpl.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) 2019, Daniil Baturin and +is licensed under the GPL, see above. diff --git a/debian/rules b/debian/rules new file mode 100755 index 000000000..4ccc23e86 --- /dev/null +++ b/debian/rules @@ -0,0 +1,28 @@ +#!/usr/bin/make -f + +DIR := debian/vyos-smoketest +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 + +MIGRATION_SCRIPTS_DIR := /opt/vyatta/etc/config-migrate/migrate/ + +SBINDIR := $(DIR)/usr/sbin + +%: + dh $@ --with python3, --with quilt + +override_dh_auto_build: + make all + +override_dh_auto_install: + # Install smoke test scripts + mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/smoke/ + cp -r scripts/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/smoke + + # Install system programs + mkdir -p $(SBINDIR) + cp -r bin/* $(SBINDIR) -- cgit v1.2.3 From cd34b290f444614f887eb284aaf135027c7cef41 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 11 Jul 2019 21:04:52 +0200 Subject: Separate system and CLI tests. --- scripts/system/test_module_load.py | 31 +++++++++++++++++++++++++++++++ scripts/test_module_load.py | 31 ------------------------------- 2 files changed, 31 insertions(+), 31 deletions(-) create mode 100755 scripts/system/test_module_load.py delete mode 100755 scripts/test_module_load.py diff --git a/scripts/system/test_module_load.py b/scripts/system/test_module_load.py new file mode 100755 index 000000000..c96229b92 --- /dev/null +++ b/scripts/system/test_module_load.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import sys +import os + +modules = { + "intel": ["e1000", "e1000e", "igb", "ixgb", "ixgbe", "ixgbevf", "i40e", "i40evf"], + "accel_ppp": ["ipoe"], + "misc": ["wireguard"] +} + +if __name__ == '__main__': + success = True + + print("[load modules] Test execution started") + for msk in modules: + ms = modules[msk] + for m in ms: + # We want to uncover all modules that fail, + # not fail at the first one + try: + os.system("modprobe {0}".format(m)) + except Exception as e: + print("[load modules] Test [modprobe {0}] failed: {1}".format(module, e)) + success = False + + if not success: + print("Test [load modules] failed") + sys.exit(1) + else: + print("[load modules] Test succeeded") diff --git a/scripts/test_module_load.py b/scripts/test_module_load.py deleted file mode 100755 index c96229b92..000000000 --- a/scripts/test_module_load.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import os - -modules = { - "intel": ["e1000", "e1000e", "igb", "ixgb", "ixgbe", "ixgbevf", "i40e", "i40evf"], - "accel_ppp": ["ipoe"], - "misc": ["wireguard"] -} - -if __name__ == '__main__': - success = True - - print("[load modules] Test execution started") - for msk in modules: - ms = modules[msk] - for m in ms: - # We want to uncover all modules that fail, - # not fail at the first one - try: - os.system("modprobe {0}".format(m)) - except Exception as e: - print("[load modules] Test [modprobe {0}] failed: {1}".format(module, e)) - success = False - - if not success: - print("Test [load modules] failed") - sys.exit(1) - else: - print("[load modules] Test succeeded") -- cgit v1.2.3 From afc0b35d1fb485bc315cb8d07bff9f4d62a565ed Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 11 Jul 2019 21:05:32 +0200 Subject: Add a test for 'system name-server'. --- scripts/cli/test_system_nameserver.py | 52 +++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 scripts/cli/test_system_nameserver.py diff --git a/scripts/cli/test_system_nameserver.py b/scripts/cli/test_system_nameserver.py new file mode 100644 index 000000000..591680c63 --- /dev/null +++ b/scripts/cli/test_system_nameserver.py @@ -0,0 +1,52 @@ +import os +import re +import unittest + +import vyos.config +import vyos.configsession +import vyos.util as util + +RESOLV_CONF = '/etc/resolv.conf' + +test_servers = ['192.0.2.10', '2001:db8:1::100'] + +base_path = ['system', 'name-server'] + + +def get_name_servers(): + resolv_conf = util.read_file(RESOLV_CONF) + return re.findall(r'\n?nameserver\s+(.*)', resolv_conf) + +class TestSystemNameServer(unittest.TestCase): + def setUp(self): + self.session = vyos.configsession.ConfigSession(os.getpid()) + env = self.session.get_session_env() + self.config = vyos.config.Config(session_env=env) + + # Delete existing name servers + self.session.delete(base_path) + self.session.commit() + + def test_add_server(self): + """ Check if server is added to resolv.conf """ + for s in test_servers: + self.session.set(base_path + [s]) + self.session.commit() + + servers = get_name_servers() + for s in servers: + self.assertTrue(s in servers) + + def test_delete_server(self): + """ Test if a deleted server disappears from resolv.conf """ + for s in test_servers: + self.session.delete(base_path + [s]) + self.session.commit() + + servers = get_name_servers() + for s in servers: + self.assertTrue(test_server_1 not in servers) + +if __name__ == '__main__': + unittest.main() + -- cgit v1.2.3 From 6c076df8aca590aa2a7fe9f40361a2afdb139477 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 18 Dec 2019 14:34:34 +0100 Subject: Jenkins: adjust to new Debian Buster build --- Jenkinsfile | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 000000000..3b8daeb12 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,165 @@ +// Copyright (C) 2019 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 . + +@NonCPS + +def getGitBranchName() { + def branch = scm.branches[0].name + return branch.split('/')[-1] +} + +def getGitRepoURL() { + return scm.userRemoteConfigs[0].url +} + +def getGitRepoName() { + return getGitRepoURL().split('/').last() +} + +// Returns true if this is a custom build launched on any project fork. +// Returns false if this is build from git@github.com:vyos/. +// can be e.g. vyos-1x.git or vyatta-op.git +def isCustomBuild() { + // GitHub organisation base URL + def gitURI = 'git@github.com:vyos/' + getGitRepoName() + def httpURI = 'https://github.com/vyos/' + getGitRepoName() + + return ! ((getGitRepoURL() == gitURI) || (getGitRepoURL() == httpURI)) +} + +def setDescription() { + def item = Jenkins.instance.getItemByFullName(env.JOB_NAME) + + // build up the main description text + def description = "" + description += "

VyOS individual package build: " + getGitRepoName().replace('.git', '') + "

" + + if (isCustomBuild()) { + description += "

" + description += "Build not started from official Git repository!
" + description += "
" + description += "Repository: " + getGitRepoURL() + "
" + description += "Branch: " + getGitBranchName() + "
" + description += "

" + } else { + description += "Sources taken from Git branch: " + getGitBranchName() + "
" + } + + item.setDescription(description) + item.save() +} + +/* Only keep the 10 most recent builds. */ +def projectProperties = [ + [$class: 'BuildDiscarderProperty',strategy: [$class: 'LogRotator', numToKeepStr: '10']], +] + +properties(projectProperties) +setDescription() + +pipeline { + agent { + docker { + args '--sysctl net.ipv6.conf.lo.disable_ipv6=0 -e GOSU_UID=1006 -e GOSU_GID=1006' + image 'vyos/vyos-build:current' + alwaysPull true + } + } + options { + disableConcurrentBuilds() + skipDefaultCheckout() + timeout(time: 30, unit: 'MINUTES') + timestamps() + } + stages { + stage('Fetch') { + steps { + script { + dir('build') { + git branch: getGitBranchName(), + url: getGitRepoURL() + } + } + } + } + stage('Build') { + steps { + script { + dir('build') { + def commitId = sh(returnStdout: true, script: 'git rev-parse --short=11 HEAD').trim() + currentBuild.description = sprintf('Git SHA1: %s', commitId[-11..-1]) + + sh 'dpkg-buildpackage -b -us -uc -tc' + } + } + } + } + } + post { + cleanup { + deleteDir() + } + success { + script { + // archive *.deb artifact on custom builds, deploy to repo otherwise + if ( isCustomBuild()) { + archiveArtifacts artifacts: '*.deb', fingerprint: true + } else { + // publish build result, using SSH-dev.packages.vyos.net Jenkins Credentials + sshagent(['SSH-dev.packages.vyos.net']) { + // build up some fancy groovy variables so we do not need to write/copy + // every option over and over again! + + def VYOS_REPO_PATH = '/home/sentrium/web/dev.packages.vyos.net/public_html/repositories/' + getGitBranchName() + '/' + def SSH_OPTS = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=ERROR' + def SSH_REMOTE = 'khagen@10.217.48.113' + + echo "Uploading package(s) and updating package(s) in the repository ..." + + files = findFiles(glob: '*.deb') + files.each { PACKAGE -> + def RELEASE = getGitBranchName() + def ARCH = sh(returnStdout: true, script: "dpkg-deb -f ${PACKAGE} Architecture").trim() + def SUBSTRING = sh(returnStdout: true, script: "dpkg-deb -f ${PACKAGE} Package").trim() + def SSH_DIR = '~/VyOS/' + RELEASE + '/' + ARCH + def ARCH_OPT = '' + if (ARCH != 'all') + ARCH_OPT = '-A ' + ARCH + + // No need to explicitly check the return code. The pipeline + // will fail if sh returns a non 0 exit code + sh """ + ssh ${SSH_OPTS} ${SSH_REMOTE} -t "bash --login -c 'mkdir -p ${SSH_DIR}'" + """ + sh """ + scp ${SSH_OPTS} ${PACKAGE} ${SSH_REMOTE}:${SSH_DIR}/ + """ + sh """ + ssh ${SSH_OPTS} ${SSH_REMOTE} -t "uncron-add 'reprepro -v -b ${VYOS_REPO_PATH} ${ARCH_OPT} remove ${RELEASE} ${SUBSTRING}'" + """ + sh """ + ssh ${SSH_OPTS} ${SSH_REMOTE} -t "uncron-add 'reprepro -v -b ${VYOS_REPO_PATH} deleteunreferenced'" + """ + sh """ + ssh ${SSH_OPTS} ${SSH_REMOTE} -t "uncron-add 'reprepro -v -b ${VYOS_REPO_PATH} ${ARCH_OPT} includedeb ${RELEASE} ${SSH_DIR}/${PACKAGE}'" + """ + } + } + } + } + } + } +} + -- cgit v1.2.3 From a17d72be46fe80f036058f044085e15b76740de2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 18 Dec 2019 14:42:01 +0100 Subject: Jenkins: add exception for master branch to deploy path --- Jenkinsfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 3b8daeb12..63a271bc1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -121,8 +121,12 @@ pipeline { sshagent(['SSH-dev.packages.vyos.net']) { // build up some fancy groovy variables so we do not need to write/copy // every option over and over again! + def RELEASE = getGitBranchName() + if (getGitBranchName() == "master") { + RELEASE = 'current' + } - def VYOS_REPO_PATH = '/home/sentrium/web/dev.packages.vyos.net/public_html/repositories/' + getGitBranchName() + '/' + def VYOS_REPO_PATH = '/home/sentrium/web/dev.packages.vyos.net/public_html/repositories/' + RELEASE + '/' def SSH_OPTS = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=ERROR' def SSH_REMOTE = 'khagen@10.217.48.113' @@ -130,7 +134,6 @@ pipeline { files = findFiles(glob: '*.deb') files.each { PACKAGE -> - def RELEASE = getGitBranchName() def ARCH = sh(returnStdout: true, script: "dpkg-deb -f ${PACKAGE} Architecture").trim() def SUBSTRING = sh(returnStdout: true, script: "dpkg-deb -f ${PACKAGE} Package").trim() def SSH_DIR = '~/VyOS/' + RELEASE + '/' + ARCH -- cgit v1.2.3 From 4f42b4f7411dd4ca4e670a47be1dfe7f81d02e05 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 28 Dec 2019 00:22:13 +0100 Subject: Search testcases and execute automatically --- bin/vyos-smoketest | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/bin/vyos-smoketest b/bin/vyos-smoketest index 1ae00d5b8..3c54b8780 100755 --- a/bin/vyos-smoketest +++ b/bin/vyos-smoketest @@ -1,19 +1,38 @@ #!/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 . import os import sys -test_dir = '/usr/libexec/vyos/tests/smoke' - -tests = ['test_module_load.py'] +from stat import * 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 mode & S_IXOTH: + try: + os.system(test_file) + except Exception as e: + print('Testcase "{}" raised an exception'.format(test_file)) + success = False -for t in tests: - try: - os.system(os.path.join(test_dir, t)) - except Exception as e: - success = False +if success: + sys.exit(0) -if not success: - sys.exit(1) +sys.exit(1) -- cgit v1.2.3 From ae2c359c2938466001359823d4b2a97bd360a889 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 28 Dec 2019 09:43:50 +0100 Subject: Debian: install binary to /usr/bin --- debian/rules | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/debian/rules b/debian/rules index 4ccc23e86..526fb8537 100755 --- a/debian/rules +++ b/debian/rules @@ -1,7 +1,6 @@ #!/usr/bin/make -f DIR := debian/vyos-smoketest -VYOS_SBIN_DIR := usr/sbin/ VYOS_BIN_DIR := usr/bin/ VYOS_LIBEXEC_DIR := usr/libexec/vyos VYOS_DATA_DIR := /usr/share/vyos @@ -10,8 +9,6 @@ VYOS_OP_TMPL_DIR := /opt/vyatta/share/vyatta-op/templates MIGRATION_SCRIPTS_DIR := /opt/vyatta/etc/config-migrate/migrate/ -SBINDIR := $(DIR)/usr/sbin - %: dh $@ --with python3, --with quilt @@ -24,5 +21,5 @@ override_dh_auto_install: cp -r scripts/* $(DIR)/$(VYOS_LIBEXEC_DIR)/tests/smoke # Install system programs - mkdir -p $(SBINDIR) - cp -r bin/* $(SBINDIR) + mkdir -p $(DIR)/$(VYOS_BIN_DIR) + cp -r bin/* $(DIR)/$(VYOS_BIN_DIR) -- cgit v1.2.3 From 1553a4f693729b1ecbd38b3256fc05ac456092b2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 28 Dec 2019 09:43:58 +0100 Subject: Jenkins: make pipeline branch independent --- Jenkinsfile | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 63a271bc1..ed98477f2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -69,11 +69,24 @@ def projectProperties = [ properties(projectProperties) setDescription() +node('Docker') { + stage('Define Agent') { + script { + // create container name on demand + def branchName = getGitBranchName() + if (branchName == "master") { + branchName = "current" + } + env.DOCKER_IMAGE = "vyos/vyos-build:" + branchName + } + } +} + pipeline { agent { docker { - args '--sysctl net.ipv6.conf.lo.disable_ipv6=0 -e GOSU_UID=1006 -e GOSU_GID=1006' - image 'vyos/vyos-build:current' + args "--sysctl net.ipv6.conf.lo.disable_ipv6=0 -e GOSU_UID=1006 -e GOSU_GID=1006" + image "${env.DOCKER_IMAGE}" alwaysPull true } } @@ -127,6 +140,9 @@ pipeline { } def VYOS_REPO_PATH = '/home/sentrium/web/dev.packages.vyos.net/public_html/repositories/' + RELEASE + '/' + if (getGitBranchName() == "crux") + VYOS_REPO_PATH += 'vyos/' + def SSH_OPTS = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=ERROR' def SSH_REMOTE = 'khagen@10.217.48.113' -- cgit v1.2.3 From e8e0e502cee05dc1335ae7d1cc693270e8a5292a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Jan 2020 14:19:10 +0100 Subject: nameserver: add missing shebang (#!) --- scripts/cli/test_system_nameserver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/cli/test_system_nameserver.py b/scripts/cli/test_system_nameserver.py index 591680c63..80c6cb454 100644 --- a/scripts/cli/test_system_nameserver.py +++ b/scripts/cli/test_system_nameserver.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import os import re import unittest -- cgit v1.2.3 From a985907d54ecada217e85ca23e002790bf6591e3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Jan 2020 14:19:34 +0100 Subject: nameserver: add GPL2 license --- scripts/cli/test_system_nameserver.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scripts/cli/test_system_nameserver.py b/scripts/cli/test_system_nameserver.py index 80c6cb454..cab867983 100644 --- a/scripts/cli/test_system_nameserver.py +++ b/scripts/cli/test_system_nameserver.py @@ -1,4 +1,18 @@ #!/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 . import os import re -- cgit v1.2.3 From 47c8f35dd476d2730aad3990e63240af1c7027cf Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Jan 2020 14:20:22 +0100 Subject: nameserver: enable test --- scripts/cli/test_system_nameserver.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/cli/test_system_nameserver.py diff --git a/scripts/cli/test_system_nameserver.py b/scripts/cli/test_system_nameserver.py old mode 100644 new mode 100755 -- cgit v1.2.3 From a7cd2bc683ca03ab2787879d3db06e8d5d3fd5ca Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jan 2020 14:03:45 +0100 Subject: nameserver: fix indent --- scripts/cli/test_system_nameserver.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/cli/test_system_nameserver.py b/scripts/cli/test_system_nameserver.py index cab867983..a07ad31ad 100755 --- a/scripts/cli/test_system_nameserver.py +++ b/scripts/cli/test_system_nameserver.py @@ -25,13 +25,11 @@ import vyos.util as util RESOLV_CONF = '/etc/resolv.conf' test_servers = ['192.0.2.10', '2001:db8:1::100'] - base_path = ['system', 'name-server'] - def get_name_servers(): - resolv_conf = util.read_file(RESOLV_CONF) - return re.findall(r'\n?nameserver\s+(.*)', resolv_conf) + resolv_conf = util.read_file(RESOLV_CONF) + return re.findall(r'\n?nameserver\s+(.*)', resolv_conf) class TestSystemNameServer(unittest.TestCase): def setUp(self): -- cgit v1.2.3 From b55fc6241dc9e83aed74277b1d85e1e5f436b12a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jan 2020 14:04:12 +0100 Subject: smoketest: inform user about current testcase --- bin/vyos-smoketest | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/vyos-smoketest b/bin/vyos-smoketest index 3c54b8780..6d3442622 100755 --- a/bin/vyos-smoketest +++ b/bin/vyos-smoketest @@ -27,6 +27,7 @@ for root, dirs, files in os.walk('/usr/libexec/vyos/tests/smoke'): if mode & S_IXOTH: try: + print('Running Testcase: ' + test_file) os.system(test_file) except Exception as e: print('Testcase "{}" raised an exception'.format(test_file)) -- cgit v1.2.3 From ef2cd3db3faafb209189f14dd3762587e31affe9 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jan 2020 14:04:48 +0100 Subject: bond: very basic test for a bonding interface --- scripts/cli/test_bond.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100755 scripts/cli/test_bond.py diff --git a/scripts/cli/test_bond.py b/scripts/cli/test_bond.py new file mode 100755 index 000000000..fb948ffdf --- /dev/null +++ b/scripts/cli/test_bond.py @@ -0,0 +1,53 @@ +#!/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 . + +import os +import unittest +import vyos.config +import vyos.configsession + +from netifaces import ifaddresses, AF_INET, AF_INET6 +from vyos.validate import is_intf_addr_assigned + +base_path = ['interfaces', 'bonding'] +test_addr = ['192.0.2.1/25', '2001:db8:1::ffff/64'] +interfaces = ['bond0'] + +class TestInterfacesBond(unittest.TestCase): + def setUp(self): + self.session = vyos.configsession.ConfigSession(os.getpid()) + env = self.session.get_session_env() + self.config = vyos.config.Config(session_env=env) + + def tearDown(self): + # Delete existing interfaces + self.session.delete(base_path) + self.session.commit() + + def test_add_address(self): + """ Check if address is added to interface """ + for intf in interfaces: + for addr in test_addr: + self.session.set(base_path + [intf, 'address', addr]) + self.session.commit() + + for intf in interfaces: + for af in AF_INET, AF_INET6: + for addr in ifaddresses(intf)[af]: + self.assertTrue(is_intf_addr_assigned(intf, addr['addr'])) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From ebb6bfc751bc65b8a99d7f176c753c938846e257 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jan 2020 17:22:12 +0100 Subject: kernel: migrate module load test to unittest framework --- scripts/system/test_module_load.py | 55 +++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/scripts/system/test_module_load.py b/scripts/system/test_module_load.py index c96229b92..598ea9882 100755 --- a/scripts/system/test_module_load.py +++ b/scripts/system/test_module_load.py @@ -1,31 +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 . -import sys import os +import unittest modules = { - "intel": ["e1000", "e1000e", "igb", "ixgb", "ixgbe", "ixgbevf", "i40e", "i40evf"], - "accel_ppp": ["ipoe"], - "misc": ["wireguard"] + "intel": ["e1000", "e1000e", "igb", "ixgb", "ixgbe", "ixgbevf", "i40e", "i40evf"], + "accel_ppp": ["ipoe", "vlan_mon"], + "misc": ["wireguard"] } -if __name__ == '__main__': - success = True +class TestKernelModules(unittest.TestCase): + def test_load_modules(self): + success = True + for msk in modules: + ms = modules[msk] + for m in ms: + # We want to uncover all modules that fail, + # not fail at the first one + try: + os.system("modprobe {0}".format(m)) + except: + success = False - print("[load modules] Test execution started") - for msk in modules: - ms = modules[msk] - for m in ms: - # We want to uncover all modules that fail, - # not fail at the first one - try: - os.system("modprobe {0}".format(m)) - except Exception as e: - print("[load modules] Test [modprobe {0}] failed: {1}".format(module, e)) - success = False + self.assertTrue(success) - if not success: - print("Test [load modules] failed") - sys.exit(1) - else: - print("[load modules] Test succeeded") +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From b6ad4c9908b03cc9cbb5d1173476984b08b68222 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jan 2020 11:45:10 +0100 Subject: interface: provide common test class for networking Currently only bonding and bridge interfaces are supported --- scripts/cli/test_bond.py | 53 ----------------------- scripts/cli/test_interfaces.py | 95 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 53 deletions(-) delete mode 100755 scripts/cli/test_bond.py create mode 100755 scripts/cli/test_interfaces.py diff --git a/scripts/cli/test_bond.py b/scripts/cli/test_bond.py deleted file mode 100755 index fb948ffdf..000000000 --- a/scripts/cli/test_bond.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/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 . - -import os -import unittest -import vyos.config -import vyos.configsession - -from netifaces import ifaddresses, AF_INET, AF_INET6 -from vyos.validate import is_intf_addr_assigned - -base_path = ['interfaces', 'bonding'] -test_addr = ['192.0.2.1/25', '2001:db8:1::ffff/64'] -interfaces = ['bond0'] - -class TestInterfacesBond(unittest.TestCase): - def setUp(self): - self.session = vyos.configsession.ConfigSession(os.getpid()) - env = self.session.get_session_env() - self.config = vyos.config.Config(session_env=env) - - def tearDown(self): - # Delete existing interfaces - self.session.delete(base_path) - self.session.commit() - - def test_add_address(self): - """ Check if address is added to interface """ - for intf in interfaces: - for addr in test_addr: - self.session.set(base_path + [intf, 'address', addr]) - self.session.commit() - - for intf in interfaces: - for af in AF_INET, AF_INET6: - for addr in ifaddresses(intf)[af]: - self.assertTrue(is_intf_addr_assigned(intf, addr['addr'])) - -if __name__ == '__main__': - unittest.main() diff --git a/scripts/cli/test_interfaces.py b/scripts/cli/test_interfaces.py new file mode 100755 index 000000000..1f96bdfc6 --- /dev/null +++ b/scripts/cli/test_interfaces.py @@ -0,0 +1,95 @@ +#!/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 . + +import os +import unittest +import vyos.config +import vyos.configsession + +from netifaces import ifaddresses, AF_INET, AF_INET6 +from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local +from vyos.interfaces import list_interfaces_of_type + +class BasicInterfaceTest: + class BaseTest(unittest.TestCase): + def setUp(self): + self.session = vyos.configsession.ConfigSession(os.getpid()) + env = self.session.get_session_env() + self.config = vyos.config.Config(session_env=env) + self._test_addr = ['192.0.2.1/25', '2001:db8:1::ffff/64'] + + def tearDown(self): + self.session.delete(self._base_path) + self.session.commit() + + def test_add_address(self): + """ Check if address can be added to interface """ + + # Add address + for intf in self._interfaces: + for addr in self._test_addr: + self.session.set(self._base_path + [intf, 'address', addr]) + self.session.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'])) + + +class BondInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + self._base_path = ['interfaces', 'bonding'] + self._interfaces = ['bond0'] + + def test_add_remove_member(self): + members = [] + # we need to filter out VLAN interfaces identified by a dot (.) + # in their name - just in case! + for tmp in list_interfaces_of_type("ethernet"): + if not '.' in tmp: + members.append(tmp) + + for intf in self._interfaces: + for member in members: + # We can not enslave an interface when there is an address + # assigned - thus we do not allow it in case someone + # runs vyos-smoketest on his production device + self.session.set(self._base_path + [intf, 'member', 'interface', member]) + + self.session.commit() + + for intf in self._interfaces: + self.session.delete(self._base_path + [intf, 'member']) + + self.session.commit() + + +class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + self._base_path = ['interfaces', 'bridge'] + self._interfaces = ['br0'] + + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From ecd4207f5620795356768f4235d0af8c6ab90248 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jan 2020 11:50:52 +0100 Subject: interface: adjust comments in BondInterfaceTest --- scripts/cli/test_interfaces.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/cli/test_interfaces.py b/scripts/cli/test_interfaces.py index 1f96bdfc6..8b1318791 100755 --- a/scripts/cli/test_interfaces.py +++ b/scripts/cli/test_interfaces.py @@ -72,8 +72,8 @@ class BondInterfaceTest(BasicInterfaceTest.BaseTest): for intf in self._interfaces: for member in members: # We can not enslave an interface when there is an address - # assigned - thus we do not allow it in case someone - # runs vyos-smoketest on his production device + # assigned - take care here - or find them dynamically if a user + # runs vyos-smoketest on his production device? self.session.set(self._base_path + [intf, 'member', 'interface', member]) self.session.commit() -- cgit v1.2.3 From c8814cf9692577c6bf963d2cfc4118f9ee1cd32a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jan 2020 12:01:15 +0100 Subject: interface: add test for bridge member interfaces --- scripts/cli/test_interfaces.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scripts/cli/test_interfaces.py b/scripts/cli/test_interfaces.py index 8b1318791..0e7c4dc07 100755 --- a/scripts/cli/test_interfaces.py +++ b/scripts/cli/test_interfaces.py @@ -90,6 +90,23 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): self._base_path = ['interfaces', 'bridge'] self._interfaces = ['br0'] + def test_add_remove_member(self): + members = list_interfaces_of_type("ethernet") + + for intf in self._interfaces: + cost = 1000 + priority = 10 + + self.session.set(self._base_path + [intf, 'stp']) + for member in members: + self.session.set(self._base_path + [intf, 'member', 'interface', member]) + self.session.set(self._base_path + [intf, 'member', 'interface', member, 'cost', str(cost)]) + self.session.set(self._base_path + [intf, 'member', 'interface', member, 'priority', str(priority)]) + cost += 1 + priority += 1 + + self.session.commit() + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 558c6349dc8b851fdf57411196b20765531005eb Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 26 Jan 2020 20:04:35 +0100 Subject: interface: bridge: delete member interfaces int test_add_remove_member() --- scripts/cli/test_interfaces.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/cli/test_interfaces.py b/scripts/cli/test_interfaces.py index 0e7c4dc07..fb5c53adc 100755 --- a/scripts/cli/test_interfaces.py +++ b/scripts/cli/test_interfaces.py @@ -104,7 +104,10 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): self.session.set(self._base_path + [intf, 'member', 'interface', member, 'priority', str(priority)]) cost += 1 priority += 1 + self.session.commit() + for intf in self._interfaces: + self.session.delete(self._base_path + [intf, 'member']) self.session.commit() -- cgit v1.2.3 From 4b8ee464d0899ae63ecc398b857eb144c013e4cb Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 1 Feb 2020 19:21:55 +0100 Subject: interface: add test case for interface description --- scripts/cli/test_interfaces.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scripts/cli/test_interfaces.py b/scripts/cli/test_interfaces.py index fb5c53adc..5ba01a35c 100755 --- a/scripts/cli/test_interfaces.py +++ b/scripts/cli/test_interfaces.py @@ -35,6 +35,20 @@ class BasicInterfaceTest: self.session.delete(self._base_path) self.session.commit() + def test_add_description(self): + """ Check if description can be added to interface """ + for intf in self._interfaces: + test_string='Description-Test-{}'.format(intf) + self.session.set(self._base_path + [intf, 'description', test_string]) + self.session.commit() + + # Validate interface description + for intf in self._interfaces: + test_string='Description-Test-{}'.format(intf) + with open('/sys/class/net/{}/ifalias'.format(intf), 'r') as f: + tmp = f.read().rstrip() + self.assertTrue(tmp, test_string) + def test_add_address(self): """ Check if address can be added to interface """ -- cgit v1.2.3 From bd85f1adc86b5645808cfc75fd1fef354146cba1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 1 Feb 2020 19:22:22 +0100 Subject: interface: add test for dummy interface --- scripts/cli/test_interfaces.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/cli/test_interfaces.py b/scripts/cli/test_interfaces.py index 5ba01a35c..4f52f83d7 100755 --- a/scripts/cli/test_interfaces.py +++ b/scripts/cli/test_interfaces.py @@ -69,6 +69,13 @@ class BasicInterfaceTest: self.assertTrue(is_intf_addr_assigned(intf, addr['addr'])) +class DummyInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + self._base_path = ['interfaces', 'dummy'] + self._interfaces = ['dum0', 'dum1', 'dum2'] + + class BondInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): super().setUp() -- cgit v1.2.3 From 4bb7dedba86d9bc8fd0613bbabe159a072c16c3e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 1 Feb 2020 19:22:32 +0100 Subject: interface: add test for loopback interface --- scripts/cli/test_interfaces.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/cli/test_interfaces.py b/scripts/cli/test_interfaces.py index 4f52f83d7..6bbacc585 100755 --- a/scripts/cli/test_interfaces.py +++ b/scripts/cli/test_interfaces.py @@ -76,6 +76,13 @@ class DummyInterfaceTest(BasicInterfaceTest.BaseTest): self._interfaces = ['dum0', 'dum1', 'dum2'] +class LoopbackInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + self._base_path = ['interfaces', 'loopback'] + self._interfaces = ['lo'] + + class BondInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): super().setUp() -- cgit v1.2.3 From a8d94f0a16f3473c10ef0922ee2d6d6e9659c87a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Feb 2020 07:40:36 +0100 Subject: snmp: add test for SNMPv2 --- scripts/cli/test_service_snmp.py | 81 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100755 scripts/cli/test_service_snmp.py diff --git a/scripts/cli/test_service_snmp.py b/scripts/cli/test_service_snmp.py new file mode 100755 index 000000000..d8521b770 --- /dev/null +++ b/scripts/cli/test_service_snmp.py @@ -0,0 +1,81 @@ +#!/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 . + +import os +import re +import unittest + +import vyos.config +import vyos.configsession +import vyos.util as util +from vyos.validate import is_ipv4 + +SNMPD_CONF = '/etc/snmp/snmpd.conf' + +base_path = ['service', 'snmp'] + +def get_config_value(key): + tmp = util.read_file(SNMPD_CONF) + return re.findall(r'\n?{}\s+(.*)'.format(key), tmp) + +class TestSystemNameServer(unittest.TestCase): + def setUp(self): + self.session = vyos.configsession.ConfigSession(os.getpid()) + env = self.session.get_session_env() + self.config = vyos.config.Config(session_env=env) + + def tearDown(self): + # Delete SNNP configuration + self.session.delete(base_path) + self.session.commit() + + def test_snmpv2(self): + """ Check if SNMPv2 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'] + + for auth in ['ro', 'rw']: + community = 'VyOS' + auth + self.session.set(base_path + ['community', community, 'authorization', auth]) + for client in clients: + self.session.set(base_path + ['community', community, 'client', client]) + for network in networks: + self.session.set(base_path + ['community', community, 'network', network]) + for addr in listen: + self.session.set(base_path + ['listen-address', addr]) + + self.session.set(base_path + ['contact', 'maintainers@vyos.io']) + self.session.set(base_path + ['location', 'qemu']) + + self.session.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')[0] + expected = 'unix:/run/snmpd.socket' + for addr in listen: + if is_ipv4(addr): + expected += ',udp:{}:161'.format(addr) + else: + expected += ',udp6:[{}]:161'.format(addr) + + self.assertTrue(expected in config) + +if __name__ == '__main__': + unittest.main() + -- cgit v1.2.3 From 594ca265b327ffcacfef57f30bd24939be97bc90 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 4 Feb 2020 18:10:48 +0100 Subject: snmp: ensure snmpd process is running --- scripts/cli/test_service_snmp.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/scripts/cli/test_service_snmp.py b/scripts/cli/test_service_snmp.py index d8521b770..cca39f7c6 100755 --- a/scripts/cli/test_service_snmp.py +++ b/scripts/cli/test_service_snmp.py @@ -18,10 +18,12 @@ import os import re import unittest +from vyos.validate import is_ipv4 +from psutil import process_iter + import vyos.config import vyos.configsession import vyos.util as util -from vyos.validate import is_ipv4 SNMPD_CONF = '/etc/snmp/snmpd.conf' @@ -42,8 +44,8 @@ class TestSystemNameServer(unittest.TestCase): self.session.delete(base_path) self.session.commit() - def test_snmpv2(self): - """ Check if SNMPv2 can be configured and service runs """ + def test_snmp(self): + """ 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'] @@ -55,11 +57,12 @@ class TestSystemNameServer(unittest.TestCase): self.session.set(base_path + ['community', community, 'client', client]) for network in networks: self.session.set(base_path + ['community', community, 'network', network]) - for addr in listen: - self.session.set(base_path + ['listen-address', addr]) - self.session.set(base_path + ['contact', 'maintainers@vyos.io']) - self.session.set(base_path + ['location', 'qemu']) + for addr in listen: + self.session.set(base_path + ['listen-address', addr]) + + self.session.set(base_path + ['contact', 'maintainers@vyos.io']) + self.session.set(base_path + ['location', 'qemu']) self.session.commit() @@ -76,6 +79,9 @@ class TestSystemNameServer(unittest.TestCase): self.assertTrue(expected in config) + # Check for running process + self.assertTrue("snmpd" in (p.name() for p in process_iter())) + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 70f0d321325f3a7d9966c11c39dfb2ef6ecea97e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 4 Feb 2020 18:11:16 +0100 Subject: snmp: add testcase for SNMPv3 --- scripts/cli/test_service_snmp.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/scripts/cli/test_service_snmp.py b/scripts/cli/test_service_snmp.py index cca39f7c6..04db860f6 100755 --- a/scripts/cli/test_service_snmp.py +++ b/scripts/cli/test_service_snmp.py @@ -82,6 +82,29 @@ class TestSystemNameServer(unittest.TestCase): # Check for running process self.assertTrue("snmpd" in (p.name() for p in process_iter())) + def test_snmpv3(self): + """ Check if SNMPv3 can be configured and service runs""" + + self.session.set(base_path + ['v3', 'engineid', '0xaffedeadbeef']) + self.session.set(base_path + ['v3', 'group', 'default', 'mode', 'ro']) + # check validate() - a view must be created before this can be comitted + with self.assertRaises(vyos.configsession.ConfigSessionError): + self.session.commit() + + self.session.set(base_path + ['v3', 'view', 'default', 'oid', '1']) + self.session.set(base_path + ['v3', 'group', 'default', 'view', 'default']) + self.session.commit() + + # create user + for authpriv in ['auth', 'privacy']: + self.session.set(base_path + ['v3', 'user', 'vyos', authpriv, 'plaintext-key', 'vyos1234']) + self.session.set(base_path + ['v3', 'user', 'vyos', 'group', 'default']) + + # TODO: read in config file and check values + + # Check for running process + self.assertTrue("snmpd" in (p.name() for p in process_iter())) + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From a9eb356dd479330d23e598ac76521cc67356b963 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 5 Feb 2020 22:33:05 +0100 Subject: user: add test for adding/deleting system users --- debian/control | 1 + scripts/cli/test_system_login.py | 80 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100755 scripts/cli/test_system_login.py diff --git a/debian/control b/debian/control index 97f8b27ea..7dde84d3e 100644 --- a/debian/control +++ b/debian/control @@ -9,6 +9,7 @@ Build-Depends: debhelper (>= 9), quilt, python3-lxml, python3-nose, + python3-paramiko, python3-coverage Standards-Version: 3.9.6 diff --git a/scripts/cli/test_system_login.py b/scripts/cli/test_system_login.py new file mode 100755 index 000000000..c804a6117 --- /dev/null +++ b/scripts/cli/test_system_login.py @@ -0,0 +1,80 @@ +#!/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 . + +import os +import re +import unittest + +from paramiko import SSHClient, WarningPolicy + +import vyos.config +import vyos.configsession +import vyos.util as util + +base_path = ['system', 'login'] +users = ['vyos1', 'vyos2'] + +class TestSystemLoginServer(unittest.TestCase): + def setUp(self): + self.session = vyos.configsession.ConfigSession(os.getpid()) + env = self.session.get_session_env() + self.config = vyos.config.Config(session_env=env) + + def tearDown(self): + # Delete SNNP configuration + for user in users: + self.session.delete(base_path + ['user', user]) + + self.session.commit() + + def test_user(self): + """ Check if user can be created and we can SSH to localhost """ + + for user in users: + name = "VyOS Roxx " + user + home_dir = "/tmp/" + user + + self.session.set(base_path + ['user', user, 'authentication', 'plaintext-password', user]) + self.session.set(base_path + ['user', user, 'full-name', 'VyOS Roxx']) + self.session.set(base_path + ['user', user, 'home-directory', home_dir]) + + self.session.commit() + + # check if we can login via SSH + for user in users: + # check if homedir has been created + self.assertTrue(os.path.isdir("/tmp/" + user)) + + ssh = SSHClient() + ssh.set_missing_host_key_policy(WarningPolicy) + + try: + ssh.connect('localhost', username=user, password=user) + stdin, stdout, stderr = ssh.exec_command('who') + print(stdout.read().decode()) + except Exception as e: + print(e) + self.assertTrue(False) + + ssh.close() + + # check if homedir has been created + # this can only be done after we have connected via ssh and + # pam_mkhomedir was executes + self.assertTrue(os.path.isdir("/tmp/" + user)) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From 6aea18a15f96c733505fc7587ac35e263c165549 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 7 Feb 2020 19:07:35 +0100 Subject: Debian: move python3-paramiko from Build-Depends to Depends Commit a9eb356 ("user: add test for adding/deleting system users") added python3-paramiko to the list of build dependencies, but its a runtime dependency. --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 7dde84d3e..41a0338db 100644 --- a/debian/control +++ b/debian/control @@ -9,13 +9,13 @@ Build-Depends: debhelper (>= 9), quilt, python3-lxml, python3-nose, - python3-paramiko, python3-coverage Standards-Version: 3.9.6 Package: vyos-smoketest Architecture: all Depends: python3, + python3-paramiko, ${python3:Depends}, ${shlibs:Depends}, ${misc:Depends}, -- cgit v1.2.3 From 3d4af18fb0cb257d1466fac417c9f13f080ecd04 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 8 Feb 2020 17:11:40 +0100 Subject: Evaluate exit code when running tests --- bin/vyos-smoketest | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/bin/vyos-smoketest b/bin/vyos-smoketest index 6d3442622..cb039db42 100755 --- a/bin/vyos-smoketest +++ b/bin/vyos-smoketest @@ -15,9 +15,10 @@ # along with this program. If not, see . import os -import sys -from stat import * +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'): @@ -26,14 +27,16 @@ for root, dirs, files in os.walk('/usr/libexec/vyos/tests/smoke'): mode = os.stat(test_file).st_mode if mode & S_IXOTH: - try: - print('Running Testcase: ' + test_file) - os.system(test_file) - except Exception as e: - print('Testcase "{}" raised an exception'.format(test_file)) + 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: - sys.exit(0) + exit(0) -sys.exit(1) +print("ERROR: One or more tests failed!") +exit(1) -- cgit v1.2.3 From 189b724dd7e5f4a5be4137809376127298bf9d25 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 9 Feb 2020 13:00:07 +0100 Subject: login: remove ssh test --- debian/control | 1 - scripts/cli/test_system_login.py | 25 ------------------------- 2 files changed, 26 deletions(-) diff --git a/debian/control b/debian/control index 41a0338db..97f8b27ea 100644 --- a/debian/control +++ b/debian/control @@ -15,7 +15,6 @@ Standards-Version: 3.9.6 Package: vyos-smoketest Architecture: all Depends: python3, - python3-paramiko, ${python3:Depends}, ${shlibs:Depends}, ${misc:Depends}, diff --git a/scripts/cli/test_system_login.py b/scripts/cli/test_system_login.py index c804a6117..b14b52603 100755 --- a/scripts/cli/test_system_login.py +++ b/scripts/cli/test_system_login.py @@ -18,8 +18,6 @@ import os import re import unittest -from paramiko import SSHClient, WarningPolicy - import vyos.config import vyos.configsession import vyos.util as util @@ -53,28 +51,5 @@ class TestSystemLoginServer(unittest.TestCase): self.session.commit() - # check if we can login via SSH - for user in users: - # check if homedir has been created - self.assertTrue(os.path.isdir("/tmp/" + user)) - - ssh = SSHClient() - ssh.set_missing_host_key_policy(WarningPolicy) - - try: - ssh.connect('localhost', username=user, password=user) - stdin, stdout, stderr = ssh.exec_command('who') - print(stdout.read().decode()) - except Exception as e: - print(e) - self.assertTrue(False) - - ssh.close() - - # check if homedir has been created - # this can only be done after we have connected via ssh and - # pam_mkhomedir was executes - self.assertTrue(os.path.isdir("/tmp/" + user)) - if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 6c2a6e0153ea36e5690661f0dec634322352f21a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 9 Feb 2020 13:02:18 +0100 Subject: all: do not import entire module, use "from foo import" instead --- scripts/cli/test_interfaces.py | 9 +++++---- scripts/cli/test_service_snmp.py | 16 +++++++++------- scripts/cli/test_system_login.py | 11 ++++++----- scripts/cli/test_system_nameserver.py | 8 ++++---- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/scripts/cli/test_interfaces.py b/scripts/cli/test_interfaces.py index 6bbacc585..ad8e653ce 100755 --- a/scripts/cli/test_interfaces.py +++ b/scripts/cli/test_interfaces.py @@ -16,9 +16,9 @@ import os import unittest -import vyos.config -import vyos.configsession +from vyos.config import Config +from vyos.configsession import ConfigSession, ConfigSessionError from netifaces import ifaddresses, AF_INET, AF_INET6 from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local from vyos.interfaces import list_interfaces_of_type @@ -26,9 +26,9 @@ from vyos.interfaces import list_interfaces_of_type class BasicInterfaceTest: class BaseTest(unittest.TestCase): def setUp(self): - self.session = vyos.configsession.ConfigSession(os.getpid()) + self.session = ConfigSession(os.getpid()) env = self.session.get_session_env() - self.config = vyos.config.Config(session_env=env) + self.config = Config(session_env=env) self._test_addr = ['192.0.2.1/25', '2001:db8:1::ffff/64'] def tearDown(self): @@ -136,6 +136,7 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): for intf in self._interfaces: self.session.delete(self._base_path + [intf, 'member']) + self.session.commit() diff --git a/scripts/cli/test_service_snmp.py b/scripts/cli/test_service_snmp.py index 04db860f6..5fd5eafcc 100755 --- a/scripts/cli/test_service_snmp.py +++ b/scripts/cli/test_service_snmp.py @@ -21,29 +21,30 @@ import unittest from vyos.validate import is_ipv4 from psutil import process_iter -import vyos.config -import vyos.configsession +from vyos.config import Config +from vyos.configsession import ConfigSession, ConfigSessionError import vyos.util as util SNMPD_CONF = '/etc/snmp/snmpd.conf' - base_path = ['service', 'snmp'] def get_config_value(key): tmp = util.read_file(SNMPD_CONF) return re.findall(r'\n?{}\s+(.*)'.format(key), tmp) -class TestSystemNameServer(unittest.TestCase): +class TestSNMPService(unittest.TestCase): def setUp(self): - self.session = vyos.configsession.ConfigSession(os.getpid()) + self.session = ConfigSession(os.getpid()) env = self.session.get_session_env() - self.config = vyos.config.Config(session_env=env) + self.config = Config(session_env=env) def tearDown(self): # Delete SNNP configuration self.session.delete(base_path) self.session.commit() + del self.session + def test_snmp(self): """ Check if SNMP can be configured and service runs """ clients = ['192.0.2.1', '2001:db8::1'] @@ -82,13 +83,14 @@ class TestSystemNameServer(unittest.TestCase): # Check for running process self.assertTrue("snmpd" in (p.name() for p in process_iter())) + def test_snmpv3(self): """ Check if SNMPv3 can be configured and service runs""" self.session.set(base_path + ['v3', 'engineid', '0xaffedeadbeef']) self.session.set(base_path + ['v3', 'group', 'default', 'mode', 'ro']) # check validate() - a view must be created before this can be comitted - with self.assertRaises(vyos.configsession.ConfigSessionError): + with self.assertRaises(ConfigSessionError): self.session.commit() self.session.set(base_path + ['v3', 'view', 'default', 'oid', '1']) diff --git a/scripts/cli/test_system_login.py b/scripts/cli/test_system_login.py index b14b52603..aa26b3bec 100755 --- a/scripts/cli/test_system_login.py +++ b/scripts/cli/test_system_login.py @@ -18,18 +18,18 @@ import os import re import unittest -import vyos.config -import vyos.configsession +from vyos.config import Config +from vyos.configsession import ConfigSession, ConfigSessionError import vyos.util as util base_path = ['system', 'login'] users = ['vyos1', 'vyos2'] -class TestSystemLoginServer(unittest.TestCase): +class TestSystemLogin(unittest.TestCase): def setUp(self): - self.session = vyos.configsession.ConfigSession(os.getpid()) + self.session = ConfigSession(os.getpid()) env = self.session.get_session_env() - self.config = vyos.config.Config(session_env=env) + self.config = Config(session_env=env) def tearDown(self): # Delete SNNP configuration @@ -40,6 +40,7 @@ class TestSystemLoginServer(unittest.TestCase): def test_user(self): """ Check if user can be created and we can SSH to localhost """ + self.session.set(['service', 'ssh', 'port', '22']) for user in users: name = "VyOS Roxx " + user diff --git a/scripts/cli/test_system_nameserver.py b/scripts/cli/test_system_nameserver.py index a07ad31ad..eba8acd80 100755 --- a/scripts/cli/test_system_nameserver.py +++ b/scripts/cli/test_system_nameserver.py @@ -18,8 +18,8 @@ import os import re import unittest -import vyos.config -import vyos.configsession +from vyos.config import Config +from vyos.configsession import ConfigSession, ConfigSessionError import vyos.util as util RESOLV_CONF = '/etc/resolv.conf' @@ -33,9 +33,9 @@ def get_name_servers(): class TestSystemNameServer(unittest.TestCase): def setUp(self): - self.session = vyos.configsession.ConfigSession(os.getpid()) + self.session = ConfigSession(os.getpid()) env = self.session.get_session_env() - self.config = vyos.config.Config(session_env=env) + self.config = Config(session_env=env) # Delete existing name servers self.session.delete(base_path) -- cgit v1.2.3 From c033c77b487caeb9505217232add571f362f7afe Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 9 Feb 2020 13:02:40 +0100 Subject: all: delete session in tearDown() --- scripts/cli/test_interfaces.py | 2 ++ scripts/cli/test_system_login.py | 1 + scripts/cli/test_system_nameserver.py | 3 +++ 3 files changed, 6 insertions(+) diff --git a/scripts/cli/test_interfaces.py b/scripts/cli/test_interfaces.py index ad8e653ce..1b775e1fe 100755 --- a/scripts/cli/test_interfaces.py +++ b/scripts/cli/test_interfaces.py @@ -35,6 +35,8 @@ class BasicInterfaceTest: self.session.delete(self._base_path) self.session.commit() + del self.session + def test_add_description(self): """ Check if description can be added to interface """ for intf in self._interfaces: diff --git a/scripts/cli/test_system_login.py b/scripts/cli/test_system_login.py index aa26b3bec..88e5695e7 100755 --- a/scripts/cli/test_system_login.py +++ b/scripts/cli/test_system_login.py @@ -37,6 +37,7 @@ class TestSystemLogin(unittest.TestCase): self.session.delete(base_path + ['user', user]) self.session.commit() + del self.session def test_user(self): """ Check if user can be created and we can SSH to localhost """ diff --git a/scripts/cli/test_system_nameserver.py b/scripts/cli/test_system_nameserver.py index eba8acd80..07fc9494b 100755 --- a/scripts/cli/test_system_nameserver.py +++ b/scripts/cli/test_system_nameserver.py @@ -37,10 +37,13 @@ class TestSystemNameServer(unittest.TestCase): env = self.session.get_session_env() self.config = Config(session_env=env) + def tearDown(self): # Delete existing name servers self.session.delete(base_path) self.session.commit() + del self.session + def test_add_server(self): """ Check if server is added to resolv.conf """ for s in test_servers: -- cgit v1.2.3 From bcc0b5bd6e11a9fbc2fcb3c450fdb48bb85499e8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 9 Feb 2020 14:54:42 +0100 Subject: snmp: output expected value on error --- scripts/cli/test_service_snmp.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/cli/test_service_snmp.py b/scripts/cli/test_service_snmp.py index 5fd5eafcc..e9d04a1ae 100755 --- a/scripts/cli/test_service_snmp.py +++ b/scripts/cli/test_service_snmp.py @@ -78,7 +78,11 @@ class TestSNMPService(unittest.TestCase): else: expected += ',udp6:[{}]:161'.format(addr) - self.assertTrue(expected in config) + try: + self.assertTrue(expected in config) + except: + print("expected: {}".format(expected)) + print("config: {}".format(config)) # Check for running process self.assertTrue("snmpd" in (p.name() for p in process_iter())) -- cgit v1.2.3 From 1b96bcb56ffbf3b6161eb95383ac6726be0281c1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 9 Feb 2020 15:39:26 +0100 Subject: login: check if our newly created users can login to the system --- scripts/cli/test_system_login.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/cli/test_system_login.py b/scripts/cli/test_system_login.py index 88e5695e7..9e95a445d 100755 --- a/scripts/cli/test_system_login.py +++ b/scripts/cli/test_system_login.py @@ -18,6 +18,7 @@ import os import re import unittest +from subprocess import Popen, PIPE from vyos.config import Config from vyos.configsession import ConfigSession, ConfigSessionError import vyos.util as util @@ -53,5 +54,17 @@ class TestSystemLogin(unittest.TestCase): self.session.commit() + for user in users: + cmd = ['su','-', user] + proc = Popen(cmd, 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 vyos 4.19.101-amd64-vyos #1 SMP Sun Feb 2 10:18:07 UTC 2020 x86_64 GNU/Linux\n' + self.assertTrue(len(stdout) > 40) + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 0ffc1fa39944a202dc8cfd1a77a2493e59c84755 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 9 Feb 2020 15:45:59 +0100 Subject: ssh: basic test to see if SSH server launches --- scripts/cli/test_service_ssh.py | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100755 scripts/cli/test_service_ssh.py diff --git a/scripts/cli/test_service_ssh.py b/scripts/cli/test_service_ssh.py new file mode 100755 index 000000000..b5247d77a --- /dev/null +++ b/scripts/cli/test_service_ssh.py @@ -0,0 +1,47 @@ +#!/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 . + +import os +import unittest + +from psutil import process_iter +from vyos.config import Config +from vyos.configsession import ConfigSession, ConfigSessionError + +base_path = ['service', 'ssh'] + +class TestServiceSSH(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + env = self.session.get_session_env() + self.config = Config(session_env=env) + + def tearDown(self): + # Delete SSH configuration + self.session.delete(base_path) + self.session.commit() + + del self.session + + def test_ssh(self): + """ Check if SSH service can be configured and runs """ + self.session.set(base_path) + + # Check for running process + self.assertTrue("sshd" in (p.name() for p in process_iter())) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From 3f739f8e55f80948aebebf1f89f0ef49b0c14447 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 9 Feb 2020 15:46:22 +0100 Subject: snmp: only import read_file from vyos.util --- scripts/cli/test_service_snmp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/cli/test_service_snmp.py b/scripts/cli/test_service_snmp.py index e9d04a1ae..72f338e31 100755 --- a/scripts/cli/test_service_snmp.py +++ b/scripts/cli/test_service_snmp.py @@ -23,13 +23,13 @@ from psutil import process_iter from vyos.config import Config from vyos.configsession import ConfigSession, ConfigSessionError -import vyos.util as util +from vyos.util import read_file SNMPD_CONF = '/etc/snmp/snmpd.conf' base_path = ['service', 'snmp'] def get_config_value(key): - tmp = util.read_file(SNMPD_CONF) + tmp = read_file(SNMPD_CONF) return re.findall(r'\n?{}\s+(.*)'.format(key), tmp) class TestSNMPService(unittest.TestCase): -- cgit v1.2.3 From c63bf6ea07009a60fe2d00c5ca2c2264710200a7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 9 Feb 2020 15:46:46 +0100 Subject: login: fix typo in tearDown() --- scripts/cli/test_system_login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cli/test_system_login.py b/scripts/cli/test_system_login.py index 9e95a445d..4abbcaf20 100755 --- a/scripts/cli/test_system_login.py +++ b/scripts/cli/test_system_login.py @@ -33,7 +33,7 @@ class TestSystemLogin(unittest.TestCase): self.config = Config(session_env=env) def tearDown(self): - # Delete SNNP configuration + # Delete individual users from configuration for user in users: self.session.delete(base_path + ['user', user]) -- cgit v1.2.3 From 86c87d5110d9ad602a91f887da3fa50d024fd7aa Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 9 Feb 2020 18:42:39 +0100 Subject: ssh: add missing commit() --- scripts/cli/test_service_ssh.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/cli/test_service_ssh.py b/scripts/cli/test_service_ssh.py index b5247d77a..e272872ea 100755 --- a/scripts/cli/test_service_ssh.py +++ b/scripts/cli/test_service_ssh.py @@ -40,6 +40,9 @@ class TestServiceSSH(unittest.TestCase): """ Check if SSH service can be configured and runs """ self.session.set(base_path) + # commit changes + self.session.commit() + # Check for running process self.assertTrue("sshd" in (p.name() for p in process_iter())) -- cgit v1.2.3 From 50315abd38be71554a2daf18f8c3c23bf8690bcf Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 9 Feb 2020 18:43:08 +0100 Subject: ssh: add test for proper port and host validation --- scripts/cli/test_service_ssh.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/scripts/cli/test_service_ssh.py b/scripts/cli/test_service_ssh.py index e272872ea..f0ad2e565 100755 --- a/scripts/cli/test_service_ssh.py +++ b/scripts/cli/test_service_ssh.py @@ -14,15 +14,23 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import re import os import unittest from psutil import process_iter from vyos.config import Config from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.util import read_file +SSHD_CONF = '/etc/ssh/sshd_config' base_path = ['service', 'ssh'] +def get_config_value(key): + tmp = read_file(SSHD_CONF) + return re.findall(r'\n?{}\s+(.*)'.format(key), tmp) + + class TestServiceSSH(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) @@ -38,11 +46,20 @@ class TestServiceSSH(unittest.TestCase): def test_ssh(self): """ Check if SSH service can be configured and runs """ - self.session.set(base_path) + self.session.set(base_path + ['port', '2222']) + self.session.set(base_path + ['disable-host-validation']) # commit changes self.session.commit() + # Check configured port + port = get_config_value('Port')[0] + self.assertTrue("2222" in port) + + # Check DNS usage + dns = get_config_value('UseDNS')[0] + self.assertTrue("no" in dns) + # Check for running process self.assertTrue("sshd" in (p.name() for p in process_iter())) -- cgit v1.2.3 From d9af64aa0af6ba1e42fc0afd5cad10b1853e8a43 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 10 Feb 2020 19:14:05 +0100 Subject: Revert "snmp: output expected value on error" This reverts commit bcc0b5bd6e11a9fbc2fcb3c450fdb48bb85499e8. --- scripts/cli/test_service_snmp.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/scripts/cli/test_service_snmp.py b/scripts/cli/test_service_snmp.py index 72f338e31..2f918c929 100755 --- a/scripts/cli/test_service_snmp.py +++ b/scripts/cli/test_service_snmp.py @@ -78,11 +78,7 @@ class TestSNMPService(unittest.TestCase): else: expected += ',udp6:[{}]:161'.format(addr) - try: - self.assertTrue(expected in config) - except: - print("expected: {}".format(expected)) - print("config: {}".format(config)) + self.assertTrue(expected in config) # Check for running process self.assertTrue("snmpd" in (p.name() for p in process_iter())) -- cgit v1.2.3 From 8a871793c54dc45bd85fdb1b98f51bbfad3a25a2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Feb 2020 20:03:46 +0100 Subject: interface: bond: remove useless for loop on member removal --- scripts/cli/test_interfaces.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/cli/test_interfaces.py b/scripts/cli/test_interfaces.py index 1b775e1fe..712a150c6 100755 --- a/scripts/cli/test_interfaces.py +++ b/scripts/cli/test_interfaces.py @@ -108,9 +108,9 @@ class BondInterfaceTest(BasicInterfaceTest.BaseTest): self.session.commit() - for intf in self._interfaces: - self.session.delete(self._base_path + [intf, 'member']) + # check if member deletion works as expected + self.session.delete(self._base_path + [intf, 'member']) self.session.commit() -- cgit v1.2.3 From 42bc2005d32ac5c0fcd1ce3d2d7327e830d0dfb8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Feb 2020 20:04:15 +0100 Subject: interface: bond: check that only real interfaces can be enslaved --- scripts/cli/test_interfaces.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/cli/test_interfaces.py b/scripts/cli/test_interfaces.py index 712a150c6..4e6617319 100755 --- a/scripts/cli/test_interfaces.py +++ b/scripts/cli/test_interfaces.py @@ -108,6 +108,10 @@ class BondInterfaceTest(BasicInterfaceTest.BaseTest): self.session.commit() + # check validate() - we can only add existing interfaces + self.session.set(self._base_path + [intf, 'member', 'interface', 'eth99']) + with self.assertRaises(ConfigSessionError): + self.session.commit() # check if member deletion works as expected self.session.delete(self._base_path + [intf, 'member']) -- cgit v1.2.3 From 5014e35efc13d762ef12e7934ba59374751c487c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Feb 2020 13:16:56 +0100 Subject: ddns: add initial unittest --- scripts/cli/test_service_dns_dynamic.py | 109 ++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100755 scripts/cli/test_service_dns_dynamic.py diff --git a/scripts/cli/test_service_dns_dynamic.py b/scripts/cli/test_service_dns_dynamic.py new file mode 100755 index 000000000..2b65b38c7 --- /dev/null +++ b/scripts/cli/test_service_dns_dynamic.py @@ -0,0 +1,109 @@ +#!/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 . + +import re +import os +import unittest + +from psutil import process_iter +from vyos.config import Config +from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.util import read_file + +DDCLIENT_CONF = '/etc/ddclient/ddclient.conf' +base_path = ['service', 'dns', 'dynamic'] + +def get_config_value(key): + tmp = read_file(DDCLIENT_CONF) + return re.findall(r'\n?{}\s+(.*)'.format(key), tmp) + + +class TestServiceDDNS(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + env = self.session.get_session_env() + self.config = Config(session_env=env) + + def tearDown(self): + # Delete DDNS configuration + self.session.delete(base_path) + self.session.commit() + + del self.session + + def test_service(self): + """ Check individual DDNS service providers """ + ddns = ['interface', 'eth0', 'service'] + services = ['cloudflare'] + + for service in services: + self.session.set(base_path + ddns + [service, 'host-name', 'test.ddns.vyos.io']) + self.session.set(base_path + ddns + [service, 'login', 'vyos_user']) + self.session.set(base_path + ddns + [service, 'password', 'vyos_pass']) + + # commit changes + self.session.commit() + + # TODO: inspect generated configuration file + + # Check for running process + # process name changes dynamically "ddclient - sleeping for 270 seconds" + # thus we need a different approach + running = False + for p in process_iter(): + if "ddclient" in p.name(): + running = True + self.assertTrue(running) + + + def test_rfc2136(self): + """ Check if DDNS service can be configured and runs """ + ddns = ['interface', 'eth0', 'rfc2136', 'vyos'] + ddns_key_file = '/config/auth/my.key' + + self.session.set(base_path + ddns + ['key', ddns_key_file]) + self.session.set(base_path + ddns + ['record', 'test.ddns.vyos.io']) + self.session.set(base_path + ddns + ['server', 'ns1.vyos.io']) + self.session.set(base_path + ddns + ['ttl', '300']) + self.session.set(base_path + ddns + ['zone', 'vyos.io']) + + # ensure an exception will be raised as no key is present + if os.path.exists(ddns_key_file): + os.unlink(ddns_key_file) + + # check validate() - the key file does not exist yet + with self.assertRaises(ConfigSessionError): + self.session.commit() + + with open(ddns_key_file, 'w') as f: + f.write('S3cretKey') + + # commit changes + self.session.commit() + + # TODO: inspect generated configuration file + + # Check for running process + # process name changes dynamically "ddclient - sleeping for 270 seconds" + # thus we need a different approach + running = False + for p in process_iter(): + if "ddclient" in p.name(): + running = True + self.assertTrue(running) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From b612fb0a8e6afa94b2fb239335c8f41724689172 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Feb 2020 20:36:12 +0100 Subject: ddns: read back configuration file and check plausibility --- scripts/cli/test_service_dns_dynamic.py | 87 +++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 26 deletions(-) diff --git a/scripts/cli/test_service_dns_dynamic.py b/scripts/cli/test_service_dns_dynamic.py index 2b65b38c7..21d52be18 100755 --- a/scripts/cli/test_service_dns_dynamic.py +++ b/scripts/cli/test_service_dns_dynamic.py @@ -18,6 +18,7 @@ import re import os import unittest +from getpass import getuser from psutil import process_iter from vyos.config import Config from vyos.configsession import ConfigSession, ConfigSessionError @@ -28,8 +29,20 @@ base_path = ['service', 'dns', 'dynamic'] def get_config_value(key): tmp = read_file(DDCLIENT_CONF) - return re.findall(r'\n?{}\s+(.*)'.format(key), tmp) - + tmp = re.findall(r'\n?{}=+(.*)'.format(key), tmp) + tmp = tmp[0].rstrip(',') + return tmp + +def check_process(): + """ + Check for running process, process name changes dynamically e.g. + "ddclient - sleeping for 270 seconds", thus we need a different approach + """ + running = False + for p in process_iter(): + if "ddclient" in p.name(): + running = True + return running class TestServiceDDNS(unittest.TestCase): def setUp(self): @@ -47,26 +60,54 @@ class TestServiceDDNS(unittest.TestCase): def test_service(self): """ Check individual DDNS service providers """ ddns = ['interface', 'eth0', 'service'] - services = ['cloudflare'] + services = ['cloudflare', 'afraid', 'dyndns', 'zoneedit'] for service in services: + user = 'vyos_user' + password = 'vyos_pass' + zone = 'vyos.io' + self.session.delete(base_path) self.session.set(base_path + ddns + [service, 'host-name', 'test.ddns.vyos.io']) - self.session.set(base_path + ddns + [service, 'login', 'vyos_user']) - self.session.set(base_path + ddns + [service, 'password', 'vyos_pass']) - - # commit changes - self.session.commit() - - # TODO: inspect generated configuration file - - # Check for running process - # process name changes dynamically "ddclient - sleeping for 270 seconds" - # thus we need a different approach - running = False - for p in process_iter(): - if "ddclient" in p.name(): - running = True - self.assertTrue(running) + self.session.set(base_path + ddns + [service, 'login', user]) + self.session.set(base_path + ddns + [service, 'password', password]) + self.session.set(base_path + ddns + [service, 'zone', zone]) + + # commit changes + if service == 'cloudflare': + self.session.commit() + else: + # zone option only works on cloudflare, an exception is raised + # for all others + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.delete(base_path + ddns + [service, 'zone', 'vyos.io']) + # commit changes again - now it should work + self.session.commit() + + # we can only read the configuration file when we operate as 'root' + if getuser() == 'root': + protocol = get_config_value('protocol') + login = get_config_value('login') + pwd = get_config_value('password') + + # some services need special treatment + protoname = service + if service == 'cloudflare': + tmp = get_config_value('zone') + self.assertTrue(tmp == zone) + elif service == 'afraid': + protoname = 'freedns' + elif service == 'dyndns': + protoname = 'dyndns2' + elif service == 'zoneedit': + protoname = 'zoneedit1' + + self.assertTrue(protocol == protoname) + self.assertTrue(login == user) + self.assertTrue(pwd == "'" + password + "'") + + # Check for running process + self.assertTrue(check_process()) def test_rfc2136(self): @@ -97,13 +138,7 @@ class TestServiceDDNS(unittest.TestCase): # TODO: inspect generated configuration file # Check for running process - # process name changes dynamically "ddclient - sleeping for 270 seconds" - # thus we need a different approach - running = False - for p in process_iter(): - if "ddclient" in p.name(): - running = True - self.assertTrue(running) + self.assertTrue(check_process()) if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 8627392150bb9bc9e0f9f494ff2b0eb88f224e30 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 20:27:36 +0100 Subject: interfaces: split interface tests into individual files --- scripts/cli/base_interfaces_test.py | 76 ++++++++++++++++ scripts/cli/test_interfaces.py | 150 -------------------------------- scripts/cli/test_interfaces_bonding.py | 52 +++++++++++ scripts/cli/test_interfaces_bridge.py | 47 ++++++++++ scripts/cli/test_interfaces_dummy.py | 26 ++++++ scripts/cli/test_interfaces_loopback.py | 26 ++++++ 6 files changed, 227 insertions(+), 150 deletions(-) create mode 100644 scripts/cli/base_interfaces_test.py delete mode 100755 scripts/cli/test_interfaces.py create mode 100755 scripts/cli/test_interfaces_bonding.py create mode 100755 scripts/cli/test_interfaces_bridge.py create mode 100755 scripts/cli/test_interfaces_dummy.py create mode 100755 scripts/cli/test_interfaces_loopback.py diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py new file mode 100644 index 000000000..e6667f993 --- /dev/null +++ b/scripts/cli/base_interfaces_test.py @@ -0,0 +1,76 @@ +# 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 . + +import os +import unittest + +from vyos.config import Config +from vyos.configsession import ConfigSession, ConfigSessionError +from netifaces import ifaddresses, AF_INET, AF_INET6 +from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local +from vyos.interfaces import list_interfaces_of_type + +class BasicInterfaceTest: + class BaseTest(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + env = self.session.get_session_env() + self.config = Config(session_env=env) + + self._test_addr = ['192.0.2.1/25', '2001:db8:1::ffff/64'] + self._test_mtu = False + + def tearDown(self): + self.session.delete(self._base_path) + self.session.commit() + + del self.session + + def test_add_description(self): + """ + Check if description can be added to interface + """ + for intf in self._interfaces: + test_string='Description-Test-{}'.format(intf) + self.session.set(self._base_path + [intf, 'description', test_string]) + self.session.commit() + + # Validate interface description + for intf in self._interfaces: + test_string='Description-Test-{}'.format(intf) + with open('/sys/class/net/{}/ifalias'.format(intf), 'r') as f: + tmp = f.read().rstrip() + self.assertTrue(tmp, test_string) + + def test_add_address(self): + """ + Check if address can be added to interface + """ + + # Add address + for intf in self._interfaces: + for addr in self._test_addr: + self.session.set(self._base_path + [intf, 'address', addr]) + self.session.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'])) + diff --git a/scripts/cli/test_interfaces.py b/scripts/cli/test_interfaces.py deleted file mode 100755 index 4e6617319..000000000 --- a/scripts/cli/test_interfaces.py +++ /dev/null @@ -1,150 +0,0 @@ -#!/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 . - -import os -import unittest - -from vyos.config import Config -from vyos.configsession import ConfigSession, ConfigSessionError -from netifaces import ifaddresses, AF_INET, AF_INET6 -from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local -from vyos.interfaces import list_interfaces_of_type - -class BasicInterfaceTest: - class BaseTest(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) - env = self.session.get_session_env() - self.config = Config(session_env=env) - self._test_addr = ['192.0.2.1/25', '2001:db8:1::ffff/64'] - - def tearDown(self): - self.session.delete(self._base_path) - self.session.commit() - - del self.session - - def test_add_description(self): - """ Check if description can be added to interface """ - for intf in self._interfaces: - test_string='Description-Test-{}'.format(intf) - self.session.set(self._base_path + [intf, 'description', test_string]) - self.session.commit() - - # Validate interface description - for intf in self._interfaces: - test_string='Description-Test-{}'.format(intf) - with open('/sys/class/net/{}/ifalias'.format(intf), 'r') as f: - tmp = f.read().rstrip() - self.assertTrue(tmp, test_string) - - def test_add_address(self): - """ Check if address can be added to interface """ - - # Add address - for intf in self._interfaces: - for addr in self._test_addr: - self.session.set(self._base_path + [intf, 'address', addr]) - self.session.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'])) - - -class DummyInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - super().setUp() - self._base_path = ['interfaces', 'dummy'] - self._interfaces = ['dum0', 'dum1', 'dum2'] - - -class LoopbackInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - super().setUp() - self._base_path = ['interfaces', 'loopback'] - self._interfaces = ['lo'] - - -class BondInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - super().setUp() - self._base_path = ['interfaces', 'bonding'] - self._interfaces = ['bond0'] - - def test_add_remove_member(self): - members = [] - # we need to filter out VLAN interfaces identified by a dot (.) - # in their name - just in case! - for tmp in list_interfaces_of_type("ethernet"): - if not '.' in tmp: - members.append(tmp) - - for intf in self._interfaces: - for member in members: - # We can not enslave an interface when there is an address - # assigned - take care here - or find them dynamically if a user - # runs vyos-smoketest on his production device? - self.session.set(self._base_path + [intf, 'member', 'interface', member]) - - self.session.commit() - - # check validate() - we can only add existing interfaces - self.session.set(self._base_path + [intf, 'member', 'interface', 'eth99']) - with self.assertRaises(ConfigSessionError): - self.session.commit() - - # check if member deletion works as expected - self.session.delete(self._base_path + [intf, 'member']) - self.session.commit() - - -class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): - def setUp(self): - super().setUp() - self._base_path = ['interfaces', 'bridge'] - self._interfaces = ['br0'] - - def test_add_remove_member(self): - members = list_interfaces_of_type("ethernet") - - for intf in self._interfaces: - cost = 1000 - priority = 10 - - self.session.set(self._base_path + [intf, 'stp']) - for member in members: - self.session.set(self._base_path + [intf, 'member', 'interface', member]) - self.session.set(self._base_path + [intf, 'member', 'interface', member, 'cost', str(cost)]) - self.session.set(self._base_path + [intf, 'member', 'interface', member, 'priority', str(priority)]) - cost += 1 - priority += 1 - self.session.commit() - - for intf in self._interfaces: - self.session.delete(self._base_path + [intf, 'member']) - - self.session.commit() - - -if __name__ == '__main__': - unittest.main() diff --git a/scripts/cli/test_interfaces_bonding.py b/scripts/cli/test_interfaces_bonding.py new file mode 100755 index 000000000..7e6523778 --- /dev/null +++ b/scripts/cli/test_interfaces_bonding.py @@ -0,0 +1,52 @@ +#!/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 . + +from base_interfaces_test import * + +class BondInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + self._base_path = ['interfaces', 'bonding'] + self._interfaces = ['bond0'] + + def test_add_remove_member(self): + members = [] + # we need to filter out VLAN interfaces identified by a dot (.) + # in their name - just in case! + for tmp in list_interfaces_of_type("ethernet"): + if not '.' in tmp: + members.append(tmp) + + for intf in self._interfaces: + for member in members: + # We can not enslave an interface when there is an address + # assigned - take care here - or find them dynamically if a user + # runs vyos-smoketest on his production device? + self.session.set(self._base_path + [intf, 'member', 'interface', member]) + + self.session.commit() + + # check validate() - we can only add existing interfaces + self.session.set(self._base_path + [intf, 'member', 'interface', 'eth99']) + with self.assertRaises(ConfigSessionError): + self.session.commit() + + # check if member deletion works as expected + self.session.delete(self._base_path + [intf, 'member']) + self.session.commit() + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/cli/test_interfaces_bridge.py b/scripts/cli/test_interfaces_bridge.py new file mode 100755 index 000000000..72e5da65f --- /dev/null +++ b/scripts/cli/test_interfaces_bridge.py @@ -0,0 +1,47 @@ +#!/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 . + +from base_interfaces_test import * + +class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + self._base_path = ['interfaces', 'bridge'] + self._interfaces = ['br0'] + + def test_add_remove_member(self): + members = list_interfaces_of_type("ethernet") + + for intf in self._interfaces: + cost = 1000 + priority = 10 + + self.session.set(self._base_path + [intf, 'stp']) + for member in members: + self.session.set(self._base_path + [intf, 'member', 'interface', member]) + self.session.set(self._base_path + [intf, 'member', 'interface', member, 'cost', str(cost)]) + self.session.set(self._base_path + [intf, 'member', 'interface', member, 'priority', str(priority)]) + cost += 1 + priority += 1 + self.session.commit() + + for intf in self._interfaces: + self.session.delete(self._base_path + [intf, 'member']) + + self.session.commit() + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/cli/test_interfaces_dummy.py b/scripts/cli/test_interfaces_dummy.py new file mode 100755 index 000000000..3f0cb8bcc --- /dev/null +++ b/scripts/cli/test_interfaces_dummy.py @@ -0,0 +1,26 @@ +#!/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 . + +from base_interfaces_test import * + +class DummyInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + self._base_path = ['interfaces', 'dummy'] + self._interfaces = ['dum0', 'dum1', 'dum2'] + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/cli/test_interfaces_loopback.py b/scripts/cli/test_interfaces_loopback.py new file mode 100755 index 000000000..ceb06aced --- /dev/null +++ b/scripts/cli/test_interfaces_loopback.py @@ -0,0 +1,26 @@ +#!/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 . + +from base_interfaces_test import * + +class LoopbackInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + self._base_path = ['interfaces', 'loopback'] + self._interfaces = ['lo'] + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From 8077cb1106beb2d2e8eceaa258880198216a2c47 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 20:28:01 +0100 Subject: ethernet: add initial interface test --- scripts/cli/test_interfaces_ethernet.py | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100755 scripts/cli/test_interfaces_ethernet.py diff --git a/scripts/cli/test_interfaces_ethernet.py b/scripts/cli/test_interfaces_ethernet.py new file mode 100755 index 000000000..e070ab98a --- /dev/null +++ b/scripts/cli/test_interfaces_ethernet.py @@ -0,0 +1,34 @@ +#!/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 . + +from base_interfaces_test import * +from vyos.interfaces import list_interfaces_of_type + +class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + self._base_path = ['interfaces', 'ethernet'] + self._interfaces = [] + + # auto detect all ethernet interfaces + for interface in list_interfaces_of_type("ethernet"): + if '.' not in interface: + self._interfaces.append(interface) + + + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From f0b4e4ff74cf735488bba985748c448c7202a366 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 20:31:19 +0100 Subject: gitignore: initial version --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..e57e2953a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +debian/.debhelper/ +debian/files +debian/vyos-smoketest.debhelper.log +debian/vyos-smoketest.substvars +debian/vyos-smoketest/ -- cgit v1.2.3 From 175447bfec4cb60068fcea9899b09fcbdd2103ba Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Mon, 17 Feb 2020 19:35:59 +0000 Subject: Makefile: add "make deb" target adding an option to the Makefile to build the debian package --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index c99579b45..e3d0d706c 100644 --- a/Makefile +++ b/Makefile @@ -2,3 +2,6 @@ all: # Install is just xcopy + +deb: + dpkg-buildpackage -uc -us -tc -b -- cgit v1.2.3 From f95c1471f241f2c0a4d0dca5832a1636755ee391 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 20:47:54 +0100 Subject: interfaces: add generic MTU test which can be enabled per interface --- scripts/cli/base_interfaces_test.py | 23 ++++++++++++++++++++++- scripts/cli/test_interfaces_bonding.py | 5 ++++- scripts/cli/test_interfaces_bridge.py | 3 +++ scripts/cli/test_interfaces_ethernet.py | 2 ++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index e6667f993..885995fb8 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -55,7 +55,7 @@ class BasicInterfaceTest: def test_add_address(self): """ - Check if address can be added to interface + Check if IPv4/IPv6 addresses can be added to interface. """ # Add address @@ -74,3 +74,24 @@ class BasicInterfaceTest: self.assertTrue(is_intf_addr_assigned(intf, addr['addr'])) + + def test_change_mtu(self): + """ + Check if MTU can be changed on interface. + Test MTU size will be 1400 bytes. + """ + if self._test_mtu is False: + return None + + # choose a MTU which works on every interface + mtu = '1400' + for intf in self._interfaces: + self.session.set(self._base_path + [intf, 'mtu', mtu]) + + self.session.commit() + + # Validate interface description + for intf in self._interfaces: + with open('/sys/class/net/{}/mtu'.format(intf), 'r') as f: + tmp = f.read().rstrip() + self.assertTrue(tmp == mtu) diff --git a/scripts/cli/test_interfaces_bonding.py b/scripts/cli/test_interfaces_bonding.py index 7e6523778..e1abd7ff1 100755 --- a/scripts/cli/test_interfaces_bonding.py +++ b/scripts/cli/test_interfaces_bonding.py @@ -15,11 +15,14 @@ # along with this program. If not, see . from base_interfaces_test import * +from vyos.interfaces import list_interfaces_of_type -class BondInterfaceTest(BasicInterfaceTest.BaseTest): +class BondingInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): super().setUp() + self._base_path = ['interfaces', 'bonding'] + self._test_mtu = True self._interfaces = ['bond0'] def test_add_remove_member(self): diff --git a/scripts/cli/test_interfaces_bridge.py b/scripts/cli/test_interfaces_bridge.py index 72e5da65f..1d462645e 100755 --- a/scripts/cli/test_interfaces_bridge.py +++ b/scripts/cli/test_interfaces_bridge.py @@ -15,11 +15,14 @@ # along with this program. If not, see . from base_interfaces_test import * +from vyos.interfaces import list_interfaces_of_type class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): super().setUp() + self._base_path = ['interfaces', 'bridge'] + self._test_mtu = True self._interfaces = ['br0'] def test_add_remove_member(self): diff --git a/scripts/cli/test_interfaces_ethernet.py b/scripts/cli/test_interfaces_ethernet.py index e070ab98a..330db08e0 100755 --- a/scripts/cli/test_interfaces_ethernet.py +++ b/scripts/cli/test_interfaces_ethernet.py @@ -20,7 +20,9 @@ from vyos.interfaces import list_interfaces_of_type class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): super().setUp() + self._base_path = ['interfaces', 'ethernet'] + self._test_mtu = True self._interfaces = [] # auto detect all ethernet interfaces -- cgit v1.2.3 From 7311b15f601a7aedae96f545f752b208153bb73f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 20:48:13 +0100 Subject: ethernet: automatically detect available interfaces --- scripts/cli/test_interfaces_ethernet.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/cli/test_interfaces_ethernet.py b/scripts/cli/test_interfaces_ethernet.py index 330db08e0..f8eac6ec3 100755 --- a/scripts/cli/test_interfaces_ethernet.py +++ b/scripts/cli/test_interfaces_ethernet.py @@ -25,11 +25,11 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): self._test_mtu = True self._interfaces = [] - # auto detect all ethernet interfaces - for interface in list_interfaces_of_type("ethernet"): - if '.' not in interface: - self._interfaces.append(interface) - + # we need to filter out VLAN interfaces identified by a dot (.) + # in their name - just in case! + for tmp in list_interfaces_of_type("ethernet"): + if not '.' in tmp: + self._interfaces.append(tmp) if __name__ == '__main__': -- cgit v1.2.3 From bcd124eb8db82c0a857d5351a1b6fccf615d05dc Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 20:48:27 +0100 Subject: bridge: automatically detect available interfaces --- scripts/cli/test_interfaces_bridge.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/cli/test_interfaces_bridge.py b/scripts/cli/test_interfaces_bridge.py index 1d462645e..28ba218a6 100755 --- a/scripts/cli/test_interfaces_bridge.py +++ b/scripts/cli/test_interfaces_bridge.py @@ -26,19 +26,27 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): self._interfaces = ['br0'] def test_add_remove_member(self): - members = list_interfaces_of_type("ethernet") + members = [] + # we need to filter out VLAN interfaces identified by a dot (.) + # in their name - just in case! + for tmp in list_interfaces_of_type("ethernet"): + if not '.' in tmp: + members.append(tmp) for intf in self._interfaces: cost = 1000 priority = 10 self.session.set(self._base_path + [intf, 'stp']) + + # assign members to bridge interface for member in members: self.session.set(self._base_path + [intf, 'member', 'interface', member]) self.session.set(self._base_path + [intf, 'member', 'interface', member, 'cost', str(cost)]) self.session.set(self._base_path + [intf, 'member', 'interface', member, 'priority', str(priority)]) cost += 1 priority += 1 + self.session.commit() for intf in self._interfaces: -- cgit v1.2.3 From 83597a5f435703595d42cef4839acad6a341c6ec Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 20:53:52 +0100 Subject: bridge: disable MTU test as it can not be configured --- scripts/cli/test_interfaces_bridge.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/cli/test_interfaces_bridge.py b/scripts/cli/test_interfaces_bridge.py index 28ba218a6..d35ac6eb6 100755 --- a/scripts/cli/test_interfaces_bridge.py +++ b/scripts/cli/test_interfaces_bridge.py @@ -22,7 +22,6 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): super().setUp() self._base_path = ['interfaces', 'bridge'] - self._test_mtu = True self._interfaces = ['br0'] def test_add_remove_member(self): -- cgit v1.2.3 From 36629c88ccdf856a79627a710ff34a6f5a47cee4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 21:08:25 +0100 Subject: ethernet: generic tearDown() should not destroy ethernet CLI nodes --- scripts/cli/base_interfaces_test.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 885995fb8..6c558795a 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -32,9 +32,15 @@ class BasicInterfaceTest: self._test_mtu = False def tearDown(self): - self.session.delete(self._base_path) - self.session.commit() + # we should not remove ethernet from the overall CLI + if 'ethernet' in self._base_path: + self.session.delete(self._base_path) + for intf in self._interfaces: + self.session.set(self._base_path + [intf]) + else: + self.session.delete(self._base_path) + self.session.commit() del self.session def test_add_description(self): -- cgit v1.2.3 From 0078259e5eecee5439e0ef013b64235e4db91c76 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 21:20:22 +0100 Subject: ssh: get_config_value() should return str not list --- scripts/cli/test_service_ssh.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/cli/test_service_ssh.py b/scripts/cli/test_service_ssh.py index f0ad2e565..40e1433b2 100755 --- a/scripts/cli/test_service_ssh.py +++ b/scripts/cli/test_service_ssh.py @@ -28,7 +28,8 @@ base_path = ['service', 'ssh'] def get_config_value(key): tmp = read_file(SSHD_CONF) - return re.findall(r'\n?{}\s+(.*)'.format(key), tmp) + tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) + return tmp[0] class TestServiceSSH(unittest.TestCase): @@ -53,11 +54,11 @@ class TestServiceSSH(unittest.TestCase): self.session.commit() # Check configured port - port = get_config_value('Port')[0] + port = get_config_value('Port') self.assertTrue("2222" in port) # Check DNS usage - dns = get_config_value('UseDNS')[0] + dns = get_config_value('UseDNS') self.assertTrue("no" in dns) # Check for running process -- cgit v1.2.3 From 13686def7e7370ff910abce20b744f5ded932718 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 21:21:12 +0100 Subject: ssh: add test for disable-password-authentication --- scripts/cli/test_service_ssh.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/cli/test_service_ssh.py b/scripts/cli/test_service_ssh.py index 40e1433b2..e882b8f0a 100755 --- a/scripts/cli/test_service_ssh.py +++ b/scripts/cli/test_service_ssh.py @@ -49,6 +49,7 @@ class TestServiceSSH(unittest.TestCase): """ Check if SSH service can be configured and runs """ self.session.set(base_path + ['port', '2222']) self.session.set(base_path + ['disable-host-validation']) + self.session.set(base_path + ['disable-password-authentication']) # commit changes self.session.commit() @@ -61,6 +62,10 @@ class TestServiceSSH(unittest.TestCase): dns = get_config_value('UseDNS') self.assertTrue("no" in dns) + # Check PasswordAuthentication + pwd = get_config_value('PasswordAuthentication') + self.assertTrue("no" in pwd) + # Check for running process self.assertTrue("sshd" in (p.name() for p in process_iter())) -- cgit v1.2.3 From b6000fb7e48d049ce78afbdbeaa24d91c85f0191 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 21:21:54 +0100 Subject: ssh: clean out current configuration in setUp() --- scripts/cli/test_service_ssh.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/cli/test_service_ssh.py b/scripts/cli/test_service_ssh.py index e882b8f0a..43656be9b 100755 --- a/scripts/cli/test_service_ssh.py +++ b/scripts/cli/test_service_ssh.py @@ -38,11 +38,11 @@ class TestServiceSSH(unittest.TestCase): env = self.session.get_session_env() self.config = Config(session_env=env) - def tearDown(self): - # Delete SSH configuration + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) self.session.delete(base_path) - self.session.commit() + def tearDown(self): del self.session def test_ssh(self): -- cgit v1.2.3 From e38c7397d4a7f427bef1dfebf3e7d329d895ec1e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 21:22:20 +0100 Subject: snmp: get_config_value() should return str not list --- scripts/cli/test_service_snmp.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/cli/test_service_snmp.py b/scripts/cli/test_service_snmp.py index 2f918c929..e46b4b588 100755 --- a/scripts/cli/test_service_snmp.py +++ b/scripts/cli/test_service_snmp.py @@ -30,7 +30,8 @@ base_path = ['service', 'snmp'] def get_config_value(key): tmp = read_file(SNMPD_CONF) - return re.findall(r'\n?{}\s+(.*)'.format(key), tmp) + tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) + return tmp[0] class TestSNMPService(unittest.TestCase): def setUp(self): @@ -70,7 +71,7 @@ class TestSNMPService(unittest.TestCase): # 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')[0] + config = get_config_value('agentaddress') expected = 'unix:/run/snmpd.socket' for addr in listen: if is_ipv4(addr): @@ -100,6 +101,7 @@ class TestSNMPService(unittest.TestCase): # create user for authpriv in ['auth', 'privacy']: self.session.set(base_path + ['v3', 'user', 'vyos', authpriv, 'plaintext-key', 'vyos1234']) + self.session.set(base_path + ['v3', 'user', 'vyos', 'group', 'default']) # TODO: read in config file and check values -- cgit v1.2.3 From 963825a3a32e3bff4f298b2accf2b7e3ed13b91b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 21:22:37 +0100 Subject: snmp: clean out current configuration in setUp() --- scripts/cli/test_service_snmp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/cli/test_service_snmp.py b/scripts/cli/test_service_snmp.py index e46b4b588..d2e5fff27 100755 --- a/scripts/cli/test_service_snmp.py +++ b/scripts/cli/test_service_snmp.py @@ -39,11 +39,11 @@ class TestSNMPService(unittest.TestCase): env = self.session.get_session_env() self.config = Config(session_env=env) - def tearDown(self): - # Delete SNNP configuration + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) self.session.delete(base_path) - self.session.commit() + def tearDown(self): del self.session def test_snmp(self): -- cgit v1.2.3 From c521c670dd2b6a3dfe66a9a2c32382fb3e6524a8 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Mon, 17 Feb 2020 20:54:20 +0000 Subject: tunnel: T2028: check for tunnel creation check that we are able to create tunnel for all encapsulation supported in interfaces tunnel. --- scripts/cli/test_interfaces_tunnel.py | 82 +++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 scripts/cli/test_interfaces_tunnel.py diff --git a/scripts/cli/test_interfaces_tunnel.py b/scripts/cli/test_interfaces_tunnel.py new file mode 100644 index 000000000..01d6ff93b --- /dev/null +++ b/scripts/cli/test_interfaces_tunnel.py @@ -0,0 +1,82 @@ +#!/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 . + +from base_interfaces_test import * + +from vyos.ifconfig import Interface + + +class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): + _base_path = ['interfaces', 'tunnel'] + _interfaces = [] + + # encoding, tunnel endpoint (v4/v6), address (v4/v6) + _valid = [ + ('gre', 4, 4), + ('gre', 4, 6), + ('ip6gre', 6, 4), + ('ip6gre', 6, 6), + ('gre-bridge', 4, 4), + ('ipip', 4, 4), + ('ipip', 4, 6), + ('ipip6', 6, 4), + ('ipip6', 6, 6), + ('ip6ip6', 6, 6), + ('sit', 4, 6), + ] + + remote = { + 4: '192.0.{}.1', + 6: '2002::{}:1', + } + + addrs = { + 4: '10.100.{}.1/24', + 6: '2001:db8:{}::1/64', + } + + def setUp(self): + for number in range(len(self._valid)): + self._interfaces.append('tun%d' % (number+1)) + self._test_mtu = True + super().setUp() + + self.session.set(['interfaces', 'dummy', 'dum444', + 'address', '169.254.0.1/24']) + self.session.set( + ['interfaces', 'dummy', 'dum666', 'address', '2002::1/16']) + self.session.commit() + + local = { + 4: Interface('dum444').get_addr()[0].split('/')[0], + 6: Interface('dum666').get_addr()[0].split('/')[0], + } + + number = 1 + for encap, p2p, addr in self._valid: + intf = 'tun%d' % number + tunnel = {} + tunnel['encapsulation'] = encap + tunnel['local-ip'] = local[p2p].format(number) + tunnel['remote-ip'] = self.remote[p2p].format(number) + tunnel['address'] = self.addrs[addr].format(number) + for name in tunnel: + self.session.set(self._base_path + [intf, name, tunnel[name]]) + number += 1 + + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From b6fd5d5799590c5069e1b34c0e0381c684bd94ce Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 17 Feb 2020 22:15:12 +0100 Subject: tunnel: enable unittest (chmod +x) --- scripts/cli/test_interfaces_tunnel.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/cli/test_interfaces_tunnel.py diff --git a/scripts/cli/test_interfaces_tunnel.py b/scripts/cli/test_interfaces_tunnel.py old mode 100644 new mode 100755 -- cgit v1.2.3 From 74888b22f92abf7f10c8b0baddf815fb5d462897 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 19 Feb 2020 08:47:31 +0100 Subject: tunnel: remove duplicated code --- scripts/cli/test_interfaces_tunnel.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/scripts/cli/test_interfaces_tunnel.py b/scripts/cli/test_interfaces_tunnel.py index 01d6ff93b..f67bda702 100755 --- a/scripts/cli/test_interfaces_tunnel.py +++ b/scripts/cli/test_interfaces_tunnel.py @@ -15,10 +15,8 @@ # along with this program. If not, see . from base_interfaces_test import * - from vyos.ifconfig import Interface - class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): _base_path = ['interfaces', 'tunnel'] _interfaces = [] @@ -54,10 +52,9 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): self._test_mtu = True super().setUp() - self.session.set(['interfaces', 'dummy', 'dum444', - 'address', '169.254.0.1/24']) - self.session.set( - ['interfaces', 'dummy', 'dum666', 'address', '2002::1/16']) + base_path = ['interfaces', 'dummy'] + self.session.set(base_path + ['dum444', 'address', '169.254.0.1/24']) + self.session.set(base_path + ['dum666', 'address', '2002::1/16']) self.session.commit() local = { @@ -77,6 +74,5 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): self.session.set(self._base_path + [intf, name, tunnel[name]]) number += 1 - if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 30965ed538d6b90e338c25c4c5bfe45cebdc1ce4 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Wed, 19 Feb 2020 09:53:33 +0000 Subject: tunnel: tidying up move all variable creation within setUp() create the interfaces using a list comprehension --- scripts/cli/test_interfaces_tunnel.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/cli/test_interfaces_tunnel.py b/scripts/cli/test_interfaces_tunnel.py index f67bda702..136a33c35 100755 --- a/scripts/cli/test_interfaces_tunnel.py +++ b/scripts/cli/test_interfaces_tunnel.py @@ -17,10 +17,8 @@ from base_interfaces_test import * from vyos.ifconfig import Interface -class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): - _base_path = ['interfaces', 'tunnel'] - _interfaces = [] +class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): # encoding, tunnel endpoint (v4/v6), address (v4/v6) _valid = [ ('gre', 4, 4), @@ -47,11 +45,13 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): } def setUp(self): - for number in range(len(self._valid)): - self._interfaces.append('tun%d' % (number+1)) - self._test_mtu = True super().setUp() + self._base_path = ['interfaces', 'tunnel'] + self._interfaces = ['tun%d' % (n+1) for n in range(len(self._valid))] + self._test_mtu = True + + # creating two dummy interface as to use as local-ip for the tunnels base_path = ['interfaces', 'dummy'] self.session.set(base_path + ['dum444', 'address', '169.254.0.1/24']) self.session.set(base_path + ['dum666', 'address', '2002::1/16']) -- cgit v1.2.3 From f1beefe681240113c6ec856b01e55dc646f52294 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 26 Feb 2020 07:27:52 +0100 Subject: cli: remove superfluous imports (we never use Config()) Every test hat a Config() instance but it was never referenced. It has been introduced by simple means of copy/pasting the first nameserver test. --- scripts/cli/base_interfaces_test.py | 3 --- scripts/cli/test_service_dns_dynamic.py | 3 --- scripts/cli/test_service_snmp.py | 4 ---- scripts/cli/test_service_ssh.py | 4 ---- scripts/cli/test_system_login.py | 3 --- scripts/cli/test_system_nameserver.py | 3 --- 6 files changed, 20 deletions(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 6c558795a..180a11bfb 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -15,7 +15,6 @@ import os import unittest -from vyos.config import Config from vyos.configsession import ConfigSession, ConfigSessionError from netifaces import ifaddresses, AF_INET, AF_INET6 from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local @@ -25,8 +24,6 @@ class BasicInterfaceTest: class BaseTest(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) - env = self.session.get_session_env() - self.config = Config(session_env=env) self._test_addr = ['192.0.2.1/25', '2001:db8:1::ffff/64'] self._test_mtu = False diff --git a/scripts/cli/test_service_dns_dynamic.py b/scripts/cli/test_service_dns_dynamic.py index 21d52be18..e6205cf8b 100755 --- a/scripts/cli/test_service_dns_dynamic.py +++ b/scripts/cli/test_service_dns_dynamic.py @@ -20,7 +20,6 @@ import unittest from getpass import getuser from psutil import process_iter -from vyos.config import Config from vyos.configsession import ConfigSession, ConfigSessionError from vyos.util import read_file @@ -47,8 +46,6 @@ def check_process(): class TestServiceDDNS(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) - env = self.session.get_session_env() - self.config = Config(session_env=env) def tearDown(self): # Delete DDNS configuration diff --git a/scripts/cli/test_service_snmp.py b/scripts/cli/test_service_snmp.py index d2e5fff27..d4bbdc0b1 100755 --- a/scripts/cli/test_service_snmp.py +++ b/scripts/cli/test_service_snmp.py @@ -21,7 +21,6 @@ import unittest from vyos.validate import is_ipv4 from psutil import process_iter -from vyos.config import Config from vyos.configsession import ConfigSession, ConfigSessionError from vyos.util import read_file @@ -36,9 +35,6 @@ def get_config_value(key): class TestSNMPService(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) - env = self.session.get_session_env() - self.config = Config(session_env=env) - # ensure we can also run this test on a live system - so lets clean # out the current configuration :) self.session.delete(base_path) diff --git a/scripts/cli/test_service_ssh.py b/scripts/cli/test_service_ssh.py index 43656be9b..f9f0de3db 100755 --- a/scripts/cli/test_service_ssh.py +++ b/scripts/cli/test_service_ssh.py @@ -19,7 +19,6 @@ import os import unittest from psutil import process_iter -from vyos.config import Config from vyos.configsession import ConfigSession, ConfigSessionError from vyos.util import read_file @@ -35,9 +34,6 @@ def get_config_value(key): class TestServiceSSH(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) - env = self.session.get_session_env() - self.config = Config(session_env=env) - # ensure we can also run this test on a live system - so lets clean # out the current configuration :) self.session.delete(base_path) diff --git a/scripts/cli/test_system_login.py b/scripts/cli/test_system_login.py index 4abbcaf20..3c4b1fa28 100755 --- a/scripts/cli/test_system_login.py +++ b/scripts/cli/test_system_login.py @@ -19,7 +19,6 @@ import re import unittest from subprocess import Popen, PIPE -from vyos.config import Config from vyos.configsession import ConfigSession, ConfigSessionError import vyos.util as util @@ -29,8 +28,6 @@ users = ['vyos1', 'vyos2'] class TestSystemLogin(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) - env = self.session.get_session_env() - self.config = Config(session_env=env) def tearDown(self): # Delete individual users from configuration diff --git a/scripts/cli/test_system_nameserver.py b/scripts/cli/test_system_nameserver.py index 07fc9494b..9040be072 100755 --- a/scripts/cli/test_system_nameserver.py +++ b/scripts/cli/test_system_nameserver.py @@ -18,7 +18,6 @@ import os import re import unittest -from vyos.config import Config from vyos.configsession import ConfigSession, ConfigSessionError import vyos.util as util @@ -34,8 +33,6 @@ def get_name_servers(): class TestSystemNameServer(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) - env = self.session.get_session_env() - self.config = Config(session_env=env) def tearDown(self): # Delete existing name servers -- cgit v1.2.3 From e5acf008785066e2266a913703abb7b9f5e99286 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 26 Feb 2020 07:38:13 +0100 Subject: interfaces: replace wildcard import with dedicated import --- scripts/cli/test_interfaces_bonding.py | 2 +- scripts/cli/test_interfaces_bridge.py | 2 +- scripts/cli/test_interfaces_dummy.py | 2 +- scripts/cli/test_interfaces_ethernet.py | 2 +- scripts/cli/test_interfaces_loopback.py | 2 +- scripts/cli/test_interfaces_tunnel.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/cli/test_interfaces_bonding.py b/scripts/cli/test_interfaces_bonding.py index e1abd7ff1..ccb4428b4 100755 --- a/scripts/cli/test_interfaces_bonding.py +++ b/scripts/cli/test_interfaces_bonding.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from base_interfaces_test import * +from base_interfaces_test import BasicInterfaceTest from vyos.interfaces import list_interfaces_of_type class BondingInterfaceTest(BasicInterfaceTest.BaseTest): diff --git a/scripts/cli/test_interfaces_bridge.py b/scripts/cli/test_interfaces_bridge.py index d35ac6eb6..545239038 100755 --- a/scripts/cli/test_interfaces_bridge.py +++ b/scripts/cli/test_interfaces_bridge.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from base_interfaces_test import * +from base_interfaces_test import BasicInterfaceTest from vyos.interfaces import list_interfaces_of_type class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): diff --git a/scripts/cli/test_interfaces_dummy.py b/scripts/cli/test_interfaces_dummy.py index 3f0cb8bcc..21d0a5cf8 100755 --- a/scripts/cli/test_interfaces_dummy.py +++ b/scripts/cli/test_interfaces_dummy.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from base_interfaces_test import * +from base_interfaces_test import BasicInterfaceTest class DummyInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): diff --git a/scripts/cli/test_interfaces_ethernet.py b/scripts/cli/test_interfaces_ethernet.py index f8eac6ec3..a2cede02e 100755 --- a/scripts/cli/test_interfaces_ethernet.py +++ b/scripts/cli/test_interfaces_ethernet.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from base_interfaces_test import * +from base_interfaces_test import BasicInterfaceTest from vyos.interfaces import list_interfaces_of_type class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): diff --git a/scripts/cli/test_interfaces_loopback.py b/scripts/cli/test_interfaces_loopback.py index ceb06aced..697c7cd99 100755 --- a/scripts/cli/test_interfaces_loopback.py +++ b/scripts/cli/test_interfaces_loopback.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from base_interfaces_test import * +from base_interfaces_test import BasicInterfaceTest class LoopbackInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): diff --git a/scripts/cli/test_interfaces_tunnel.py b/scripts/cli/test_interfaces_tunnel.py index f67bda702..980bfa726 100755 --- a/scripts/cli/test_interfaces_tunnel.py +++ b/scripts/cli/test_interfaces_tunnel.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from base_interfaces_test import * +from base_interfaces_test import BasicInterfaceTest from vyos.ifconfig import Interface class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): -- cgit v1.2.3 From 3d94f59766dbfa3230bcebc9233efe8dbf807b2e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 26 Feb 2020 18:28:49 +0100 Subject: interfaces: add missing unittest import --- scripts/cli/test_interfaces_bonding.py | 2 ++ scripts/cli/test_interfaces_bridge.py | 2 ++ scripts/cli/test_interfaces_dummy.py | 2 ++ scripts/cli/test_interfaces_ethernet.py | 2 ++ scripts/cli/test_interfaces_loopback.py | 2 ++ scripts/cli/test_interfaces_tunnel.py | 2 ++ 6 files changed, 12 insertions(+) diff --git a/scripts/cli/test_interfaces_bonding.py b/scripts/cli/test_interfaces_bonding.py index ccb4428b4..d42e34ee4 100755 --- a/scripts/cli/test_interfaces_bonding.py +++ b/scripts/cli/test_interfaces_bonding.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import unittest + from base_interfaces_test import BasicInterfaceTest from vyos.interfaces import list_interfaces_of_type diff --git a/scripts/cli/test_interfaces_bridge.py b/scripts/cli/test_interfaces_bridge.py index 545239038..100f6fdf2 100755 --- a/scripts/cli/test_interfaces_bridge.py +++ b/scripts/cli/test_interfaces_bridge.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import unittest + from base_interfaces_test import BasicInterfaceTest from vyos.interfaces import list_interfaces_of_type diff --git a/scripts/cli/test_interfaces_dummy.py b/scripts/cli/test_interfaces_dummy.py index 21d0a5cf8..01942fc89 100755 --- a/scripts/cli/test_interfaces_dummy.py +++ b/scripts/cli/test_interfaces_dummy.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import unittest + from base_interfaces_test import BasicInterfaceTest class DummyInterfaceTest(BasicInterfaceTest.BaseTest): diff --git a/scripts/cli/test_interfaces_ethernet.py b/scripts/cli/test_interfaces_ethernet.py index a2cede02e..a6c5e902c 100755 --- a/scripts/cli/test_interfaces_ethernet.py +++ b/scripts/cli/test_interfaces_ethernet.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import unittest + from base_interfaces_test import BasicInterfaceTest from vyos.interfaces import list_interfaces_of_type diff --git a/scripts/cli/test_interfaces_loopback.py b/scripts/cli/test_interfaces_loopback.py index 697c7cd99..4462f64fe 100755 --- a/scripts/cli/test_interfaces_loopback.py +++ b/scripts/cli/test_interfaces_loopback.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import unittest + from base_interfaces_test import BasicInterfaceTest class LoopbackInterfaceTest(BasicInterfaceTest.BaseTest): diff --git a/scripts/cli/test_interfaces_tunnel.py b/scripts/cli/test_interfaces_tunnel.py index 8f8311511..d0ccede54 100755 --- a/scripts/cli/test_interfaces_tunnel.py +++ b/scripts/cli/test_interfaces_tunnel.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import unittest + from base_interfaces_test import BasicInterfaceTest from vyos.ifconfig import Interface -- cgit v1.2.3 From dec387b1450ec9fcac9bca6c3df2991558444851 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 26 Feb 2020 19:02:54 +0100 Subject: interfaces: bond: add missing import on ConfigSessionError --- scripts/cli/test_interfaces_bonding.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/cli/test_interfaces_bonding.py b/scripts/cli/test_interfaces_bonding.py index d42e34ee4..ecd12b957 100755 --- a/scripts/cli/test_interfaces_bonding.py +++ b/scripts/cli/test_interfaces_bonding.py @@ -17,7 +17,9 @@ import unittest from base_interfaces_test import BasicInterfaceTest + from vyos.interfaces import list_interfaces_of_type +from vyos.configsession import ConfigSessionError class BondingInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): -- cgit v1.2.3 From a44bf235060e2b12c60b0f3dff7f4ef07615ab78 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 26 Feb 2020 19:03:25 +0100 Subject: interfaces: base: remove superfluous import of ConfigSessionError --- scripts/cli/base_interfaces_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 180a11bfb..12932605e 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -15,7 +15,7 @@ import os import unittest -from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.configsession import ConfigSession from netifaces import ifaddresses, AF_INET, AF_INET6 from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local from vyos.interfaces import list_interfaces_of_type -- cgit v1.2.3 From fdad8650e851ecc67d982139fbfce744708eb32d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 27 Feb 2020 21:17:53 +0100 Subject: pppoe: add initial test --- scripts/cli/test_interfaces_pppoe.py | 96 ++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100755 scripts/cli/test_interfaces_pppoe.py diff --git a/scripts/cli/test_interfaces_pppoe.py b/scripts/cli/test_interfaces_pppoe.py new file mode 100755 index 000000000..714e3e739 --- /dev/null +++ b/scripts/cli/test_interfaces_pppoe.py @@ -0,0 +1,96 @@ +#!/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 . + +import re +import os +import unittest + +from psutil import process_iter +from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.util import read_file + +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 [] + +class PPPoEInterfaceTest(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + self._interfaces = ['pppoe0', 'pppoe1', 'pppoe2'] + + def tearDown(self): + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_pppoe_1(self): + """ Check if PPPoE dialer can be configured and runs """ + # ensure source-interface is available + source_interface = 'eth0' + self.session.set(['interfaces', 'ethernet', source_interface]) + + for interface in self._interfaces: + user = 'VyOS-user-' + interface + passwd = 'VyOS-passwd-' + interface + + self.session.set(base_path + [interface, 'authentication', 'user', user]) + self.session.set(base_path + [interface, 'authentication', 'password', passwd]) + self.session.set(base_path + [interface, 'default-route', 'auto']) + self.session.set(base_path + [interface, 'mtu', '1400']) + self.session.set(base_path + [interface, 'no-peer-dns']) + + # check validate() - a source-interface is required + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(base_path + [interface, 'source-interface', 'eth0']) + + # commit changes + self.session.commit() + + # verify configuration file(s) + for interface in self._interfaces: + user = 'VyOS-user-' + interface + password = 'VyOS-passwd-' + interface + + cur_mtu = get_config_value(interface, 'mtu')[1] + cur_user = get_config_value(interface, 'user')[1].replace('"', '') + cur_password = get_config_value(interface, 'password')[1].replace('"', '') + cur_default_route = get_config_value(interface, 'defaultroute')[0] + cur_ifname = get_config_value(interface, 'ifname')[1] + + self.assertTrue(cur_mtu == '1400') + self.assertTrue(cur_user == user) + self.assertTrue(cur_password == password) + self.assertTrue(cur_default_route == 'defaultroute') + self.assertTrue(cur_ifname == interface) + + # Check if ppp process is running in the interface in question + running = False + for p in process_iter(): + if "pppd" in p.name(): + if interface in p.cmdline(): + running = True + + self.assertTrue(running) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From 5baaa066ff35b673e2b8453ea4a88688ad70ca20 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Thu, 27 Feb 2020 20:19:42 +0000 Subject: tunnel: fix several issues in testcase The addresses for the tunnels were not correctly assigned, leading to some tunnels sharing the same local-ip/remote-ip pairs. The tearnDown code was not removing the dummy interface created and required for testing. Still two tunnels are not deleted as the current code is not using 'ip tunnel delete' for ip6gre (most likely 'ip link delete' instead) This will be resolved when the tunnel interface code is moved to python/vyos-1x --- scripts/cli/test_interfaces_tunnel.py | 69 +++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/scripts/cli/test_interfaces_tunnel.py b/scripts/cli/test_interfaces_tunnel.py index d0ccede54..7611ffe26 100755 --- a/scripts/cli/test_interfaces_tunnel.py +++ b/scripts/cli/test_interfaces_tunnel.py @@ -14,11 +14,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os import unittest -from base_interfaces_test import BasicInterfaceTest -from vyos.ifconfig import Interface +from vyos.configsession import ConfigSession +from base_interfaces_test import BasicInterfaceTest class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): # encoding, tunnel endpoint (v4/v6), address (v4/v6) @@ -36,45 +37,73 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): ('sit', 4, 6), ] + local = { + 4: '10.100.{}.1/24', + 6: '2001:db8:{}::1/64', + } + remote = { 4: '192.0.{}.1', 6: '2002::{}:1', } - addrs = { + address = { 4: '10.100.{}.1/24', 6: '2001:db8:{}::1/64', } def setUp(self): - super().setUp() + local = {} + remote = {} + address = {} + self._intf_dummy = ['interfaces', 'dummy'] self._base_path = ['interfaces', 'tunnel'] - self._interfaces = ['tun%d' % (n+1) for n in range(len(self._valid))] + self._interfaces = ['tun{}'.format(n) for n in range(len(self._valid))] + self._test_mtu = True + super().setUp() - # creating two dummy interface as to use as local-ip for the tunnels - base_path = ['interfaces', 'dummy'] - self.session.set(base_path + ['dum444', 'address', '169.254.0.1/24']) - self.session.set(base_path + ['dum666', 'address', '2002::1/16']) - self.session.commit() + for number in range(len(self._valid)): + dum4 = 'dum4{}'.format(number) + dum6 = 'dum6{}'.format(number) - local = { - 4: Interface('dum444').get_addr()[0].split('/')[0], - 6: Interface('dum666').get_addr()[0].split('/')[0], - } + ipv4 = self.local[4].format(number) + ipv6 = self.local[6].format(number) - number = 1 - for encap, p2p, addr in self._valid: + local.setdefault(4, {})[number] = ipv4 + local.setdefault(6, {})[number] = ipv6 + + ipv4 = self.remote[4].format(number) + ipv6 = self.remote[6].format(number) + + remote.setdefault(4, {})[number] = ipv4 + remote.setdefault(6, {})[number] = ipv6 + + ipv4 = self.address[4].format(number) + ipv6 = self.address[6].format(number) + + address.setdefault(4, {})[number] = ipv4 + address.setdefault(6, {})[number] = ipv6 + + self.session.set(self._intf_dummy + [dum4, 'address', ipv4]) + self.session.set(self._intf_dummy + [dum6, 'address', ipv6]) + self.session.commit() + + for number, (encap, p2p, addr) in enumerate(self._valid): intf = 'tun%d' % number tunnel = {} tunnel['encapsulation'] = encap - tunnel['local-ip'] = local[p2p].format(number) - tunnel['remote-ip'] = self.remote[p2p].format(number) - tunnel['address'] = self.addrs[addr].format(number) + tunnel['local-ip'] = local[p2p][number].split('/')[0] + tunnel['remote-ip'] = remote[p2p][number].split('/')[0] + tunnel['address'] = address[addr][number] for name in tunnel: self.session.set(self._base_path + [intf, name, tunnel[name]]) - number += 1 + + def tearDown(self): + self.session.delete(self._intf_dummy) + super().tearDown() + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From dae341a019335e22ea31b3e12934be08d9c00b8a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 29 Feb 2020 08:50:37 +0100 Subject: interfaces: base: add rfc3021 test using /31 prefixes --- scripts/cli/base_interfaces_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 12932605e..11a7031ef 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -25,7 +25,8 @@ class BasicInterfaceTest: def setUp(self): self.session = ConfigSession(os.getpid()) - self._test_addr = ['192.0.2.1/25', '2001:db8:1::ffff/64'] + self._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'] self._test_mtu = False def tearDown(self): -- cgit v1.2.3 From 300452f724c7dcd1aec1a5afc132319347c14939 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 29 Feb 2020 09:38:19 +0100 Subject: interfaces: wireguard: add initial test WireGuard interface can not be easily inherit the BasicInterfaceTest class as every operation on WireGuard interfaces require IP addresses assigned to the interface - I do not now if this is really a requirement or just a to restrict validate() on the WireGuard interface. Nevertheless this helps to see if changes on e.g. ifconfig.py breaks this interface. --- scripts/cli/test_interfaces_wireguard.py | 65 ++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 scripts/cli/test_interfaces_wireguard.py diff --git a/scripts/cli/test_interfaces_wireguard.py b/scripts/cli/test_interfaces_wireguard.py new file mode 100644 index 000000000..ff1ff5d7e --- /dev/null +++ b/scripts/cli/test_interfaces_wireguard.py @@ -0,0 +1,65 @@ +#!/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 . + +import os +import unittest + +from vyos.configsession import ConfigSession, ConfigSessionError +from base_interfaces_test import BasicInterfaceTest + +# Generate WireGuard default keypair +if not os.path.isdir('/config/auth/wireguard/default'): + os.system('/usr/libexec/vyos/op_mode/wireguard.py --genkey') + +base_path = ['interfaces', 'wireguard'] + +class WireGuardInterfaceTest(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + self._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'] + self._interfaces = ['wg0', 'wg1'] + + def tearDown(self): + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_peer_setup(self): + """ + Create WireGuard interfaces with associated peers + """ + for intf in self._interfaces: + peer = 'foo-' + intf + psk = 'u2xdA70hkz0S1CG0dZlOh0aq2orwFXRIVrKo4DCvHgM=' + pubkey = 'n6ZZL7ph/QJUJSUUTyu19c77my1dRCDHkMzFQUO9Z3A=' + + for addr in self._test_addr: + self.session.set(base_path + [intf, 'address', addr]) + + self.session.set(base_path + [intf, 'peer', peer, 'endpoint', '127.0.0.1:1337']) + + # Allow different prefixes to traverse the tunnel + allowed_ips = ['0.0.0.0/0'] + for ip in allowed_ips: + self.session.set(base_path + [intf, 'peer', peer, 'allowed-ips', ip]) + + self.session.set(base_path + [intf, 'peer', peer, 'preshared-key', psk]) + self.session.set(base_path + [intf, 'peer', peer, 'pubkey', pubkey]) + self.session.commit() + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From b242ca610ddc5f30470ba5d2e43af625574806e5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 29 Feb 2020 10:09:01 +0100 Subject: interfaces: wireguard: make test script executable --- scripts/cli/test_interfaces_wireguard.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/cli/test_interfaces_wireguard.py diff --git a/scripts/cli/test_interfaces_wireguard.py b/scripts/cli/test_interfaces_wireguard.py old mode 100644 new mode 100755 -- cgit v1.2.3 From e978f00f4749866a38c349528360a063b47d78ff Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 5 Mar 2020 22:08:49 +0100 Subject: vrf: initial unittest --- scripts/cli/test_vrf.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100755 scripts/cli/test_vrf.py diff --git a/scripts/cli/test_vrf.py b/scripts/cli/test_vrf.py new file mode 100755 index 000000000..efa095b30 --- /dev/null +++ b/scripts/cli/test_vrf.py @@ -0,0 +1,52 @@ +#!/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 . + +import os +import unittest + +from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.util import read_file + +class VRFTest(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + self._vrfs = ['red', 'green', 'blue'] + + def tearDown(self): + # delete all VRFs + self.session.delete(['vrf']) + self.session.commit() + del self.session + + def test_table_id(self): + table = 1000 + for vrf in self._vrfs: + base = ['vrf', 'name', vrf] + description = "VyOS-VRF-" + vrf + self.session.set(base + ['description', description]) + + # check validate() - a table ID is mandatory + with self.assertRaises(ConfigSessionError): + self.session.commit() + + self.session.set(base + ['table', str(table)]) + table += 1 + + # commit changes + self.session.commit() + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From e5231d59c636dd62d916a533fa86d865a1a1e54d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 8 Mar 2020 18:33:29 +0100 Subject: wireless: add basic unittest --- scripts/cli/test_interfaces_wireless.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100755 scripts/cli/test_interfaces_wireless.py diff --git a/scripts/cli/test_interfaces_wireless.py b/scripts/cli/test_interfaces_wireless.py new file mode 100755 index 000000000..9fdc2d20a --- /dev/null +++ b/scripts/cli/test_interfaces_wireless.py @@ -0,0 +1,31 @@ +#!/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 . + +import os +import unittest + +from base_interfaces_test import BasicInterfaceTest + +class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + + self._base_path = ['interfaces', 'wireless'] + self._interfaces = ['wlan0', 'wlan1'] + +if __name__ == '__main__': + os.system("modprobe mac80211_hwsim") + unittest.main() -- cgit v1.2.3 From 9957f76f3aa7726b8b2b86c3e76a763da0b8d34f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 21 Mar 2020 18:25:29 +0100 Subject: Jenkins: T1870: support GitHub PullRequest builds --- Jenkinsfile | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ed98477f2..7a79b0f43 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -36,7 +36,7 @@ def isCustomBuild() { def gitURI = 'git@github.com:vyos/' + getGitRepoName() def httpURI = 'https://github.com/vyos/' + getGitRepoName() - return ! ((getGitRepoURL() == gitURI) || (getGitRepoURL() == httpURI)) + return !((getGitRepoURL() == gitURI) || (getGitRepoURL() == httpURI)) || env.CHANGE_ID } def setDescription() { @@ -74,7 +74,13 @@ node('Docker') { script { // create container name on demand def branchName = getGitBranchName() - if (branchName == "master") { + // Adjust PR target branch name so we can re-map it to the proper + // Docker image. CHANGE_ID is set only for pull requests, so it is + // safe to access the pullRequest global variable + if (env.CHANGE_ID) { + branchName = "${env.CHANGE_TARGET}".toLowerCase() + } + if (branchName.equals("master")) { branchName = "current" } env.DOCKER_IMAGE = "vyos/vyos-build:" + branchName @@ -92,7 +98,6 @@ pipeline { } options { disableConcurrentBuilds() - skipDefaultCheckout() timeout(time: 30, unit: 'MINUTES') timestamps() } @@ -101,8 +106,7 @@ pipeline { steps { script { dir('build') { - git branch: getGitBranchName(), - url: getGitRepoURL() + checkout scm } } } -- cgit v1.2.3 From 5fe2877e539d62294d29e485d1a613b70685c39f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 22 Mar 2020 12:11:49 +0100 Subject: module: add Intel iavf driver to load list --- scripts/system/test_module_load.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/test_module_load.py b/scripts/system/test_module_load.py index 598ea9882..59c3e0b6a 100755 --- a/scripts/system/test_module_load.py +++ b/scripts/system/test_module_load.py @@ -18,7 +18,7 @@ import os import unittest modules = { - "intel": ["e1000", "e1000e", "igb", "ixgb", "ixgbe", "ixgbevf", "i40e", "i40evf"], + "intel": ["e1000", "e1000e", "igb", "ixgb", "ixgbe", "ixgbevf", "i40e", "i40evf", "iavf"], "accel_ppp": ["ipoe", "vlan_mon"], "misc": ["wireguard"] } -- cgit v1.2.3 From 40540d9c27d781bbb5730d7d83a93edd2380acd3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 22 Mar 2020 18:31:34 +0100 Subject: wireless: add mandatory physical-interface --- scripts/cli/test_interfaces_wireless.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/scripts/cli/test_interfaces_wireless.py b/scripts/cli/test_interfaces_wireless.py index 9fdc2d20a..f88533b94 100755 --- a/scripts/cli/test_interfaces_wireless.py +++ b/scripts/cli/test_interfaces_wireless.py @@ -24,7 +24,26 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): super().setUp() self._base_path = ['interfaces', 'wireless'] - self._interfaces = ['wlan0', 'wlan1'] + self._interfaces = ['wlan0'] + + + def test_add_description(self): + """ + A physical interface is mandatory thus we overwrite this function. + """ + for intf in self._interfaces: + self.session.set(self._base_path + [intf, 'physical-device', 'phy0']) + + super.test_add_description() + + def test_add_address(self): + """ + A physical interface is mandatory thus we overwrite this function. + """ + for intf in self._interfaces: + self.session.set(self._base_path + [intf, 'physical-device', 'phy0']) + + super.test_add_description() if __name__ == '__main__': os.system("modprobe mac80211_hwsim") -- cgit v1.2.3 From ff626be2b3378cdad5f1e22bde2f5ba552a18e5a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 22 Mar 2020 19:01:54 +0100 Subject: wireless: fix call to wrong base class method --- scripts/cli/test_interfaces_wireless.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/cli/test_interfaces_wireless.py b/scripts/cli/test_interfaces_wireless.py index f88533b94..2d26ed039 100755 --- a/scripts/cli/test_interfaces_wireless.py +++ b/scripts/cli/test_interfaces_wireless.py @@ -26,7 +26,6 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): self._base_path = ['interfaces', 'wireless'] self._interfaces = ['wlan0'] - def test_add_description(self): """ A physical interface is mandatory thus we overwrite this function. @@ -43,7 +42,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): for intf in self._interfaces: self.session.set(self._base_path + [intf, 'physical-device', 'phy0']) - super.test_add_description() + super.test_add_address() if __name__ == '__main__': os.system("modprobe mac80211_hwsim") -- cgit v1.2.3 From fc44f25ea39deff5953933e821fb9d47cd6c603a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 22 Mar 2020 20:03:21 +0100 Subject: wireless: temporary disable wireless test --- scripts/cli/test_interfaces_wireless.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 scripts/cli/test_interfaces_wireless.py diff --git a/scripts/cli/test_interfaces_wireless.py b/scripts/cli/test_interfaces_wireless.py old mode 100755 new mode 100644 -- cgit v1.2.3 From 442dd75b3635428824f1e0921846b0a2a7012b14 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sun, 22 Mar 2020 20:55:19 +0000 Subject: vxlan: add basic testing --- scripts/cli/base_interfaces_test.py | 14 ++++++++++++++ scripts/cli/test_interfaces_vxlan.py | 37 ++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 scripts/cli/test_interfaces_vxlan.py diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 11a7031ef..9fa0fb49e 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -22,12 +22,18 @@ from vyos.interfaces import list_interfaces_of_type class BasicInterfaceTest: class BaseTest(unittest.TestCase): + _test_mtu = False + _base_path = [] + _options = {} + _interfaces = [] + def setUp(self): self.session = ConfigSession(os.getpid()) self._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'] self._test_mtu = False + self._options = {} def tearDown(self): # we should not remove ethernet from the overall CLI @@ -48,6 +54,9 @@ class BasicInterfaceTest: for intf in self._interfaces: test_string='Description-Test-{}'.format(intf) self.session.set(self._base_path + [intf, 'description', test_string]) + for option in self._options.get(intf, []): + self.session.set(self._base_path + [intf] + option.split()) + self.session.commit() # Validate interface description @@ -66,6 +75,9 @@ class BasicInterfaceTest: for intf in self._interfaces: for addr in self._test_addr: self.session.set(self._base_path + [intf, 'address', addr]) + for option in self._options.get(intf, []): + self.session.set(self._base_path + [intf] + option.split()) + self.session.commit() # Validate address @@ -91,6 +103,8 @@ class BasicInterfaceTest: mtu = '1400' for intf in self._interfaces: self.session.set(self._base_path + [intf, 'mtu', mtu]) + for option in self._options.get(intf, []): + self.session.set(self._base_path + [intf] + option.split()) self.session.commit() diff --git a/scripts/cli/test_interfaces_vxlan.py b/scripts/cli/test_interfaces_vxlan.py new file mode 100644 index 000000000..dc5502838 --- /dev/null +++ b/scripts/cli/test_interfaces_vxlan.py @@ -0,0 +1,37 @@ +#!/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 . + +import os +import unittest + +from vyos.configsession import ConfigSession, ConfigSessionError +from base_interfaces_test import BasicInterfaceTest + + +class VXLANInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + + self._test_mtu = True + self._base_path = ['interfaces', 'vxlan'] + self._options = { + 'vxlan0': ['vni 10', 'remote 127.0.0.2'], + 'vxlan1': ['vni 20', 'group 239.1.1.1', 'link eth0'], + } + self._interfaces = list(self._options) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From a3704e5408b1caafd004ed7040d5fe82b3fb2acc Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Mon, 23 Mar 2020 12:03:29 +0000 Subject: geneve: add basic interface testing --- scripts/cli/test_interfaces_geneve.py | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 scripts/cli/test_interfaces_geneve.py diff --git a/scripts/cli/test_interfaces_geneve.py b/scripts/cli/test_interfaces_geneve.py new file mode 100644 index 000000000..0479240d7 --- /dev/null +++ b/scripts/cli/test_interfaces_geneve.py @@ -0,0 +1,38 @@ +#!/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 . + +import os +import unittest + +from vyos.configsession import ConfigSession, ConfigSessionError +from base_interfaces_test import BasicInterfaceTest + + +class GeneveInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + + self._test_mtu = True + self._base_path = ['interfaces', 'geneve'] + self._options = { + 'gnv0': ['vni 10', 'remote 127.0.1.1'], + 'gnv1': ['vni 20', 'remote 127.0.1.2'], + } + self._interfaces = list(self._options) + + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From c0b55338a9503a7db49690fca0ec028bb8683875 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Wed, 25 Mar 2020 09:34:58 +0000 Subject: update to use Interface.listing --- scripts/cli/base_interfaces_test.py | 2 +- scripts/cli/test_interfaces_bonding.py | 4 ++-- scripts/cli/test_interfaces_bridge.py | 4 ++-- scripts/cli/test_interfaces_ethernet.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 9fa0fb49e..1fa5472d5 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -18,7 +18,7 @@ import unittest from vyos.configsession import ConfigSession from netifaces import ifaddresses, AF_INET, AF_INET6 from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local -from vyos.interfaces import list_interfaces_of_type +from vyos.ifconfig import Interface class BasicInterfaceTest: class BaseTest(unittest.TestCase): diff --git a/scripts/cli/test_interfaces_bonding.py b/scripts/cli/test_interfaces_bonding.py index ecd12b957..36433ed62 100755 --- a/scripts/cli/test_interfaces_bonding.py +++ b/scripts/cli/test_interfaces_bonding.py @@ -18,7 +18,7 @@ import unittest from base_interfaces_test import BasicInterfaceTest -from vyos.interfaces import list_interfaces_of_type +from vyos.ifconfig import Interface from vyos.configsession import ConfigSessionError class BondingInterfaceTest(BasicInterfaceTest.BaseTest): @@ -33,7 +33,7 @@ class BondingInterfaceTest(BasicInterfaceTest.BaseTest): members = [] # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! - for tmp in list_interfaces_of_type("ethernet"): + for tmp in Interface.listing("ethernet"): if not '.' in tmp: members.append(tmp) diff --git a/scripts/cli/test_interfaces_bridge.py b/scripts/cli/test_interfaces_bridge.py index 100f6fdf2..af6b62724 100755 --- a/scripts/cli/test_interfaces_bridge.py +++ b/scripts/cli/test_interfaces_bridge.py @@ -17,7 +17,7 @@ import unittest from base_interfaces_test import BasicInterfaceTest -from vyos.interfaces import list_interfaces_of_type +from vyos.ifconfig import Interface class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): @@ -30,7 +30,7 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): members = [] # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! - for tmp in list_interfaces_of_type("ethernet"): + for tmp in Interface.listing("ethernet"): if not '.' in tmp: members.append(tmp) diff --git a/scripts/cli/test_interfaces_ethernet.py b/scripts/cli/test_interfaces_ethernet.py index a6c5e902c..ddc6ffb27 100755 --- a/scripts/cli/test_interfaces_ethernet.py +++ b/scripts/cli/test_interfaces_ethernet.py @@ -17,7 +17,7 @@ import unittest from base_interfaces_test import BasicInterfaceTest -from vyos.interfaces import list_interfaces_of_type +from vyos.ifconfig import Interface class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): @@ -29,7 +29,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! - for tmp in list_interfaces_of_type("ethernet"): + for tmp in Interface.listing("ethernet"): if not '.' in tmp: self._interfaces.append(tmp) -- cgit v1.2.3 From c26648b99c9a5e7019294dbd2e0a6a3c23f9f85a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Apr 2020 20:36:39 +0200 Subject: pppoe: T2219: default route now implemented via ip-up script --- scripts/cli/test_interfaces_pppoe.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/cli/test_interfaces_pppoe.py b/scripts/cli/test_interfaces_pppoe.py index 714e3e739..9a9a1512c 100755 --- a/scripts/cli/test_interfaces_pppoe.py +++ b/scripts/cli/test_interfaces_pppoe.py @@ -74,13 +74,11 @@ class PPPoEInterfaceTest(unittest.TestCase): cur_mtu = get_config_value(interface, 'mtu')[1] cur_user = get_config_value(interface, 'user')[1].replace('"', '') cur_password = get_config_value(interface, 'password')[1].replace('"', '') - cur_default_route = get_config_value(interface, 'defaultroute')[0] cur_ifname = get_config_value(interface, 'ifname')[1] self.assertTrue(cur_mtu == '1400') self.assertTrue(cur_user == user) self.assertTrue(cur_password == password) - self.assertTrue(cur_default_route == 'defaultroute') self.assertTrue(cur_ifname == interface) # Check if ppp process is running in the interface in question -- cgit v1.2.3 From dc36efc4d70e861d9adcfdcb2865706ac7f32e0f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 5 Apr 2020 15:02:05 +0200 Subject: wifi: add precondition of physical-interface for all tests --- scripts/cli/test_interfaces_wireless.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) mode change 100644 => 100755 scripts/cli/test_interfaces_wireless.py diff --git a/scripts/cli/test_interfaces_wireless.py b/scripts/cli/test_interfaces_wireless.py old mode 100644 new mode 100755 index 2d26ed039..cefe0e323 --- a/scripts/cli/test_interfaces_wireless.py +++ b/scripts/cli/test_interfaces_wireless.py @@ -24,7 +24,13 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): super().setUp() self._base_path = ['interfaces', 'wireless'] - self._interfaces = ['wlan0'] + self._interfaces = ['wlan0', 'wlan1', 'wlan10', 'wlan11'] + self._options = { + 'wlan0': ['physical-device phy0'], + 'wlan1': ['physical-device phy0'], + 'wlan10': ['physical-device phy1'], + 'wlan11': ['physical-device phy1'], + } def test_add_description(self): """ -- cgit v1.2.3 From b2da290cdce8d5a9cd1fd964f4583b1a2c34c9f2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 5 Apr 2020 15:03:52 +0200 Subject: wifi: remove superfluous inherited tests --- scripts/cli/test_interfaces_wireless.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/scripts/cli/test_interfaces_wireless.py b/scripts/cli/test_interfaces_wireless.py index cefe0e323..8693f77e2 100755 --- a/scripts/cli/test_interfaces_wireless.py +++ b/scripts/cli/test_interfaces_wireless.py @@ -32,23 +32,6 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): 'wlan11': ['physical-device phy1'], } - def test_add_description(self): - """ - A physical interface is mandatory thus we overwrite this function. - """ - for intf in self._interfaces: - self.session.set(self._base_path + [intf, 'physical-device', 'phy0']) - - super.test_add_description() - - def test_add_address(self): - """ - A physical interface is mandatory thus we overwrite this function. - """ - for intf in self._interfaces: - self.session.set(self._base_path + [intf, 'physical-device', 'phy0']) - - super.test_add_address() if __name__ == '__main__': os.system("modprobe mac80211_hwsim") -- cgit v1.2.3 From 6ad15ea8bd767d5c6655d3255d0d8532c507c305 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 5 Apr 2020 16:10:50 +0200 Subject: wifi: add basic interface test --- scripts/cli/test_interfaces_wireless.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/scripts/cli/test_interfaces_wireless.py b/scripts/cli/test_interfaces_wireless.py index 8693f77e2..6edff78b3 100755 --- a/scripts/cli/test_interfaces_wireless.py +++ b/scripts/cli/test_interfaces_wireless.py @@ -24,13 +24,29 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): super().setUp() self._base_path = ['interfaces', 'wireless'] - self._interfaces = ['wlan0', 'wlan1', 'wlan10', 'wlan11'] self._options = { - 'wlan0': ['physical-device phy0'], - 'wlan1': ['physical-device phy0'], - 'wlan10': ['physical-device phy1'], - 'wlan11': ['physical-device phy1'], + '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'], } + self._interfaces = list(self._options) + self.session.set(['system', 'wifi-regulatory-domain', 'SE']) + + def test_wifi_client(self): + """ test creation of a wireless station """ + for intf in self._interfaces: + # prepare interfaces + for option in self._options.get(intf, []): + self.session.set(self._base_path + [intf] + option.split()) + + # commit changes + self.session.commit() + if __name__ == '__main__': -- cgit v1.2.3 From d2b7359a7bd953e03b5dcdd440e3fa7c633fdfa4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 5 Apr 2020 16:42:09 +0200 Subject: wireguard: T2206: split endpoint into proper address and port nodes --- scripts/cli/test_interfaces_wireguard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/cli/test_interfaces_wireguard.py b/scripts/cli/test_interfaces_wireguard.py index ff1ff5d7e..159be02f1 100755 --- a/scripts/cli/test_interfaces_wireguard.py +++ b/scripts/cli/test_interfaces_wireguard.py @@ -50,7 +50,8 @@ class WireGuardInterfaceTest(unittest.TestCase): for addr in self._test_addr: self.session.set(base_path + [intf, 'address', addr]) - self.session.set(base_path + [intf, 'peer', peer, 'endpoint', '127.0.0.1:1337']) + self.session.set(base_path + [intf, 'peer', peer, 'address', '127.0.0.1']) + self.session.set(base_path + [intf, 'peer', peer, 'port', '1337']) # Allow different prefixes to traverse the tunnel allowed_ips = ['0.0.0.0/0'] -- cgit v1.2.3 From 73a9ff255de4be4e8a9d9cb258a5f5c9d8311098 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 11 Apr 2020 20:15:41 +0100 Subject: interfaces: T2238: update code to match new API --- scripts/cli/test_interfaces_bonding.py | 4 ++-- scripts/cli/test_interfaces_bridge.py | 4 ++-- scripts/cli/test_interfaces_ethernet.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/cli/test_interfaces_bonding.py b/scripts/cli/test_interfaces_bonding.py index 36433ed62..bfadc4a9d 100755 --- a/scripts/cli/test_interfaces_bonding.py +++ b/scripts/cli/test_interfaces_bonding.py @@ -18,7 +18,7 @@ import unittest from base_interfaces_test import BasicInterfaceTest -from vyos.ifconfig import Interface +from vyos.ifconfig import Section from vyos.configsession import ConfigSessionError class BondingInterfaceTest(BasicInterfaceTest.BaseTest): @@ -33,7 +33,7 @@ class BondingInterfaceTest(BasicInterfaceTest.BaseTest): members = [] # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! - for tmp in Interface.listing("ethernet"): + for tmp in Section.interfaces("ethernet"): if not '.' in tmp: members.append(tmp) diff --git a/scripts/cli/test_interfaces_bridge.py b/scripts/cli/test_interfaces_bridge.py index af6b62724..03c78c210 100755 --- a/scripts/cli/test_interfaces_bridge.py +++ b/scripts/cli/test_interfaces_bridge.py @@ -17,7 +17,7 @@ import unittest from base_interfaces_test import BasicInterfaceTest -from vyos.ifconfig import Interface +from vyos.ifconfig import Section class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): @@ -30,7 +30,7 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): members = [] # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! - for tmp in Interface.listing("ethernet"): + for tmp in Section.interfaces("ethernet"): if not '.' in tmp: members.append(tmp) diff --git a/scripts/cli/test_interfaces_ethernet.py b/scripts/cli/test_interfaces_ethernet.py index ddc6ffb27..373c81680 100755 --- a/scripts/cli/test_interfaces_ethernet.py +++ b/scripts/cli/test_interfaces_ethernet.py @@ -17,7 +17,7 @@ import unittest from base_interfaces_test import BasicInterfaceTest -from vyos.ifconfig import Interface +from vyos.ifconfig import Section class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): @@ -29,7 +29,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! - for tmp in Interface.listing("ethernet"): + for tmp in Section.interfaces("ethernet"): if not '.' in tmp: self._interfaces.append(tmp) -- cgit v1.2.3 From c40b538e329da9ce49965ae442ff66ee2a6a7c22 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 13 Apr 2020 21:49:36 +0200 Subject: ddns: adjust to new config path in /run --- scripts/cli/test_service_dns_dynamic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cli/test_service_dns_dynamic.py b/scripts/cli/test_service_dns_dynamic.py index e6205cf8b..be52360ed 100755 --- a/scripts/cli/test_service_dns_dynamic.py +++ b/scripts/cli/test_service_dns_dynamic.py @@ -23,7 +23,7 @@ from psutil import process_iter from vyos.configsession import ConfigSession, ConfigSessionError from vyos.util import read_file -DDCLIENT_CONF = '/etc/ddclient/ddclient.conf' +DDCLIENT_CONF = '/run/ddclient/ddclient.conf' base_path = ['service', 'dns', 'dynamic'] def get_config_value(key): -- cgit v1.2.3 From f0b453c6709b2830d04981c09ec4075b978a5427 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 21 May 2020 20:07:48 +0200 Subject: pppoe: do not check manually if pppoe process runs ... this should rather be done by checking "systemctl status ppp@pppoe0" --- scripts/cli/test_interfaces_pppoe.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/scripts/cli/test_interfaces_pppoe.py b/scripts/cli/test_interfaces_pppoe.py index 9a9a1512c..0e19f514a 100755 --- a/scripts/cli/test_interfaces_pppoe.py +++ b/scripts/cli/test_interfaces_pppoe.py @@ -81,14 +81,5 @@ class PPPoEInterfaceTest(unittest.TestCase): self.assertTrue(cur_password == password) self.assertTrue(cur_ifname == interface) - # Check if ppp process is running in the interface in question - running = False - for p in process_iter(): - if "pppd" in p.name(): - if interface in p.cmdline(): - running = True - - self.assertTrue(running) - if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 8417f55135979be657ae0a3d3e010104c73fc400 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 22 May 2020 12:30:47 +0200 Subject: Revert "pppoe: do not check manually if pppoe process runs" This reverts commit f0b453c6709b2830d04981c09ec4075b978a5427. Test was right - pppd did not run (it was a bug T2488) --- scripts/cli/test_interfaces_pppoe.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/cli/test_interfaces_pppoe.py b/scripts/cli/test_interfaces_pppoe.py index 0e19f514a..9a9a1512c 100755 --- a/scripts/cli/test_interfaces_pppoe.py +++ b/scripts/cli/test_interfaces_pppoe.py @@ -81,5 +81,14 @@ class PPPoEInterfaceTest(unittest.TestCase): self.assertTrue(cur_password == password) self.assertTrue(cur_ifname == interface) + # Check if ppp process is running in the interface in question + running = False + for p in process_iter(): + if "pppd" in p.name(): + if interface in p.cmdline(): + running = True + + self.assertTrue(running) + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 160cb06775d51da89fb10b083850ddf42c96b11b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 12 Jun 2020 16:03:56 +0200 Subject: interfaces: base: speedup address test Instead of calling commit() on every interface we add an address, rather configure all addresses first and then commit() in a single shot! --- scripts/cli/base_interfaces_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 1fa5472d5..3f8a74254 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -78,7 +78,7 @@ class BasicInterfaceTest: for option in self._options.get(intf, []): self.session.set(self._base_path + [intf] + option.split()) - self.session.commit() + self.session.commit() # Validate address for intf in self._interfaces: -- cgit v1.2.3 From b38a42d9d4ab302b44d48844fae49bb0a0817d04 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 19 Jun 2020 21:29:04 +0200 Subject: peth/macvlan: T2589: add basic smoketest --- scripts/cli/test_interfaces_pseudo_ethernet.py | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100755 scripts/cli/test_interfaces_pseudo_ethernet.py diff --git a/scripts/cli/test_interfaces_pseudo_ethernet.py b/scripts/cli/test_interfaces_pseudo_ethernet.py new file mode 100755 index 000000000..e75fb0250 --- /dev/null +++ b/scripts/cli/test_interfaces_pseudo_ethernet.py @@ -0,0 +1,33 @@ +#!/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 . + +import unittest + +from base_interfaces_test import BasicInterfaceTest + +class PEthInterfaceTest(BasicInterfaceTest.BaseTest): + + def setUp(self): + super().setUp() + self._base_path = ['interfaces', 'pseudo-ethernet'] + self._options = { + 'peth0': ['source-interface eth0', 'ip arp-cache-timeout 10'], + 'peth1': ['source-interface eth0', 'ip arp-cache-timeout 10'], + } + self._interfaces = list(self._options) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From 353fd532ca97711f6c7a22a5c394e9ef7d03f910 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 22 Jun 2020 22:18:01 +0200 Subject: Jenkins: T2625: migrate to build library --- Jenkinsfile | 178 +++--------------------------------------------------------- 1 file changed, 7 insertions(+), 171 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7a79b0f43..7a760b40b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,4 +1,4 @@ -// Copyright (C) 2019 VyOS maintainers and contributors +// Copyright (C) 2020 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 @@ -15,174 +15,10 @@ @NonCPS -def getGitBranchName() { - def branch = scm.branches[0].name - return branch.split('/')[-1] -} - -def getGitRepoURL() { - return scm.userRemoteConfigs[0].url -} - -def getGitRepoName() { - return getGitRepoURL().split('/').last() -} - -// Returns true if this is a custom build launched on any project fork. -// Returns false if this is build from git@github.com:vyos/. -// can be e.g. vyos-1x.git or vyatta-op.git -def isCustomBuild() { - // GitHub organisation base URL - def gitURI = 'git@github.com:vyos/' + getGitRepoName() - def httpURI = 'https://github.com/vyos/' + getGitRepoName() - - return !((getGitRepoURL() == gitURI) || (getGitRepoURL() == httpURI)) || env.CHANGE_ID -} - -def setDescription() { - def item = Jenkins.instance.getItemByFullName(env.JOB_NAME) - - // build up the main description text - def description = "" - description += "

VyOS individual package build: " + getGitRepoName().replace('.git', '') + "

" - - if (isCustomBuild()) { - description += "

" - description += "Build not started from official Git repository!
" - description += "
" - description += "Repository: " + getGitRepoURL() + "
" - description += "Branch: " + getGitBranchName() + "
" - description += "

" - } else { - description += "Sources taken from Git branch: " + getGitBranchName() + "
" - } - - item.setDescription(description) - item.save() -} - -/* Only keep the 10 most recent builds. */ -def projectProperties = [ - [$class: 'BuildDiscarderProperty',strategy: [$class: 'LogRotator', numToKeepStr: '10']], -] - -properties(projectProperties) -setDescription() - -node('Docker') { - stage('Define Agent') { - script { - // create container name on demand - def branchName = getGitBranchName() - // Adjust PR target branch name so we can re-map it to the proper - // Docker image. CHANGE_ID is set only for pull requests, so it is - // safe to access the pullRequest global variable - if (env.CHANGE_ID) { - branchName = "${env.CHANGE_TARGET}".toLowerCase() - } - if (branchName.equals("master")) { - branchName = "current" - } - env.DOCKER_IMAGE = "vyos/vyos-build:" + branchName - } - } -} - -pipeline { - agent { - docker { - args "--sysctl net.ipv6.conf.lo.disable_ipv6=0 -e GOSU_UID=1006 -e GOSU_GID=1006" - image "${env.DOCKER_IMAGE}" - alwaysPull true - } - } - options { - disableConcurrentBuilds() - timeout(time: 30, unit: 'MINUTES') - timestamps() - } - stages { - stage('Fetch') { - steps { - script { - dir('build') { - checkout scm - } - } - } - } - stage('Build') { - steps { - script { - dir('build') { - def commitId = sh(returnStdout: true, script: 'git rev-parse --short=11 HEAD').trim() - currentBuild.description = sprintf('Git SHA1: %s', commitId[-11..-1]) - - sh 'dpkg-buildpackage -b -us -uc -tc' - } - } - } - } - } - post { - cleanup { - deleteDir() - } - success { - script { - // archive *.deb artifact on custom builds, deploy to repo otherwise - if ( isCustomBuild()) { - archiveArtifacts artifacts: '*.deb', fingerprint: true - } else { - // publish build result, using SSH-dev.packages.vyos.net Jenkins Credentials - sshagent(['SSH-dev.packages.vyos.net']) { - // build up some fancy groovy variables so we do not need to write/copy - // every option over and over again! - def RELEASE = getGitBranchName() - if (getGitBranchName() == "master") { - RELEASE = 'current' - } - - def VYOS_REPO_PATH = '/home/sentrium/web/dev.packages.vyos.net/public_html/repositories/' + RELEASE + '/' - if (getGitBranchName() == "crux") - VYOS_REPO_PATH += 'vyos/' - - def SSH_OPTS = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=ERROR' - def SSH_REMOTE = 'khagen@10.217.48.113' - - echo "Uploading package(s) and updating package(s) in the repository ..." - - files = findFiles(glob: '*.deb') - files.each { PACKAGE -> - def ARCH = sh(returnStdout: true, script: "dpkg-deb -f ${PACKAGE} Architecture").trim() - def SUBSTRING = sh(returnStdout: true, script: "dpkg-deb -f ${PACKAGE} Package").trim() - def SSH_DIR = '~/VyOS/' + RELEASE + '/' + ARCH - def ARCH_OPT = '' - if (ARCH != 'all') - ARCH_OPT = '-A ' + ARCH - - // No need to explicitly check the return code. The pipeline - // will fail if sh returns a non 0 exit code - sh """ - ssh ${SSH_OPTS} ${SSH_REMOTE} -t "bash --login -c 'mkdir -p ${SSH_DIR}'" - """ - sh """ - scp ${SSH_OPTS} ${PACKAGE} ${SSH_REMOTE}:${SSH_DIR}/ - """ - sh """ - ssh ${SSH_OPTS} ${SSH_REMOTE} -t "uncron-add 'reprepro -v -b ${VYOS_REPO_PATH} ${ARCH_OPT} remove ${RELEASE} ${SUBSTRING}'" - """ - sh """ - ssh ${SSH_OPTS} ${SSH_REMOTE} -t "uncron-add 'reprepro -v -b ${VYOS_REPO_PATH} deleteunreferenced'" - """ - sh """ - ssh ${SSH_OPTS} ${SSH_REMOTE} -t "uncron-add 'reprepro -v -b ${VYOS_REPO_PATH} ${ARCH_OPT} includedeb ${RELEASE} ${SSH_DIR}/${PACKAGE}'" - """ - } - } - } - } - } - } -} +// 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/c-po/vyos-build +buildPackage() -- cgit v1.2.3 From 95f08f491b96861d2230afdcd6f4428af7dd05d5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 23 Jun 2020 08:13:18 +0200 Subject: macsec: initial interface test --- scripts/cli/test_interfaces_macsec.py | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100755 scripts/cli/test_interfaces_macsec.py diff --git a/scripts/cli/test_interfaces_macsec.py b/scripts/cli/test_interfaces_macsec.py new file mode 100755 index 000000000..1ba9f5c27 --- /dev/null +++ b/scripts/cli/test_interfaces_macsec.py @@ -0,0 +1,43 @@ +#!/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 . + +import unittest + +from vyos.ifconfig import Section +from base_interfaces_test import BasicInterfaceTest + +class MACsecInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + self._base_path = ['interfaces', 'macsec'] + self._options = { + 'macsec0': ['source-interface eth0', + 'security cipher gcm-aes-128', + 'security encrypt', + 'security mka cak 232e44b7fda6f8e2d88a07bf78a7aff4', + 'security mka ckn 40916f4b23e3d548ad27eedd2d10c6f98c2d21684699647d63d41b500dfe8836', + 'security replay-window 128'] + } + + # if we have a physical eth1 interface, add a second macsec instance + if 'eth1' in Section.interfaces("ethernet"): + macsec = { 'macsec1': ['source-interface eth1', 'security cipher gcm-aes-128'] } + self._options.update(macsec) + + self._interfaces = list(self._options) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From 23257105b68cb26fe16638928ab490fe3ea99164 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 23 Jun 2020 08:13:24 +0200 Subject: Debian: cleanup runtime dependencies Depends field of package vyos-smoketest: substitution variable ${shlibs:Depends} used, but is not defined --- debian/control | 1 - 1 file changed, 1 deletion(-) diff --git a/debian/control b/debian/control index 97f8b27ea..ada46a4c1 100644 --- a/debian/control +++ b/debian/control @@ -16,7 +16,6 @@ Package: vyos-smoketest Architecture: all Depends: python3, ${python3:Depends}, - ${shlibs:Depends}, ${misc:Depends}, vyos-1x Description: VyOS build sanity checking toolkit -- cgit v1.2.3 From 74c1b3a202a55f4d528f34caf842ad1383b04313 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 23 Jun 2020 08:14:47 +0200 Subject: geneve: enable test --- scripts/cli/test_interfaces_geneve.py | 1 - 1 file changed, 1 deletion(-) mode change 100644 => 100755 scripts/cli/test_interfaces_geneve.py diff --git a/scripts/cli/test_interfaces_geneve.py b/scripts/cli/test_interfaces_geneve.py old mode 100644 new mode 100755 index 0479240d7..f84a55f86 --- a/scripts/cli/test_interfaces_geneve.py +++ b/scripts/cli/test_interfaces_geneve.py @@ -25,7 +25,6 @@ class GeneveInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): super().setUp() - self._test_mtu = True self._base_path = ['interfaces', 'geneve'] self._options = { 'gnv0': ['vni 10', 'remote 127.0.1.1'], -- cgit v1.2.3 From 82024ebc21ff8516620f2fc8ee47e62f83082e3a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 23 Jun 2020 08:14:50 +0200 Subject: vxlan: adjust to new CLI source-interface node --- scripts/cli/test_interfaces_vxlan.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/cli/test_interfaces_vxlan.py b/scripts/cli/test_interfaces_vxlan.py index dc5502838..2628e0285 100644 --- a/scripts/cli/test_interfaces_vxlan.py +++ b/scripts/cli/test_interfaces_vxlan.py @@ -14,13 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os import unittest from vyos.configsession import ConfigSession, ConfigSessionError from base_interfaces_test import BasicInterfaceTest - class VXLANInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): super().setUp() @@ -29,7 +27,7 @@ class VXLANInterfaceTest(BasicInterfaceTest.BaseTest): self._base_path = ['interfaces', 'vxlan'] self._options = { 'vxlan0': ['vni 10', 'remote 127.0.0.2'], - 'vxlan1': ['vni 20', 'group 239.1.1.1', 'link eth0'], + 'vxlan1': ['vni 20', 'group 239.1.1.1', 'source-interface eth0'], } self._interfaces = list(self._options) -- cgit v1.2.3 From 3d329a77b30b48f51fd104c302c2f2dce0eb4cb2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 23 Jun 2020 08:15:01 +0200 Subject: vxlan: enable test --- scripts/cli/test_interfaces_vxlan.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/cli/test_interfaces_vxlan.py diff --git a/scripts/cli/test_interfaces_vxlan.py b/scripts/cli/test_interfaces_vxlan.py old mode 100644 new mode 100755 -- cgit v1.2.3 From 1dd18ea39de4184a65527ee0255802f3a6fe3953 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 23 Jun 2020 18:51:56 +0200 Subject: ssh: extend tests --- scripts/cli/test_service_ssh.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/cli/test_service_ssh.py b/scripts/cli/test_service_ssh.py index f9f0de3db..9127d77ba 100755 --- a/scripts/cli/test_service_ssh.py +++ b/scripts/cli/test_service_ssh.py @@ -46,6 +46,9 @@ class TestServiceSSH(unittest.TestCase): self.session.set(base_path + ['port', '2222']) self.session.set(base_path + ['disable-host-validation']) self.session.set(base_path + ['disable-password-authentication']) + self.session.set(base_path + ['loglevel', 'VERBOSE']) + self.session.set(base_path + ['listen-address', '127.0.0.1']) + self.session.set(base_path + ['client-keepalive-interval', '100']) # commit changes self.session.commit() @@ -62,6 +65,18 @@ class TestServiceSSH(unittest.TestCase): pwd = get_config_value('PasswordAuthentication') self.assertTrue("no" in pwd) + # Check loglevel + loglevel = get_config_value('LogLevel') + self.assertTrue("VERBOSE" in loglevel) + + # Check listen address + address = get_config_value('ListenAddress') + self.assertTrue("127.0.0.1" in address) + + # Check keepalive + keepalive = get_config_value('ClientAliveInterval') + self.assertTrue("100" in keepalive) + # Check for running process self.assertTrue("sshd" in (p.name() for p in process_iter())) -- cgit v1.2.3 From abe6f677ec620c98af7b612569d0d431e05a0bf7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 24 Jun 2020 18:56:36 +0200 Subject: wireguard: extend tests with multiple allowed-ips --- scripts/cli/test_interfaces_wireguard.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/cli/test_interfaces_wireguard.py b/scripts/cli/test_interfaces_wireguard.py index 159be02f1..0c32a4696 100755 --- a/scripts/cli/test_interfaces_wireguard.py +++ b/scripts/cli/test_interfaces_wireguard.py @@ -22,7 +22,7 @@ from base_interfaces_test import BasicInterfaceTest # Generate WireGuard default keypair if not os.path.isdir('/config/auth/wireguard/default'): - os.system('/usr/libexec/vyos/op_mode/wireguard.py --genkey') + os.system('sudo /usr/libexec/vyos/op_mode/wireguard.py --genkey') base_path = ['interfaces', 'wireguard'] @@ -54,7 +54,7 @@ class WireGuardInterfaceTest(unittest.TestCase): self.session.set(base_path + [intf, 'peer', peer, 'port', '1337']) # Allow different prefixes to traverse the tunnel - allowed_ips = ['0.0.0.0/0'] + allowed_ips = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'] for ip in allowed_ips: self.session.set(base_path + [intf, 'peer', peer, 'allowed-ips', ip]) @@ -62,5 +62,7 @@ class WireGuardInterfaceTest(unittest.TestCase): self.session.set(base_path + [intf, 'peer', peer, 'pubkey', pubkey]) self.session.commit() + self.assertTrue(os.path.isdir(f'/sys/class/net/{intf}')) + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From d41708ee247ce5efc5dc06f2d233e8d620fc6e4a Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 07:09:10 +0100 Subject: http-api: T2494: always exit with non zero on failure systemd is setup with Restart=on-failure thereforer the service will only be restarted if the daemon died and reported an error. Previously any OsError would cause a exit(0) and therefore the API would not have been restarted. https://www.freedesktop.org/software/systemd/man/systemd.service.html --- src/services/vyos-http-api-server | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 4c41fa96d..b256add98 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -400,4 +400,4 @@ if __name__ == '__main__': serve(app, host=server_config["listen_address"], port=server_config["port"]) except OSError as e: - print(f"OSError {e}") + sys.exit(f"OSError {e}") -- cgit v1.2.3 From d9fbad6a6bbba292f31fe50e57af893062c7a584 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 07:33:47 +0100 Subject: systemd: T2494: use Type=notify with daemon Notify systemd via the notify API when the python daemon are ready to take connection https://github.com/torfsen/python-systemd-tutorial --- debian/control | 1 + src/services/vyos-hostsd | 1 + src/services/vyos-http-api-server | 2 ++ src/systemd/vyos-hostsd.service | 2 +- src/systemd/vyos-http-api.service | 2 +- 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/debian/control b/debian/control index 5e14340a8..aaf8fa1e7 100644 --- a/debian/control +++ b/debian/control @@ -36,6 +36,7 @@ Depends: python3, python3-xmltodict, python3-pyudev, python3-voluptuous, + python3-systemd, bsdmainutils, cron, etherwake, diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index 0079f7e5c..53ac5a770 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -592,6 +592,7 @@ if __name__ == '__main__': socket.bind(SOCKET_PATH) os.umask(o_mask) + systemd.daemon.notify('READY=1') while True: # Wait for next request from client msg_json = socket.recv().decode() diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index b256add98..38bf2f8ce 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -28,6 +28,7 @@ import vyos.config from flask import Flask, request from waitress import serve +import systemd.daemon from functools import wraps @@ -396,6 +397,7 @@ if __name__ == '__main__': signal.signal(signal.SIGTERM, sig_handler) + systemd.daemon.notify('READY=1') try: serve(app, host=server_config["listen_address"], port=server_config["port"]) diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service index b77335778..418601d1a 100644 --- a/src/systemd/vyos-hostsd.service +++ b/src/systemd/vyos-hostsd.service @@ -14,7 +14,7 @@ WorkingDirectory=/run/vyos-hostsd RuntimeDirectory=vyos-hostsd RuntimeDirectoryPreserve=yes ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-hostsd -Type=idle +Type=notify KillMode=process SyslogIdentifier=vyos-hostsd diff --git a/src/systemd/vyos-http-api.service b/src/systemd/vyos-http-api.service index 4fa68b4ff..636973be7 100644 --- a/src/systemd/vyos-http-api.service +++ b/src/systemd/vyos-http-api.service @@ -6,7 +6,7 @@ Requires=vyos-router.service [Service] ExecStartPre=/usr/libexec/vyos/init/vyos-config ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-http-api-server -Type=idle +Type=notify KillMode=process SyslogIdentifier=vyos-http-api -- cgit v1.2.3 From 8d50cb7a4044049b8287ce9b076fbc6b7bb1cfef Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 07:52:31 +0100 Subject: http-api: T2494: remove inaccurate systemd comment --- src/systemd/vyos-http-api.service | 1 - 1 file changed, 1 deletion(-) diff --git a/src/systemd/vyos-http-api.service b/src/systemd/vyos-http-api.service index 636973be7..e336ed18b 100644 --- a/src/systemd/vyos-http-api.service +++ b/src/systemd/vyos-http-api.service @@ -14,7 +14,6 @@ SyslogFacility=daemon Restart=on-failure -# Does't work but leave it here User=root Group=vyattacfg -- cgit v1.2.3 From 4d6f9a3f803d1f4a5735867ae3ea124deedbdae1 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 08:29:16 +0100 Subject: hostsd: T2494: systemd remove inaccurate systemd comment --- src/systemd/vyos-hostsd.service | 1 - 1 file changed, 1 deletion(-) diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service index 418601d1a..560e04052 100644 --- a/src/systemd/vyos-hostsd.service +++ b/src/systemd/vyos-hostsd.service @@ -22,7 +22,6 @@ SyslogFacility=daemon Restart=on-failure -# Does't work in Jessie but leave it here User=root Group=hostsd -- cgit v1.2.3 From 54b92aea418ddca07b9f699466411e58ddfc52c3 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 09:03:10 +0100 Subject: hostsd: T2494: vyos-hostsd is part of systemd vyos.target The install section determine if the package should be enabled. vyos-hostd should be install if vyos.target is enabled. --- src/systemd/vyos-hostsd.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service index 560e04052..d10a0a939 100644 --- a/src/systemd/vyos-hostsd.service +++ b/src/systemd/vyos-hostsd.service @@ -30,4 +30,4 @@ Group=hostsd # 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 +WantedBy=vyos.target -- cgit v1.2.3 From 1ecb1d76cd0b149d6381a143adc9907adee47607 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 09:05:43 +0100 Subject: http-api: T2494: remove "barrier" PreExec The PreExec is making sure that the vyos-config-status file exists and blocks until it does. This file is created on boot completion and I can see no reason why the http service has to wait for the end of boot to start. Any barrier to start should be done with systemd itself. --- src/systemd/vyos-http-api.service | 1 - 1 file changed, 1 deletion(-) diff --git a/src/systemd/vyos-http-api.service b/src/systemd/vyos-http-api.service index e336ed18b..a2920376e 100644 --- a/src/systemd/vyos-http-api.service +++ b/src/systemd/vyos-http-api.service @@ -4,7 +4,6 @@ After=auditd.service systemd-user-sessions.service time-sync.target vyos-router. Requires=vyos-router.service [Service] -ExecStartPre=/usr/libexec/vyos/init/vyos-config ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-http-api-server Type=notify KillMode=process -- cgit v1.2.3 From 8cd23ec0449b3755240b9aebe236fdb66937d7d6 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Sat, 23 May 2020 15:02:47 +0100 Subject: http-api: T2494: systemd http-api has no link getty.target WantedBy is about the service installation and is not related to the boot order, linking to vyos.target instead --- src/systemd/vyos-http-api.service | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/systemd/vyos-http-api.service b/src/systemd/vyos-http-api.service index a2920376e..74a97d193 100644 --- a/src/systemd/vyos-http-api.service +++ b/src/systemd/vyos-http-api.service @@ -17,6 +17,4 @@ User=root Group=vyattacfg [Install] -# Installing in a earlier target leaves ExecStartPre waiting -WantedBy=getty.target - +WantedBy=vyos.target -- cgit v1.2.3 From be8cda7f711a7a26c85b51976c299a6837750a63 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Mon, 25 May 2020 17:33:07 +0100 Subject: http-api: T2494: start before vyos-router vyos-router may/is requiring access to the service, make sure it starts before, if not is has no consequence to do so anyway. --- src/systemd/vyos-hostsd.service | 1 + 1 file changed, 1 insertion(+) diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service index d10a0a939..26e9c745e 100644 --- a/src/systemd/vyos-hostsd.service +++ b/src/systemd/vyos-hostsd.service @@ -1,5 +1,6 @@ [Unit] Description=VyOS DNS configuration keeper +Before=vyos-router.service # Without this option, lots of default dependencies are added, # among them network.target, which creates a dependency cycle -- cgit v1.2.3 From f717f3f3ddb2afca78cf0ff3aab8457242d5f3d0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 26 Jun 2020 09:43:55 +0200 Subject: ssh: add second test listening on multiple addresses and ports --- scripts/cli/test_service_ssh.py | 56 ++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/scripts/cli/test_service_ssh.py b/scripts/cli/test_service_ssh.py index 9127d77ba..072984ca8 100755 --- a/scripts/cli/test_service_ssh.py +++ b/scripts/cli/test_service_ssh.py @@ -28,8 +28,7 @@ base_path = ['service', 'ssh'] def get_config_value(key): tmp = read_file(SSHD_CONF) tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) - return tmp[0] - + return tmp class TestServiceSSH(unittest.TestCase): def setUp(self): @@ -39,46 +38,79 @@ class TestServiceSSH(unittest.TestCase): self.session.delete(base_path) def tearDown(self): + # delete testing SSH config + self.session.delete(base_path) + # restore "plain" SSH access + self.session.set(base_path) + + self.session.commit() del self.session - def test_ssh(self): + def test_ssh_single(self): """ Check if SSH service can be configured and runs """ - self.session.set(base_path + ['port', '2222']) + self.session.set(base_path + ['port', '1234']) self.session.set(base_path + ['disable-host-validation']) self.session.set(base_path + ['disable-password-authentication']) self.session.set(base_path + ['loglevel', 'VERBOSE']) - self.session.set(base_path + ['listen-address', '127.0.0.1']) self.session.set(base_path + ['client-keepalive-interval', '100']) + self.session.set(base_path + ['listen-address', '127.0.0.1']) # commit changes self.session.commit() # Check configured port - port = get_config_value('Port') - self.assertTrue("2222" in port) + port = get_config_value('Port')[0] + self.assertTrue("1234" in port) # Check DNS usage - dns = get_config_value('UseDNS') + dns = get_config_value('UseDNS')[0] self.assertTrue("no" in dns) # Check PasswordAuthentication - pwd = get_config_value('PasswordAuthentication') + pwd = get_config_value('PasswordAuthentication')[0] self.assertTrue("no" in pwd) # Check loglevel - loglevel = get_config_value('LogLevel') + loglevel = get_config_value('LogLevel')[0] self.assertTrue("VERBOSE" in loglevel) # Check listen address - address = get_config_value('ListenAddress') + address = get_config_value('ListenAddress')[0] self.assertTrue("127.0.0.1" in address) # Check keepalive - keepalive = get_config_value('ClientAliveInterval') + keepalive = get_config_value('ClientAliveInterval')[0] self.assertTrue("100" in keepalive) # Check for running process self.assertTrue("sshd" in (p.name() for p in process_iter())) + def test_ssh_multi(self): + """ Check if SSH service can be configured and runs with multiple + listen ports and listen-addresses """ + ports = ['22', '2222'] + for port in ports: + self.session.set(base_path + ['port', port]) + + addresses = ['127.0.0.1', '::1'] + for address in addresses: + self.session.set(base_path + ['listen-address', address]) + + # commit changes + self.session.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) + + # Check for running process + self.assertTrue("sshd" in (p.name() for p in process_iter())) + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From d6dc8874daa56e76a8d4ddccfe47224bcd73eb75 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 26 Jun 2020 15:23:38 +0200 Subject: interfaces: add basic test adding only one interface IP address ... this is required for proper testing the "weird" bahavior of T2636 and that the underlaying config parse fabric works as expected while migrating interface code. --- scripts/cli/base_interfaces_test.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 3f8a74254..4fef01236 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -66,7 +66,20 @@ class BasicInterfaceTest: tmp = f.read().rstrip() self.assertTrue(tmp, test_string) - def test_add_address(self): + def test_add_address_single(self): + """ + Check if a single address can be added to interface. + """ + addr = '192.0.2.0/31' + for intf in self._interfaces: + self.session.set(self._base_path + [intf, 'address', addr]) + + self.session.commit() + + for intf in self._interfaces: + self.assertTrue(is_intf_addr_assigned(intf, addr)) + + def test_add_address_multi(self): """ Check if IPv4/IPv6 addresses can be added to interface. """ -- cgit v1.2.3 From 078da2c8c48d29d327740344bfa7c0182fa10986 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 26 Jun 2020 16:58:32 +0200 Subject: loopback: ensure 127.0.0.1, ::1 is always assigned --- scripts/cli/test_interfaces_loopback.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/cli/test_interfaces_loopback.py b/scripts/cli/test_interfaces_loopback.py index 4462f64fe..ba428b5d3 100755 --- a/scripts/cli/test_interfaces_loopback.py +++ b/scripts/cli/test_interfaces_loopback.py @@ -17,12 +17,25 @@ import unittest from base_interfaces_test import BasicInterfaceTest +from vyos.validate import is_intf_addr_assigned class LoopbackInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): super().setUp() + # these addresses are never allowed to be removed from the system + self._loopback_addresses = ['127.0.0.1', '::1'] self._base_path = ['interfaces', 'loopback'] self._interfaces = ['lo'] + def test_add_address_single(self): + super().test_add_address_single() + for addr in self._loopback_addresses: + self.assertTrue(is_intf_addr_assigned('lo', addr)) + + def test_add_address_multi(self): + super().test_add_address_multi() + for addr in self._loopback_addresses: + self.assertTrue(is_intf_addr_assigned('lo', addr)) + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From dc5c01765683433a2932bb76ac870651e3ac8904 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 26 Jun 2020 18:07:18 +0200 Subject: interfaces: base: add missing interface options to single IP test --- scripts/cli/base_interfaces_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 4fef01236..53fe553bc 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -73,6 +73,8 @@ class BasicInterfaceTest: addr = '192.0.2.0/31' for intf in self._interfaces: self.session.set(self._base_path + [intf, 'address', addr]) + for option in self._options.get(intf, []): + self.session.set(self._base_path + [intf] + option.split()) self.session.commit() -- cgit v1.2.3 From 90d6d8f3a45d10eb86daee700b10463a3c0fd8d7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 26 Jun 2020 18:49:48 +0200 Subject: peth: extend basic test with IP options --- scripts/cli/test_interfaces_pseudo_ethernet.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/cli/test_interfaces_pseudo_ethernet.py b/scripts/cli/test_interfaces_pseudo_ethernet.py index e75fb0250..1f5de4f61 100755 --- a/scripts/cli/test_interfaces_pseudo_ethernet.py +++ b/scripts/cli/test_interfaces_pseudo_ethernet.py @@ -23,9 +23,14 @@ class PEthInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): super().setUp() self._base_path = ['interfaces', 'pseudo-ethernet'] + options = ['source-interface eth0', 'ip arp-cache-timeout 10', + 'ip disable-arp-filter', 'ip enable-arp-accept', + 'ip enable-arp-announce', 'ip enable-arp-ignore', + 'ip enable-proxy-arp', 'ip proxy-arp-pvlan'] + self._options = { - 'peth0': ['source-interface eth0', 'ip arp-cache-timeout 10'], - 'peth1': ['source-interface eth0', 'ip arp-cache-timeout 10'], + 'peth0': options, + 'peth1': options, } self._interfaces = list(self._options) -- cgit v1.2.3 From a83375fe1179f694c66314e1640e0a0ea64e3a9e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 27 Jun 2020 10:31:50 +0200 Subject: macsec: test verify() functions --- scripts/cli/test_interfaces_macsec.py | 70 ++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/scripts/cli/test_interfaces_macsec.py b/scripts/cli/test_interfaces_macsec.py index 1ba9f5c27..60b7037bb 100755 --- a/scripts/cli/test_interfaces_macsec.py +++ b/scripts/cli/test_interfaces_macsec.py @@ -14,10 +14,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import re import unittest +from psutil import process_iter from vyos.ifconfig import Section from base_interfaces_test import BasicInterfaceTest +from vyos.configsession import ConfigSessionError +from vyos.util import read_file + +def get_config_value(intf, key): + tmp = read_file(f'/run/wpa_supplicant/{intf}.conf') + tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp) + return tmp[0] class MACsecInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): @@ -25,11 +34,7 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest): self._base_path = ['interfaces', 'macsec'] self._options = { 'macsec0': ['source-interface eth0', - 'security cipher gcm-aes-128', - 'security encrypt', - 'security mka cak 232e44b7fda6f8e2d88a07bf78a7aff4', - 'security mka ckn 40916f4b23e3d548ad27eedd2d10c6f98c2d21684699647d63d41b500dfe8836', - 'security replay-window 128'] + 'security cipher gcm-aes-128'] } # if we have a physical eth1 interface, add a second macsec instance @@ -39,5 +44,60 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest): self._interfaces = list(self._options) + def test_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 """ + intf = 'macsec0' + src_intf = 'eth0' + mak_cak = '232e44b7fda6f8e2d88a07bf78a7aff4' + mak_ckn = '40916f4b23e3d548ad27eedd2d10c6f98c2d21684699647d63d41b500dfe8836' + mak_priority = '100' + replay_window = '64' + self.session.set(self._base_path + [intf, 'security', 'encrypt']) + + # check validate() - Cipher suite must be set for MACsec + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [intf, 'security', 'cipher', 'gcm-aes-128']) + + # check validate() - Physical source interface must be set for MACsec + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [intf, 'source-interface', src_intf]) + + # check validate() - MACsec security keys mandartory when encryption is enabled + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [intf, 'security', 'mka', 'cak', mak_cak]) + + # check validate() - MACsec security keys mandartory when encryption is enabled + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [intf, 'security', 'mka', 'ckn', mak_ckn]) + + self.session.set(self._base_path + [intf, 'security', 'mka', 'priority', mak_priority]) + self.session.set(self._base_path + [intf, 'security', 'replay-window', replay_window]) + self.session.commit() + + tmp = get_config_value(src_intf, 'macsec_integ_only') + self.assertTrue("0" in tmp) + + tmp = get_config_value(src_intf, 'mka_cak') + self.assertTrue(mak_cak in tmp) + + tmp = get_config_value(src_intf, 'mka_ckn') + self.assertTrue(mak_ckn in tmp) + + tmp = get_config_value(src_intf, 'mka_priority') + self.assertTrue(mak_priority in tmp) + + tmp = get_config_value(src_intf, 'macsec_replay_window') + self.assertTrue(replay_window in tmp) + + # Check for running process + self.assertTrue("wpa_supplicant" in (p.name() for p in process_iter())) + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 28a6cb6b87734a502218d6e7127124dea9353348 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 27 Jun 2020 13:50:00 +0200 Subject: wireless: always use sudo when calling modprobe --- scripts/cli/test_interfaces_wireless.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cli/test_interfaces_wireless.py b/scripts/cli/test_interfaces_wireless.py index 6edff78b3..5d04a1c44 100755 --- a/scripts/cli/test_interfaces_wireless.py +++ b/scripts/cli/test_interfaces_wireless.py @@ -50,5 +50,5 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): if __name__ == '__main__': - os.system("modprobe mac80211_hwsim") + os.system('sudo modprobe mac80211_hwsim') unittest.main() -- cgit v1.2.3 From 9c3832fc6f6e4f1b326bcd714b6db26fb3188c52 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 27 Jun 2020 14:19:09 +0200 Subject: macsec: test default value for mka priority --- scripts/cli/test_interfaces_macsec.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/cli/test_interfaces_macsec.py b/scripts/cli/test_interfaces_macsec.py index 60b7037bb..0f1b6486d 100755 --- a/scripts/cli/test_interfaces_macsec.py +++ b/scripts/cli/test_interfaces_macsec.py @@ -53,7 +53,6 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest): src_intf = 'eth0' mak_cak = '232e44b7fda6f8e2d88a07bf78a7aff4' mak_ckn = '40916f4b23e3d548ad27eedd2d10c6f98c2d21684699647d63d41b500dfe8836' - mak_priority = '100' replay_window = '64' self.session.set(self._base_path + [intf, 'security', 'encrypt']) @@ -77,7 +76,6 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest): self.session.commit() self.session.set(self._base_path + [intf, 'security', 'mka', 'ckn', mak_ckn]) - self.session.set(self._base_path + [intf, 'security', 'mka', 'priority', mak_priority]) self.session.set(self._base_path + [intf, 'security', 'replay-window', replay_window]) self.session.commit() @@ -90,8 +88,9 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest): tmp = get_config_value(src_intf, 'mka_ckn') self.assertTrue(mak_ckn in tmp) + # check that the default priority of 255 is programmed tmp = get_config_value(src_intf, 'mka_priority') - self.assertTrue(mak_priority in tmp) + self.assertTrue("255" in tmp) tmp = get_config_value(src_intf, 'macsec_replay_window') self.assertTrue(replay_window in tmp) -- cgit v1.2.3 From 4ad5cd436f1082ef5416c03a54730e710cc4441c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 27 Jun 2020 22:46:00 +0200 Subject: interfaces: wwan: initial testcase --- scripts/cli/test_interfaces_wirelessmodem.py | 84 ++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100755 scripts/cli/test_interfaces_wirelessmodem.py diff --git a/scripts/cli/test_interfaces_wirelessmodem.py b/scripts/cli/test_interfaces_wirelessmodem.py new file mode 100755 index 000000000..baacb8c66 --- /dev/null +++ b/scripts/cli/test_interfaces_wirelessmodem.py @@ -0,0 +1,84 @@ +#!/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 . + +import re +import os +import unittest + +from psutil import process_iter +from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.util import read_file + +config_file = '/etc/ppp/peers/{}' +base_path = ['interfaces', 'wirelessmodem'] + +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 [] + +class PPPoEInterfaceTest(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + self._interfaces = ['wlm0', 'wlm1'] + + def tearDown(self): + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_pppoe_1(self): + for interface in self._interfaces: + self.session.set(base_path + [interface, 'no-peer-dns']) + self.session.set(base_path + [interface, 'ondemand']) + + # check validate() - APN must be configure + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(base_path + [interface, 'apn', 'vyos.net']) + + # check validate() - device must be configure + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(base_path + [interface, 'device', 'ttyS0']) + + # commit changes + self.session.commit() + + # verify configuration file(s) + for interface in self._interfaces: + tmp = get_config_value(interface, 'ifname')[1] + self.assertTrue(interface in tmp) + + tmp = get_config_value(interface, 'demand')[0] + self.assertTrue('demand' in tmp) + + tmp = os.path.isfile(f'/etc/ppp/peers/chat.{interface}') + self.assertTrue(tmp) + + # Check if ppp process is running in the interface in question + running = False + for p in process_iter(): + if "pppd" in p.name(): + if interface in p.cmdline(): + running = True + + self.assertTrue(running) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From 256f5bc1bd1bf80e323d7d7db5ba3c9b7d3463a1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 28 Jun 2020 20:22:13 +0200 Subject: interfaces: pppoe: add DHCPv6-PD testcase --- scripts/cli/test_interfaces_pppoe.py | 92 +++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 16 deletions(-) diff --git a/scripts/cli/test_interfaces_pppoe.py b/scripts/cli/test_interfaces_pppoe.py index 9a9a1512c..0ad50a2bc 100755 --- a/scripts/cli/test_interfaces_pppoe.py +++ b/scripts/cli/test_interfaces_pppoe.py @@ -23,6 +23,7 @@ from vyos.configsession import ConfigSession, ConfigSessionError from vyos.util import read_file config_file = '/etc/ppp/peers/{}' +dhcp6c_config_file = '/run/dhcp6c/dhcp6c.{}.conf' base_path = ['interfaces', 'pppoe'] def get_config_value(interface, key): @@ -32,10 +33,20 @@ def get_config_value(interface, key): return list(line.split()) return [] +def get_dhcp6c_config_value(interface, key): + tmp = read_file(dhcp6c_config_file.format(interface)) + tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) + + out = [] + for item in tmp: + out.append(item.replace(';','')) + return out + class PPPoEInterfaceTest(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) - self._interfaces = ['pppoe0', 'pppoe1', 'pppoe2'] + self._interfaces = ['pppoe0', 'pppoe50'] + self._source_interface = 'eth0' def tearDown(self): self.session.delete(base_path) @@ -44,24 +55,21 @@ class PPPoEInterfaceTest(unittest.TestCase): def test_pppoe_1(self): """ Check if PPPoE dialer can be configured and runs """ - # ensure source-interface is available - source_interface = 'eth0' - self.session.set(['interfaces', 'ethernet', source_interface]) - for interface in self._interfaces: user = 'VyOS-user-' + interface passwd = 'VyOS-passwd-' + interface + mtu = '1400' self.session.set(base_path + [interface, 'authentication', 'user', user]) self.session.set(base_path + [interface, 'authentication', 'password', passwd]) self.session.set(base_path + [interface, 'default-route', 'auto']) - self.session.set(base_path + [interface, 'mtu', '1400']) + self.session.set(base_path + [interface, 'mtu', mtu]) self.session.set(base_path + [interface, 'no-peer-dns']) # check validate() - a source-interface is required with self.assertRaises(ConfigSessionError): self.session.commit() - self.session.set(base_path + [interface, 'source-interface', 'eth0']) + self.session.set(base_path + [interface, 'source-interface', self._source_interface]) # commit changes self.session.commit() @@ -71,15 +79,14 @@ class PPPoEInterfaceTest(unittest.TestCase): user = 'VyOS-user-' + interface password = 'VyOS-passwd-' + interface - cur_mtu = get_config_value(interface, 'mtu')[1] - cur_user = get_config_value(interface, 'user')[1].replace('"', '') - cur_password = get_config_value(interface, 'password')[1].replace('"', '') - cur_ifname = get_config_value(interface, 'ifname')[1] - - self.assertTrue(cur_mtu == '1400') - self.assertTrue(cur_user == user) - self.assertTrue(cur_password == password) - self.assertTrue(cur_ifname == interface) + tmp = get_config_value(interface, 'mtu')[1] + self.assertTrue(tmp in mtu) + tmp = get_config_value(interface, 'user')[1].replace('"', '') + self.assertTrue(tmp in user) + tmp = get_config_value(interface, 'password')[1].replace('"', '') + self.assertTrue(tmp in password) + tmp = get_config_value(interface, 'ifname')[1] + self.assertTrue(tmp in interface) # Check if ppp process is running in the interface in question running = False @@ -90,5 +97,58 @@ class PPPoEInterfaceTest(unittest.TestCase): self.assertTrue(running) + def test_pppoe_dhcpv6pd(self): + """ Check if PPPoE dialer can be configured and runs """ + address = '1' + sla_id = '0' + sla_len = '8' + for interface in self._interfaces: + self.session.set(base_path + [interface, 'authentication', 'user', 'vyos']) + self.session.set(base_path + [interface, 'authentication', 'password', 'vyos']) + self.session.set(base_path + [interface, 'default-route', 'none']) + self.session.set(base_path + [interface, 'no-peer-dns']) + self.session.set(base_path + [interface, 'source-interface', self._source_interface]) + + # prefix delegation stuff + dhcpv6_pd_base = base_path + [interface, 'dhcpv6-options', 'prefix-delegation'] + self.session.set(dhcpv6_pd_base + ['length', '56']) + self.session.set(dhcpv6_pd_base + ['interface', self._source_interface, 'address', address]) + self.session.set(dhcpv6_pd_base + ['interface', self._source_interface, 'sla-id', sla_id]) + self.session.set(dhcpv6_pd_base + ['interface', self._source_interface, 'sla-len', sla_len]) + + # commit changes + self.session.commit() + + # verify "normal" PPPoE value - 1492 is default MTU + tmp = get_config_value(interface, 'mtu')[1] + self.assertTrue(tmp in '1492') + tmp = get_config_value(interface, 'user')[1].replace('"', '') + self.assertTrue(tmp in 'vyos') + tmp = get_config_value(interface, 'password')[1].replace('"', '') + self.assertTrue(tmp in 'vyos') + + # verify DHCPv6 prefix delegation + # will return: ['delegation', '::/56 infinity;'] + tmp = get_dhcp6c_config_value(interface, 'prefix')[1].split()[0] # mind the whitespace + self.assertTrue(tmp in '::/56') + tmp = get_dhcp6c_config_value(interface, 'prefix-interface')[0].split()[0] + self.assertTrue(tmp in self._source_interface) + tmp = get_dhcp6c_config_value(interface, 'ifid')[0] + self.assertTrue(tmp in address) + tmp = get_dhcp6c_config_value(interface, 'sla-id')[0] + self.assertTrue(tmp in sla_id) + tmp = get_dhcp6c_config_value(interface, 'sla-len')[0] + self.assertTrue(tmp in sla_len) + + # Check if ppp process is running in the interface in question + running = False + for p in process_iter(): + if "pppd" in p.name(): + running = True + self.assertTrue(running) + + # We can not check if wide-dhcpv6 process is running as it is started + # after the PPP interface gets a link to the ISP + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 3db596c271c37a382fb703dbf8a268dc639dcd9c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 28 Jun 2020 20:22:39 +0200 Subject: interfaces: wwan: remove PPPoE markers --- scripts/cli/test_interfaces_wirelessmodem.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/cli/test_interfaces_wirelessmodem.py b/scripts/cli/test_interfaces_wirelessmodem.py index baacb8c66..40cd03b93 100755 --- a/scripts/cli/test_interfaces_wirelessmodem.py +++ b/scripts/cli/test_interfaces_wirelessmodem.py @@ -14,13 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import re import os import unittest from psutil import process_iter from vyos.configsession import ConfigSession, ConfigSessionError -from vyos.util import read_file config_file = '/etc/ppp/peers/{}' base_path = ['interfaces', 'wirelessmodem'] @@ -32,7 +30,7 @@ def get_config_value(interface, key): return list(line.split()) return [] -class PPPoEInterfaceTest(unittest.TestCase): +class WWANInterfaceTest(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) self._interfaces = ['wlm0', 'wlm1'] @@ -42,7 +40,7 @@ class PPPoEInterfaceTest(unittest.TestCase): self.session.commit() del self.session - def test_pppoe_1(self): + def test_wlm_1(self): for interface in self._interfaces: self.session.set(base_path + [interface, 'no-peer-dns']) self.session.set(base_path + [interface, 'ondemand']) -- cgit v1.2.3 From 22eb02d2a362d30d8f3f6f1df13c59daea0eba25 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 30 Jun 2020 16:38:04 +0200 Subject: interfaces: pppoe: add IPv6 tests --- scripts/cli/test_interfaces_pppoe.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/cli/test_interfaces_pppoe.py b/scripts/cli/test_interfaces_pppoe.py index 0ad50a2bc..3c11da795 100755 --- a/scripts/cli/test_interfaces_pppoe.py +++ b/scripts/cli/test_interfaces_pppoe.py @@ -108,6 +108,7 @@ class PPPoEInterfaceTest(unittest.TestCase): self.session.set(base_path + [interface, 'default-route', 'none']) self.session.set(base_path + [interface, 'no-peer-dns']) self.session.set(base_path + [interface, 'source-interface', self._source_interface]) + self.session.set(base_path + [interface, 'ipv6', 'enable']) # prefix delegation stuff dhcpv6_pd_base = base_path + [interface, 'dhcpv6-options', 'prefix-delegation'] @@ -127,6 +128,10 @@ class PPPoEInterfaceTest(unittest.TestCase): tmp = get_config_value(interface, 'password')[1].replace('"', '') self.assertTrue(tmp in 'vyos') + for param in ['+ipv6', 'ipv6cp-use-ipaddr']: + tmp = get_config_value(interface, param)[0] + self.assertTrue(tmp in param) + # verify DHCPv6 prefix delegation # will return: ['delegation', '::/56 infinity;'] tmp = get_dhcp6c_config_value(interface, 'prefix')[1].split()[0] # mind the whitespace -- cgit v1.2.3 From 8e165496cf32f16acb182022d301360e78465a0b Mon Sep 17 00:00:00 2001 From: fmertz Date: Wed, 1 Jul 2020 17:21:17 +0000 Subject: system display: T2464: initial smoke test + scripts/cli/test_system_display.py system display smoke test --- scripts/cli/test_system_display.py | 135 +++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100755 scripts/cli/test_system_display.py diff --git a/scripts/cli/test_system_display.py b/scripts/cli/test_system_display.py new file mode 100755 index 000000000..001d2d5ab --- /dev/null +++ b/scripts/cli/test_system_display.py @@ -0,0 +1,135 @@ +#!/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 . + +import re +import os +import unittest +import configparser + +from psutil import process_iter +from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.util import read_file + +base_path = ['system', 'display'] + +""" + system display model (sdec|ezio) + system display show host (cpu|cpu-all|cpu-hist|disk|load-hist|memory|proc|uptime) + network interface alias + units (bps|Bps|pps) + clock (big|mini|date-time) + title + + system display time + system display hello + system display bye + system display disabled +""" + +class SystemDisplayTest(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + + def tearDown(self): + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_system_display(self): + # configure some system display + self.session.set(base_path + ['hello', 'Welcome to VyOS']) + self.session.set(base_path + ['bye', 'Bye from VyOS']) + self.session.set(base_path + ['time', '30']) + + # check validate() - a model and a show are required + with self.assertRaises(ConfigSessionError): + self.session.commit() + + self.session.set(base_path + ['model', 'ezio']) + + # check validate() - a show required + with self.assertRaises(ConfigSessionError): + self.session.commit() + + self.session.set(base_path + ['show', 'clock', 'big']) + + self.session.set(base_path + ['show', 'network', 'units', 'pps']) + self.session.set(base_path + ['show', 'network', 'interface', 'eth0', 'alias', 'WAN']) + self.session.set(base_path + ['show', 'network', 'interface', 'eth1', 'alias', 'LAN']) + self.session.set(base_path + ['show', 'network', 'interface', 'eth2', 'alias', 'WIFI']) + # One too many + self.session.set(base_path + ['show', 'network', 'interface', 'eth3']) + + # check validate() - more then 3 interfaces + with self.assertRaises(ConfigSessionError): + self.session.commit() + + self.session.delete(base_path + ['show', 'network', 'interface', 'eth3']) + + # commit changes + self.session.commit() + + # load up ini-styled LCDd.conf + LCDd_conf = configparser.ConfigParser() + LCDd_conf.read('/etc/LCDd.conf') + + # Check settings made it into LCDd.conf + self.assertTrue(LCDd_conf['server']['Driver'] == 'hd44780') + self.assertTrue(LCDd_conf['server']['Hello'] == '"Welcome to VyOS"') + self.assertTrue(LCDd_conf['server']['GoodBye'] == '"Bye from VyOS"') + self.assertTrue(LCDd_conf['server']['WaitTime'] == '30') + + self.assertTrue(LCDd_conf['hd44780']['ConnectionType'] == 'ezio') + self.assertTrue(LCDd_conf['hd44780']['Keypad'] == 'yes') + self.assertTrue(LCDd_conf['hd44780']['Size'] == '16x2') + self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_1'] == 'Enter') + self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_2'] == 'Up') + self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_3'] == 'Down') + self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_4'] == 'Escape') + #self.assertTrue(LCDd_conf['hd44780']['Device'] == '/dev/ttyS1') + + # load up ini-styled lcdproc.conf configuration file + lcdproc_conf = configparser.ConfigParser() + + lcdproc_conf.read('/etc/lcdproc.conf') + # clock + self.assertTrue(lcdproc_conf['TimeDate']['Active'] == 'false') + self.assertTrue(lcdproc_conf['BigClock']['Active'] == 'true') + self.assertTrue(lcdproc_conf['MiniClock']['Active'] == 'false') + # host + self.assertTrue(lcdproc_conf['CPU']['Active'] == 'false') + self.assertTrue(lcdproc_conf['Memory']['Active'] == 'false') + self.assertTrue(lcdproc_conf['Load']['Active'] == 'false') + # network + self.assertTrue(lcdproc_conf['Iface']['Active'] == 'true') + self.assertTrue(lcdproc_conf['Iface']['Interface0'] == 'eth0') + self.assertTrue(lcdproc_conf['Iface']['Alias0'] == 'WAN') + self.assertTrue(lcdproc_conf['Iface']['Interface1'] == 'eth1') + self.assertTrue(lcdproc_conf['Iface']['Alias1'] == 'LAN') + self.assertTrue(lcdproc_conf['Iface']['Interface2'] == 'eth2') + self.assertTrue(lcdproc_conf['Iface']['Alias2'] == 'WIFI') + self.assertTrue(lcdproc_conf['Iface']['unit'] == 'packet') + # Check if LCdd and lcdproc are running + running = 0 + for p in process_iter(): + if p.name() in ['lcdproc', 'LCDd']: + running += 1 + + # both processes running + self.assertTrue(running == 2) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From 7bc4167d9520901c001e8d8a08d837e528b3de33 Mon Sep 17 00:00:00 2001 From: fmertz Date: Thu, 2 Jul 2020 05:05:37 +0000 Subject: system display: T2564: conf file in /run * scripts/cli/test_system_display.py conf files in /run --- scripts/cli/test_system_display.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/cli/test_system_display.py b/scripts/cli/test_system_display.py index 001d2d5ab..f9fb00aa5 100755 --- a/scripts/cli/test_system_display.py +++ b/scripts/cli/test_system_display.py @@ -84,7 +84,7 @@ class SystemDisplayTest(unittest.TestCase): # load up ini-styled LCDd.conf LCDd_conf = configparser.ConfigParser() - LCDd_conf.read('/etc/LCDd.conf') + LCDd_conf.read('/run/LCDd/LCDd.lo.conf') # Check settings made it into LCDd.conf self.assertTrue(LCDd_conf['server']['Driver'] == 'hd44780') @@ -104,7 +104,7 @@ class SystemDisplayTest(unittest.TestCase): # load up ini-styled lcdproc.conf configuration file lcdproc_conf = configparser.ConfigParser() - lcdproc_conf.read('/etc/lcdproc.conf') + lcdproc_conf.read('/run/lcdproc/lcdproc.lo.conf') # clock self.assertTrue(lcdproc_conf['TimeDate']['Active'] == 'false') self.assertTrue(lcdproc_conf['BigClock']['Active'] == 'true') -- cgit v1.2.3 From 7981dc1e5b3fc077d3d8732c10bc967fbe9f8596 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 3 Jul 2020 11:09:35 +0200 Subject: ntp: initial simple test --- scripts/cli/test_system_ntp.py | 66 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100755 scripts/cli/test_system_ntp.py diff --git a/scripts/cli/test_system_ntp.py b/scripts/cli/test_system_ntp.py new file mode 100755 index 000000000..c3fff8b06 --- /dev/null +++ b/scripts/cli/test_system_ntp.py @@ -0,0 +1,66 @@ +#!/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 . + +import re +import os +import unittest + +from psutil import process_iter +from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.util import read_file + +NTP_CONF = '/etc/ntp.conf' +base_path = ['system', 'ntp'] + +def get_config_value(key): + tmp = read_file(NTP_CONF) + tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) + return tmp + +class TestSystemNTP(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + self.session.delete(base_path) + + def tearDown(self): + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_ntp_options(self): + servers = ['192.0.2.1', '192.0.2.2'] + options = ['noselect', 'preempt', 'prefer'] + + for server in servers: + for option in options: + self.session.set(base_path + ['server', server, option]) + + # commit changes + self.session.commit() + + # Check configured port + tmp = get_config_value('server') + for server in servers: + test = f'{server} iburst ' + ' '.join(options) + self.assertTrue(test in tmp) + + # Check for running process + self.assertTrue("ntpd" in (p.name() for p in process_iter())) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From 2ac2c022d82b572caf40ae04e197ad736233939b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 3 Jul 2020 11:41:52 +0200 Subject: ntp: extend test with allow-clients, listen-address --- scripts/cli/test_system_ntp.py | 43 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/scripts/cli/test_system_ntp.py b/scripts/cli/test_system_ntp.py index c3fff8b06..ede096376 100755 --- a/scripts/cli/test_system_ntp.py +++ b/scripts/cli/test_system_ntp.py @@ -18,6 +18,7 @@ import re import os import unittest +from ipaddress import ip_network from psutil import process_iter from vyos.configsession import ConfigSession, ConfigSessionError from vyos.util import read_file @@ -43,6 +44,7 @@ class TestSystemNTP(unittest.TestCase): del self.session def test_ntp_options(self): + """ Test basic NTP support with multiple servers and their options """ servers = ['192.0.2.1', '192.0.2.2'] options = ['noselect', 'preempt', 'prefer'] @@ -53,7 +55,7 @@ class TestSystemNTP(unittest.TestCase): # commit changes self.session.commit() - # Check configured port + # Check generated configuration tmp = get_config_value('server') for server in servers: test = f'{server} iburst ' + ' '.join(options) @@ -62,5 +64,44 @@ class TestSystemNTP(unittest.TestCase): # Check for running process self.assertTrue("ntpd" in (p.name() for p in process_iter())) + def test_ntp_clients(self): + """ Test the allowed-networks statement """ + listen_address = ['127.0.0.1', '::1'] + for listen in listen_address: + self.session.set(base_path + ['listen-address', listen]) + + networks = ['192.0.2.0/24', '2001:db8:1000::/64'] + for network in networks: + self.session.set(base_path + ['allow-clients', 'address', network]) + + # Verify "NTP server not configured" verify() statement + with self.assertRaises(ConfigSessionError): + self.session.commit() + + servers = ['192.0.2.1', '192.0.2.2'] + for server in servers: + self.session.set(base_path + ['server', server]) + + self.session.commit() + + # Check generated client address configuration + for network in networks: + network_address = ip_network(network).network_address + network_netmask = ip_network(network).netmask + + tmp = get_config_value(f'restrict {network_address}')[0] + test = f'mask {network_netmask} nomodify notrap nopeer' + self.assertTrue(tmp in test) + + # Check listen address + tmp = get_config_value('interface') + test = ['ignore wildcard'] + for listen in listen_address: + test.append(f'listen {listen}') + self.assertEqual(tmp, test) + + # Check for running process + self.assertTrue("ntpd" in (p.name() for p in process_iter())) + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 1b5ff93980fed3e45897a46fe36c5785f0c668fc Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 3 Jul 2020 13:28:18 +0200 Subject: ntp: reuse new library functions for CIDR mangling --- scripts/cli/test_system_ntp.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/cli/test_system_ntp.py b/scripts/cli/test_system_ntp.py index ede096376..856a28916 100755 --- a/scripts/cli/test_system_ntp.py +++ b/scripts/cli/test_system_ntp.py @@ -18,9 +18,9 @@ import re import os import unittest -from ipaddress import ip_network from psutil import process_iter from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.template import vyos_address_from_cidr, vyos_netmask_from_cidr from vyos.util import read_file NTP_CONF = '/etc/ntp.conf' @@ -29,7 +29,8 @@ base_path = ['system', 'ntp'] def get_config_value(key): tmp = read_file(NTP_CONF) tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) - return tmp + # remove possible trailing whitespaces + return [item.strip() for item in tmp] class TestSystemNTP(unittest.TestCase): def setUp(self): @@ -86,8 +87,8 @@ class TestSystemNTP(unittest.TestCase): # Check generated client address configuration for network in networks: - network_address = ip_network(network).network_address - network_netmask = ip_network(network).netmask + network_address = vyos_address_from_cidr(network) + network_netmask = vyos_netmask_from_cidr(network) tmp = get_config_value(f'restrict {network_address}')[0] test = f'mask {network_netmask} nomodify notrap nopeer' -- cgit v1.2.3 From cc95d6f89282c9d66120f2a9b40a9aaff2f981bb Mon Sep 17 00:00:00 2001 From: fmertz Date: Wed, 24 Jun 2020 16:25:21 +0000 Subject: system display: T2564 Extend VyOS to support appliance LCDs Added support for system LCDs under CLI system display +data/templates/system-display/LCDd.conf.tmpl template for LCDd server configuration file +data/templates/system-display/lcdproc.conf.tmpl template for lcdproc client configuration file +interface-definitions/system-display.xml.in CLI for system display +src/conf_mode/system-display.py processing code for system display +src/systemd/lcdproc.service systemd service definition file for lcdproc client CLI: system display model (SDEC|EZIO) system display config (enabled|disabled) system display show host (cpu|cpu-all|cpu-hist|disk|load-hist|memory|proc|uptime) network interface alias units (bps|Bps|pps) clock (big|mini|date-time) title system display duration system display hello system display bye --- data/templates/system-display/LCDd.conf.tmpl | 1525 +++++++++++++++++++++++ data/templates/system-display/lcdproc.conf.tmpl | 177 +++ interface-definitions/system-display.xml.in | 245 ++++ src/conf_mode/system-display.py | 92 ++ src/systemd/lcdproc.service | 10 + 5 files changed, 2049 insertions(+) create mode 100644 data/templates/system-display/LCDd.conf.tmpl create mode 100644 data/templates/system-display/lcdproc.conf.tmpl create mode 100644 interface-definitions/system-display.xml.in create mode 100755 src/conf_mode/system-display.py create mode 100644 src/systemd/lcdproc.service diff --git a/data/templates/system-display/LCDd.conf.tmpl b/data/templates/system-display/LCDd.conf.tmpl new file mode 100644 index 000000000..0b605e09d --- /dev/null +++ b/data/templates/system-display/LCDd.conf.tmpl @@ -0,0 +1,1525 @@ +### Autogenerted by system-display.py ## +# LCDd.conf -- configuration file for the LCDproc server daemon LCDd +# +# This file contains the configuration for the LCDd server. +# +# The format is ini-file-like. It is divided into sections that start at +# markers that look like [section]. Comments are all line-based comments, +# and are lines that start with '#' or ';'. +# +# The server has a 'central' section named [server]. For the menu there is +# a section called [menu]. Further each driver has a section which +# defines how the driver acts. +# +# The drivers are activated by specifying them in a driver= line in the +# server section, like: +# +# Driver=curses +# +# This tells LCDd to use the curses driver. +# The first driver that is loaded and is capable of output defines the +# size of the display. The default driver to use is curses. +# If the driver is specified using the -d command line option, +# the Driver= options in the config file are ignored. +# +# The drivers read their own options from the respective sections. + + + +## Server section with all kinds of settings for the LCDd server ## +[server] + +# Where can we find the driver modules ? +# IMPORTANT: Make sure to change this setting to reflect your +# specific setup! Otherwise LCDd won't be able to find +# the driver modules and will thus not be able to +# function properly. +# NOTE: Always place a slash as last character ! +DriverPath=/usr/lib/x86_64-linux-gnu/lcdproc/ + +# Tells the server to load the given drivers. Multiple lines can be given. +# The name of the driver is case sensitive and determines the section +# where to look for further configuration options of the specific driver +# as well as the name of the dynamic driver module to load at runtime. +# The latter one can be changed by giving a File= directive in the +# driver specific section. +# +# The following drivers are supported: +# bayrad, CFontz, CFontzPacket, curses, CwLnx, ea65, EyeboxOne, futaba, +# g15, glcd, glcdlib, glk, hd44780, icp_a106, imon, imonlcd,, IOWarrior, +# irman, joy, lb216, lcdm001, lcterm, linux_input, lirc, lis, MD8800, +# mdm166a, ms6931, mtc_s16209x, MtxOrb, mx5000, NoritakeVFD, +# Olimex_MOD_LCD1x9, picolcd, pyramid, rawserial, sdeclcd, sed1330, +# sed1520, serialPOS, serialVFD, shuttleVFD, sli, stv5730, svga, t6963, +# text, tyan, ula200, vlsys_m428, xosd, yard2LCD +{%- if model == 'SDEC' %} +Driver=sdeclcd +{%- endif %} + +{%- if model == 'EZIO' %} +Driver=hd44780 +{%- endif %} + +# Tells the driver to bind to the given interface. [default: 127.0.0.1] +#Bind=127.0.0.1 + +# Listen on this specified port. [default: 13666] +#Port=13666 + +# Sets the reporting level; defaults to warnings and errors only. +# [default: 2; legal: 0-5] +#ReportLevel=3 + +# Should we report to syslog instead of stderr? [default: no; legal: yes, no] +#ReportToSyslog=yes + +# User to run as. LCDd will drop its root privileges and run as this user +# instead. [default: nobody] +User=nobody + +# The server will stay in the foreground if set to yes. +# [default: no, legal: yes, no] +#Foreground=yes + +# Hello message: each entry represents a display line; default: builtin +Hello="{%- if hello %}{{ hello }}{%- else %}Welcome to VyOS{%- endif %}" + +# GoodBye message: each entry represents a display line; default: builtin +GoodBye="{%- if bye %}{{ bye }}{%- else %}Bye from VyOS{%- endif %}" + +# Sets the interval in microseconds for updating the display. +# [default: 125000 meaning 8Hz] +#FrameInterval=125000 + +# Sets the default time in seconds to displays a screen. [default: 4] +WaitTime={%- if duration %}{{ duration }}{%- else%}4{%- endif %} + +# If set to no, LCDd will start with screen rotation disabled. This has the +# same effect as if the ToggleRotateKey had been pressed. Rotation will start +# if the ToggleRotateKey is pressed. Note that this setting does not turn off +# priority sorting of screens. [default: on; legal: on, off] +#AutoRotate=off + +# If yes, the the serverscreen will be rotated as a usual info screen. If no, +# it will be a background screen, only visible when no other screens are +# active. The special value 'blank' is similar to no, but only a blank screen +# is displayed. [default: on; legal: on, off, blank] +ServerScreen=no + +# Set master backlight setting. If set to 'open' a client may control the +# backlight for its own screens (only). [default: open; legal: off, open, on] +#Backlight=open + +# Set master heartbeat setting. If set to 'open' a client may control the +# heartbeat for its own screens (only). [default: open; legal: off, open, on] +#Heartbeat=open + +# set title scrolling speed [default: 10; legal: 0-10] +#TitleSpeed=10 + +# The "...Key=" lines define what the server does with keypresses that +# don't go to any client. The ToggleRotateKey stops rotation of screens, while +# the PrevScreenKey and NextScreenKey go back / forward one screen (even if +# rotation is disabled. +# Assign the key string returned by the driver to the ...Key setting. These +# are the defaults: +ToggleRotateKey=Enter +PrevScreenKey=Left +NextScreenKey=Right +#ScrollUpKey=Up +#ScrollDownKey=Down + +## The menu section. The menu is an internal LCDproc client. ## +[menu] +# If true the server allows transitions between different client's menus +# [default: false; legal: true, false] +#PermissiveGoto=false + +# You can configure what keys the menu should use. Note that the MenuKey +# will be reserved exclusively, the others work in shared mode. + +# Up to six keys are supported. The MenuKey (to enter and exit the menu), the +# EnterKey (to select values) and at least one movement keys are required. +# These are the default key assignments: +MenuKey=Escape +EnterKey=Enter +UpKey=Up +DownKey=Down +#LeftKey=Left +#RightKey=Right + + +### Driver sections are below this line, in alphabetical order ### + + +## EMAC BayRAD driver ## +[bayrad] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/lcd + +# Set the communication speed [default: 9600; legal: 1200, 2400, 9600, 19200] +Speed=9600 + + + +## CrystalFontz driver (for CF632 & CF634) ## +[CFontz] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/ttyS0 +# Select the LCD size [default: 20x4] +Size=20x4 +# Set the initial contrast [default: 560; legal: 0 - 1000] +Contrast=350 +# Set the initial brightness [default: 1000; legal: 0 - 1000] +Brightness=1000 +# Set the initial off-brightness [default: 0; legal: 0 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +OffBrightness=0 +# Set the communication speed [default: 9600; legal: 1200, 2400, 9600, 19200, +# 115200] +Speed=9600 +# Set the firmware version (New means >= 2.0) [default: no; legal: yes, no] +NewFirmware=no +# Reinitialize the LCD's BIOS [default: no; legal: yes, no] +# normally you shouldn't need this +Reboot=no + + + +## CrystalFontz packet driver (for CFA533, CFA631, CFA633 & CFA635) ## +[CFontzPacket] + +# Select the LCD model [default: 633; legal: 533, 631, 633, 635] +Model=633 + +# Select the output device to use [default: /dev/lcd] +Device=/dev/ttyS1 + +# Set the initial contrast [default: 560; legal: 0 - 1000] +Contrast=350 + +# Set the initial brightness [default: 1000; legal: 0 - 1000] +Brightness=1000 + +# Set the initial off-brightness [default: 0; legal: 0 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +OffBrightness=50 + +# Reinitialize the LCD's BIOS on driver start. [default: no; legal: yes, no] +Reboot=yes + +# Enable the USB flag if the device is connected to an USB port. For +# serial ports leave it disabled. [default: no; legal: yes, no] +#USB=yes + +# Very old 633 firmware versions do not support partial screen updates using +# 'Send Data to LCD' command (31). For those devices it may be necessary to +# enable this flag. [default: no; legal: yes, no] +#OldFirmware=yes + +# Override the LCD size known for the selected model. Usually setting this +# value should not be necessary. +#Size=20x4 + +# Override the default communication speed known for the selected model. +# Default value depends on model [legal: 19200, 115200] +#Speed=115200 + + + +## Curses driver ## +[curses] + +# color settings +# foreground color [default: blue] +Foreground=blue +# background color when "backlight" is off [default: cyan] +Background=cyan +# background color when "backlight" is on [default: red] +Backlight=red + +# display size [default: 20x4] +Size=20x2 + +# What position (X,Y) to start the left top corner at... +# Default: (7,7) +TopLeftX=7 +TopLeftY=7 + +# use ASC symbols for icons & bars [default: no; legal: yes, no] +UseACS=no + +# draw Border [default: yes; legal: yes, no] +DrawBorder=yes + + + +## Cwlinux driver ## +[CwLnx] + +# Select the LCD model [default: 12232; legal: 12232, 12832, 1602] +Model=12232 + +# Select the output device to use [default: /dev/lcd] +Device=/dev/ttyUSB0 + +# Select the LCD size. Default depends on model: +# 12232: 20x4 +# 12832: 21x4 +# 1602: 16x2 +Size=20x4 + +# Set the communication speed [default: 19200; legal: 9600, 19200] +Speed=19200 + +# Reinitialize the LCD's BIOS [default: no; legal: yes, no] +# normally you shouldn't need this +Reboot=no + +# If you have a keypad connected. Keypad layout is currently not +# configureable from the config file. +Keypad=yes + +# If you have a non-standard keypad you can associate any keystrings to keys. +# There are 6 input keys in the CwLnx hardware that generate characters +# from 'A' to 'F'. +# +# The following is the built-in default mapping hardcoded in the driver. +# You can leave those unchanged if you have a standard keypad. +# You can change it if you want to report other keystrings or have a non +# standard keypad. +# KeyMap_A=Up +# KeyMap_B=Down +# KeyMap_C=Left +# KeyMap_D=Right +# KeyMap_E=Enter +# KeyMap_F=Escape + +# keypad_test_mode permits one to test keypad assignment +# Default value is no +#keypad_test_mode=yes + + + +## ea65 driver for the display in AOpen XC Cube AV EA65 media barebones ## +[ea65] + +# Device is fixed /dev/ttyS1 +# Width and Height are fixed 9x1 + +# As the VFD is self luminescent we don't have a backlight +# But we can use the backlight functions to control the front LEDs +# Brightness 0 to 299 -> LEDs off +# Brightness 300 to 699 -> LEDs half bright +# Brightness 700 to 1000 -> LEDs full bright +Brightness=500 +# OffBrightness is the the value used for the 'backlight off' state +OffBrightness=0 + + + +## EyeboxOne driver ## +[EyeboxOne] + +# Select the output device to use [default: /dev/ttyS1] +# Device=/dev/cua01 +Device=/dev/ttyS1 + +# Set the display size [default: 20x4] +Size=20x4 + +# Switch on the backlight? [default: yes] +Backlight=yes + +# Switch on the cursor? [default: no] +Cursor=no + +# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] +Speed=19200 + +# Enter Key is a \r character, so it's hardcoded in the driver +LeftKey=D +RightKey=C +UpKey=A +DownKey=B +EscapeKey=P + +# You can find out which key of your display sends which +# character by setting keypad_test_mode to yes and running +# LCDd. LCDd will output all characters it receives. +# Afterwards you can modify the settings above and set +# keypad_set_mode to no again. +keypad_test_mode=no + +## Futaba TOSD-5711BB VFD Driver ## +[futaba] + +## g15 driver for Logitech G15 Keyboard LCDs ## +[g15] + +# Display size (currently unused) +size=20x5 + + + +## glcd generic graphical display driver +[glcd] +# Select what type of connection. See documentation for types. +ConnectionType=t6963 + +# Width and height of the display in pixel. The supported sizes may depend on +# the ConnectionType. [default: 128x64; legal: 1x1 - 640x480] +#Size=128x64 + +# Width and height of a character cell in pixels. This value is only used if +# the driver has been compiled with FreeType and it is enabled. Otherwise the +# default 6x8 cell is used. +#CellSize=12x16 + +# If LCDproc has been compiled with FreeType 2 support this option can be used +# to turn if off intentionally. [default: yes; legal: yes, no] +#useFT2=no + +# Path to font file to use for FreeType rendering. This font must be monospace +# and should contain some special Unicode characters like arrows (Andale Mono +# is recommended and can be fetched at http://corefonts.sf.net). +#normal_font=/usr/local/lib/X11/fonts/TTF/andalemo.ttf + +# Some fonts miss the Unicode characters used to represent icons. In this case +# the built-in 5x8 font can used if this option is turned off. [default: yes; +# legal: yes, no] +#fontHasIcons=no + +# Set the initial contrast if supported by connection type. +# [default: 600; legal: 0 - 1000] +#Contrast=600 + +# Set brightness of the backlight if the backlight is switched 'on'. +# [default: 800; legal: 0 - 1000] +#Brightness=1000 + +# Set brightness of the backlight if the backlight is switched 'off'. Set this +# to zero to completely turn off the backlight. [default: 100; legal: 0 - 1000] +#OffBrightness=0 + +# Time (ms) from first key report to first repeat. Set to 0 to disable repeated +# key reports. [default: 500; legal: 0 - 3000] +#KeyRepeatDelay=500 + +# Time (ms) between repeated key reports. Ignored if KeyRepeatDelay is disabled +# (set to zero). [default: 300; legal: 0 - 3000] +#KeyRepeatInterval=300 + +# Assign key strings to keys. There may be up to 16 keys numbered 'A' to 'Z'. +# By default keys 'A' to 'F' are assigned Up, Down, Left, Right, Enter, Escape. +KeyMap_A=Up +KeyMap_B=Down +KeyMap_C=Enter +KeyMap_D=Escape + +# --- t6963 options --- + +# Parallel port to use [default: 0x378; legal: 0x200 - 0x400] +#Port=0x378 + +# Use LPT port in bi-directional mode. This should work on most LPT port +# and is required for proper timing! [default: yes; legal: yes, no] +#bidirectional=yes + +# Insert additional delays into reads / writes. [default: no; legal: yes, no] +#delayBus=no + +# --- serdisplib options --- + +# Name of the underlying serdisplib driver, e.g. ctinclud. See +# serdisplib documentation for details. +serdisp_name=t6963 + +# The display device to use, e.g. serraw:/dev/ttyS0, +# parport:/dev/parport0 or USB:07c0/1501. +serdisp_device=/dev/ppi0 + +# Options string to pass to serdisplib during initialization. Use +# this to set any display related options (e.g. wiring). The display size is +# always set based on the Size configured above! By default, no options are +# set. +# Important: The value must be quoted as it contains equal signs! +#serdisp_options="INVERT=1" + +# --- x11 options --- + +# PixelSize is size of each dot in pixels + a pixel gap. [default: 3+1] +#x11_PixelSize=3+1 + +# Colors are in RRGGBB format prefixed with "0x". +# PixelColor: The color of each dot at full contrast. [default: 0x000000] +#x11_PixelColor=0x000000 + +# BacklightColor: The color of the backlight as full brightness. +# [default: 0x80FF80] +#x11_BacklightColor=0x80FF80 + +# Border: Adds a border (empty space) around the LCD portion of X11 window. +# [default: 20] +#x11_Border=20 + +# Inverted: inverts the pixels [default: no; legal: yes, no] +#x11_Inverted=no + +# --- picolcdgfx options --- + +# Time in ms for usb_read to wait on a key press. [default: 125; legal: >0] +#picolcdgfx_KeyTimeout=125 + +# Inverted: Inverts the pixels. [default: no; legal: yes or no] +#picolcdgfx_Inverted=no + + + +## glcdlib meta driver for graphical LCDs ## +[glcdlib] + +## mandatory: + +# which graphical display supported by graphlcd-base to use [default: image] +# (see /etc/graphlcd.conf for possible drivers) +Driver=noritake800 + +# no=use graphlcd bitmap fonts (they have only one size / font file) +# yes=use fonts supported by FreeType2 (needs Freetype2 support in +# libglcdprocdriver and its dependants) +UseFT2=yes + +# text resolution in fixed width characters [default: 16x4] +# (if it won't fit according to available physical pixel resolution +# and the minimum available font face size in pixels, then +# 'DebugBorder' will automatically be turned on) +TextResolution=20x4 + +# path to font file to use +FontFile=/usr/share/fonts/corefonts/courbd.ttf + +## these only apply if UseFT2=yes: + +# character encoding to use +CharEncoding=iso8859-2 + +# minimum size in pixels in which fonts should be rendered +MinFontFaceSize=7x12 + +## optional: +Brightness=50 # Brightness (in %) if applicable +Contrast=50 # Contrast (in %) if applicable +Backlight=no # Backlight if applicable +UpsideDown=no # flip image upside down +Invert=no # invert light/dark pixels +ShowDebugFrame=no # turns on/off 1 pixel thick debugging + # border within the usable text area, + # for setting up TextResolution and + # MinFontFaceSize (if using FT2); +ShowBigBorder=no # border around the unused area +ShowThinBorder=yes # border around the unused area +PixelShiftX=0 +PixelShiftY=2 + + + +## Matrix Orbital GLK driver ## +[glk] + +# select the serial device to use [default: /dev/lcd] +Device=/dev/lcd + +# set the initial contrast value [default: 500; legal: 0 - 1000] +Contrast=500 + +# set the serial port speed [default: 19200; legal: 9600, 19200, 38400, 57600, 115200] +Speed=19200 + + + +## Hitachi HD44780 driver ## +[hd44780] +{%- if model == 'EZIO' %} +ConnectionType=ezio +Device=/dev/ttyS1 +Keypad=yes +Size=16x2 +KeyMatrix_4_1=Enter +KeyMatrix_4_2=Up +KeyMatrix_4_3=Down +KeyMatrix_4_4=Escape +{%- endif %} + +# Select what type of connection. See documentation for available types. +#ConnectionType=4bit + +# Select model if have non-standard one which require extra initialization or handling or +# just want extra features it offers. +# Available: standard (default), extended, winstar_oled, pt6314_vfd +# - standard is default, use for LCDs not mentioned below. +# - extended, hd66712, ks0073: allows use 4-line "extended" mode, +# same as deprecated now option ExtendedMode=yes +# - winstar_oled, weh00xxyya: changes initialization for WINSTAR's WEH00xxyyA displays +# and allows handling brightness +# - pt6314_vfd: allows handling brightness on PTC's PT6314 VFDs +# +# This option should be independent of connection type. +#Model = standard + +# I/O address of the LPT port. Usual values are: 0x278, 0x378 and 0x3BC. +# For I2C connections this sets the slave address (usually 0x20). +#Port=0x378 + +# Device of the serial, I2C, or SPI interface [default: /dev/lcd] +#Device=/dev/ttyS0 + +# Bitrate of the serial port (0 for interface default) +#Speed=0 + +# If you have a keypad connected. +# You may also need to configure the keypad layout further on in this file. +#Keypad=no + +# Set the initial contrast (bwctusb, lcd2usb, and usb4all) +# [default: 800; legal: 0 - 1000] +#Contrast=0 + +# Set brightness of the backlight (lcd2usb and usb4all): +# Brightness is the brightness while the backlight is set to 'on'. +# [default: 800; legal: 0 - 1000] +#Brightness=1000 + +# OffBrightness is the brightness while the backlight is set to 'off'. +# [default: 300; legal: 0 - 1000] +#OffBrightness=0 + +# Specify if you have a switchable backlight and if yes, can select method for turning it on/off: +# +# - none - no switchable backlight is available. For compability also boolean +# 0, n, no, off and false are aliases. +# - external - use external pin or any other method defined with ConnectionType backlight +# handling. For backward compability also this value is chosen for boolean +# TRUE values: 1, y, yes, on and true. +# - internal - means that backlight is handled using internal commands according +# to selected display model (with Model option). Depending on model, +# Brightness and OffBrightness options can be taken into account. +# - internalCmds - means that commands for turning on and off backlight are given +# with extra options BacklightOnCmd and BacklightOffCmd, which would be treated +# as catch up (last resort) for other types of displays which have similar features. +# +# You can provide multiple occurences of this option to use more than one method. +# Default is model specific: Winstar OLED and PT6314 VFD enables internal backlight mode, +# for others it is set to none. +#Backlight = none + +# Commands for enabling internal backlight for use with Backlight=internalCmds. +# Up to 4 bytes can be encoded, as integer number in big-endian order. +# +# NOTE: this is advanced option, if command contains bits other than only brighness handling, +# they must be set accordingly to not disrupt display state. If for example 'FUNCTION SET' command +# is used for this purpose, bits of interface length (4-bit / 8-bit) must be set according to +# selected ConnectionType. +#BacklightCmdOn=0x1223 + +# Commands for disabling internal backlight for use with Backlight=internalCmds. +# Up to 4 bytes can be encoded, as integer number in big-endian order. +#BacklightCmdOff=0x1234 + + +# If you have the additional output port ("bargraph") and you want to +# be able to control it with the lcdproc OUTPUT command +#OutputPort=no + +# Specifies if the last line is pixel addressable (yes) or it controls an +# underline effect (no). [default: yes; legal: yes, no] +#Lastline=yes + +# Specifies the size of the LCD. +# In case of multiple combined displays, this should be the total size. +#Size=20x4 + +# For multiple combined displays: how many lines does each display have. +# Vspan=2,2 means both displays have 2 lines. +#vspan=2,2 + +# If you have an HD66712, a KS0073 or another controller with 'extended mode', +# set this flag to get into 4-line mode. On displays with just two lines, do +# not set this flag. +# As an additional restriction, controllers with and without extended mode +# AND 4 lines cannot be mixed for those connection types that support more +# than one display! +# NOTE: This option is deprecated in favour of choosing Model=extended option. +#ExtendedMode=yes + +# In extended mode, on some controllers like the ST7036 (in 3 line mode) +# the next line in DDRAM won't start 0x20 higher. [default: 0x20] +#LineAddress=0x10 + +# Character map to to map ISO-8859-1 to the LCD's character set +# [default: hd44780_default; legal: hd44780_default, hd44780_euro, ea_ks0073, +# sed1278f_0b, hd44780_koi8_r, hd44780_cp1251, hd44780_8859_5, upd16314, +# weh001602a_1] +# (hd44780_koi8_r, hd44780_cp1251, hd44780_8859_5, upd16314 and weh001602a_1 +# are possible if compiled with additional charmaps) +CharMap=hd44780_default + +# Font bank to be used for some displays such as the WINSTAR WEH001602A +# 0: English/Japanese (default) +# 1: Western Europe I +# 2: English/Rusian +# 3: Western Europe II +#FontBank=0 + +# If your display is slow and cannot keep up with the flow of data from +# LCDd, garbage can appear on the LCDd. Set this delay factor to 2 or 4 +# to increase the delays. Default: 1. +#DelayMult=2 + +# Some displays (e.g. vdr-wakeup) need a message from the driver to that it +# is still alive. When set to a value bigger then null the character in the +# upper left corner is updated every seconds. Default: 0. +#KeepAliveDisplay=0 + +# If you experience occasional garbage on your display you can use this +# option as workaround. If set to a value bigger than null it forces a +# full screen refresh seconds. Default: 0. +#RefreshDisplay=5 + +# You can reduce the inserted delays by setting this to false. +# On fast PCs it is possible your LCD does not respond correctly. +# Default: true. +#DelayBus=true + +# If you have a keypad you can assign keystrings to the keys. +# See documentation for used terms and how to wire it. +# For example to give directly connected key 4 the string "Enter", use: +# KeyDirect_4=Enter +# For matrix keys use the X and Y coordinates of the key: +# KeyMatrix_1_3=Enter +#KeyMatrix_4_1=Enter +#KeyMatrix_4_2=Up +#KeyMatrix_4_3=Down +#KeyMatrix_4_4=Escape + +## ICP Peripheral Comminication Protocol driver ## +# Supports A125 and A106 +# +# Short Press Select: Down +# Long Press Select: Up +# Short Press Enter: Enter +# Long Press Enter: Escape +# +[icp_a106] +Device=/dev/ttyS1 + +# Display dimensions +Size=20x2 + + +## Code Mercenaries IO-Warrior driver ## +[IOWarrior] + +# display dimensions +Size=20x4 + +# serial number. Must be exactly as listed by usbview +# (if not given, the 1st IOWarrior found gets used) +#SerialNumber=00000674 + +# If you have an HD66712, a KS0073 or another 'almost HD44780-compatible', +# set this flag to get into extended mode (4-line linear). +#ExtendedMode=yes + +# Specifies if the last line is pixel addressable (yes) or it controls an +# underline effect (no). [default: yes; legal: yes, no] +#Lastline=yes + + + +## Soundgraph/Ahanix/Silverstone/Uneed/Accent iMON driver ## +[imon] + +# select the device to use +Device=/dev/lcd0 + +# display dimensions +Size=16x2 + +# Character map to to map ISO-8859-1 to the displays character set. +# [default: none; legal: none, hd44780_euro, upd16314, hd44780_koi8_r, +# hd44780_cp1251, hd44780_8859_5 ] (upd16314, hd44780_koi8_r, +# hd44780_cp1251, hd44780_8859_5 are possible if compiled with additional +# charmaps) +CharMap=hd44780_euro + +## Soundgraph iMON LCD ## +[imonlcd] +# Specify which iMon protocol should be used +# [legal: 0, 1; default: 0] +# Choose 0 for 15c2:ffdc device, +# Choose 1 for 15c2:0038 device +Protocol=0 + +# Set the exit behavior [legal: 0-2; default: 1] +# 0 means leave shutdown message, +# 1 means show the big clock, +# 2 means blank device +#OnExit=2 + +# Select the output device to use [default: /dev/lcd0] +Device=/dev/lcd0 + +# Select the displays contrast [default: 200; legal: 0-1000] +Contrast=200 + +# Specify the size of the display in pixels [default: 96x16] +#Size=96x16 + +# Set the backlight state [default: on; legal: on, off] +#Backlight=on + +# Set the disc mode [legal: 0,1; default: 0] +# 0 => spin the "slim" disc - two disc segments, +# 1 => their complement spinning; +#DiscMode=0 + + + +## IrMan driver ## +[IrMan] +# in case of trouble with IrMan, try the Lirc emulator for IrMan + +# Select the input device to use +#Device=/dev/irman + +# Select the configuration file to use +#Config=/etc/irman.cfg + + + +## IRtrans driver ## +[irtrans] + +# Does the device have a backlight? [default: no; legal: yes, no] +#Backlight=no + +# IRTrans device to connect to [default: localhost] +#Hostname=localhost + +# display dimensions +Size=16x2 + + + +## Joystick driver ## +[joy] + +# Select the input device to use [default: /dev/js0] +Device=/dev/js0 + +# set the axis map +Map_Axis1neg=Left +Map_Axis1pos=Right +Map_Axis2neg=Up +Map_Axis2pos=Down + +# set the button map +Map_Button1=Enter +Map_Button2=Escape + + +## JW-002 driver ## +[jw002] + +# Select the output device to use [default: /dev/lcd] +#Device=/dev/ttyS0 + +# Set the display size [default: 24x8] +Size=24x8 + +# Optional X and Y offsets (in characters) to center a smaller display +# size on the full 24x8 panel +X_offset=0 +Y_offset=0 + +# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] +Speed=19200 + +# Pick which font page to use [default: 0] +# Note that different fonts probably have their bargraph chars in different +# spots. For ROM-based fonts 0-3, those characters are already known. +Font=0 + +# The following table translates from jw002 key letters to logical key names. +# By default no keys are mapped, meaning the keypad is not used at all. +#KeyMap_I=Left +#KeyMap_J=Right +#KeyMap_H=Up +#KeyMap_K=Down +#KeyMap_L=Enter +#KeyMap_A=Escape +# See the [menu] section for an explanation of the key mappings + +# You can find out which key of your display sends which +# character by setting keypad_test_mode to yes and running +# LCDd. LCDd will output all characters it receives. +# Afterwards you can modify the settings above and set +# keypad_set_mode to no again. +keypad_test_mode=no + + +## LB216 driver ## +[lb216] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/lcd + +# Set the initial brightness [default: 255; legal: 0 - 255] +Brightness=255 + +# Set the communication speed [default: 9600; legal: 2400, 9600] +Speed=9600 + +# Reinitialize the LCD's BIOS [default: no; legal: yes, no] +Reboot=no + + + +## LCDM001 driver ## +[lcdm001] + +Device=/dev/ttyS1 + +# keypad settings +# Keyname Function +# Normal context Menu context +# ------- -------------- ------------ +# PauseKey Pause/Continue Enter/select +# BackKey Back(Go to previous screen) Up/Left +# ForwardKey Forward(Go to next screen) Down/Right +# MainMenuKey Open main menu Exit/Cancel +PauseKey=LeftKey +BackKey=UpKey +ForwardKey=DownKey +MainMenuKey=RightKey + +# You can rearrange the settings here. +# If your device is broken, have a look at server/drivers/lcdm001.h + + + +## HNE LCTerm driver ## +[lcterm] +Device=/dev/ttyS1 +Size=16x2 + + +## Linux event device input driver ## +[linux_input] + +# Select the input device to use [default: /dev/input/event0]. This may be +# either an absolute path to the input node, starting with '/', or +# an input device name, e.g. "Logitech Gaming Keyboard Gaming Keys". +# Device=/dev/input/event0 + +# specify a non-default key map +#key=1,Escape +#key=28,Enter +#key=96,Enter +#key=105,Left +#key=106,Right +#key=103,Up +#key=108,Down + + +## LIRC input driver ## +[lirc] + +# Specify an alternative location of the lircrc file [default: ~/.lircrc] +#lircrc=/etc/lircrc.lcdproc + +# Must be the same as in your lircrc +#prog=lcdd + + + +## LIS MCE 2005 driver ## +[lis] + +# Set the initial brightness [default: 1000; legal: 0 - 1000] +# 0-250 = 25%, 251-500 = 50%, 501-750 = 75%, 751-1000 = 100% +#Brightness=1000 + +# Columns by lines [default: 20x2] +#Size=20x2 + +# USB Vendor ID [default: 0x0403] +# Change only if testing a compatible device. +#VendorID=0x0403 + +# USB Product ID [default: 0x6001] +# Change only if testing a compatible device. +#ProductID=0x6001 + +# Specifies if the last line is pixel addressable (yes) or it only controls an +# underline effect (no). [default: yes; legal: yes, no] +#Lastline=yes + + + +##The driver for the VFD of the Medion MD8800 PC ## +[MD8800] +# device to use [default: /dev/ttyS1] +#Device=/dev/ttyS1 + +# display size [default: 16x2] +#Size=16x2 + +# Set the initial brightness [default: 1000; legal: 0 - 1000] +Brightness=1000 +# Set the initial off-brightness [default: 0; legal: 0 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +OffBrightness=50 + + + +## Futuba MDM166A Display +[mdm166a] +# Show self-running clock after LCDd shutdown +# Possible values: [default: no; legal: no, small, big] +Clock=big +# Dim display, no dimming gives full brightness [default: no, legal: yes, no] +Dimming=no +# Dim display in case LCDd is inactive [default: no, legal: yes, no] +OffDimming=yes + + + +## MSI MS-6931 driver for displays in 1HU servers ## +[ms6931] + +# device to use [default: /dev/ttyS1] +Device=/dev/ttyS1 + +# display size [default: 16x2] +#Size=16x2 + + + +## MTC-S16209x driver ## +[mtc_s16209x] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/lcd + +# Set the initial brightness [default: 255; legal: 0 - 255] +Brightness=255 + +# Reinitialize the LCD's BIOS [default: no; legal: yes, no] +Reboot=no + + + +## Matrix Orbital driver ## +[MtxOrb] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/ttyS0 + +# Set the display size [default: 20x4] +Size=20x4 + +# Set the display type [default: lcd; legal: lcd, lkd, vfd, vkd] +Type=lkd + +# Set the initial contrast [default: 480] +# NOTE: The driver will ignore this if the display +# is a vfd or vkd as they don't have this feature +Contrast=480 + +# Some old displays do not have an adjustable backlight but only can +# switch the backlight on/off. If you experience randomly appearing block +# characters, try setting this to false. [default: yes; legal: yes, no] +hasAdjustableBacklight=no + +# Set the initial brightness [default: 1000; legal: 0 - 1000] +Brightness=1000 +# Set the initial off-brightness [default: 0; legal: 0 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +OffBrightness=0 + +# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] +Speed=19200 + +# The following table translates from MtxOrb key letters to logical key names. +# By default no keys are mapped, meaning the keypad is not used at all. +#KeyMap_A=Left +#KeyMap_B=Right +#KeyMap_C=Up +#KeyMap_D=Down +#KeyMap_E=Enter +#KeyMap_F=Escape +# See the [menu] section for an explanation of the key mappings + +# You can find out which key of your display sends which +# character by setting keypad_test_mode to yes and running +# LCDd. LCDd will output all characters it receives. +# Afterwards you can modify the settings above and set +# keypad_set_mode to no again. +keypad_test_mode=no + + + +## mx5000 driver for LCD display on the Logitech MX5000 keyboard ## +[mx5000] + +# Select the output device to use [default: /dev/hiddev0] +Device = /dev/hiddev0 +# Time to wait in ms after the refresh screen has been sent [default: 1000] +WaitAfterRefresh = 1000 + + + +## Noritake VFD driver ## +[NoritakeVFD] +# device where the VFD is. Usual values are /dev/ttyS0 and /dev/ttyS1 +# [default: /dev/lcd] +Device=/dev/ttyS0 +# Specifies the size of the LCD. +Size=20x4 +# Set the initial brightness [default: 1000; legal: 0 - 1000] +Brightness=1000 +# Set the initial off-brightness [default: 0; legal: 0 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +OffBrightness=50 +# set the serial port speed [default: 9600, legal: 1200, 2400, 9600, 19200, 115200] +Speed=9600 +# Set serial data parity [default: 0; legal: 0-2 ] +# Meaning: 0(=none), 1(=odd), 2(=even) +Parity=0 +# re-initialize the VFD [default: no; legal: yes, no] +Reboot=no + + + +## Olimex MOD-LCD1x9 driver ## +[Olimex_MOD_LCD1x9] + +# device file of the i2c controler +Device=/dev/i2c-0 + + +## Mini-box.com picoLCD (usblcd) driver ## +[picolcd] + +# KeyTimeout is only used if the picoLCD driver is built with libusb-0.1. When +# built with libusb-1.0 key and IR data is input asynchronously so there is no +# need to wait for the USB data. +# KeyTimeout is the time in ms that LCDd spends waiting for a key press before +# cycling through other duties. Higher values make LCDd use less CPU time and +# make key presses more detectable. Lower values make LCDd more responsive +# but a little prone to missing key presses. 500 (.5 second) is the default +# and a balanced value. [default: 500; legal: 0 - 1000] +KeyTimeout=500 + +# Key auto repeat is only available if the picoLCD driver is built with +# libusb-1.0. Use KeyRepeatDelay and KeyRepeatInterval to configure key auto +# repeat. +# +# Key auto repeat delay (time in ms from first key report to first repeat). Use +# zero to disable auto repeat. [default: 300; legal: 0 - 3000] +KeyRepeatDelay=300 + +# Key auto repeat interval (time in ms between repeat reports). Only used if +# KeyRepeatDelay is not zero. [default: 200; legal: 0 - 3000] +KeyRepeatInterval=200 + +# Sets the initial state of the backlight upon start-up. +# [default: on; legal: on, off] +#Backlight=on + +# Set the initial brightness [default: 1000; legal: 0 - 1000]. Works only +# with the 20x4 device +Brightness=1000 + +# Set the brightness while the backlight is 'off' [default: 0; legal: 0 - 1000]. +# Works only with the 20x4 device. +#OffBrightness=0 + +# Set the initial contrast [default: 1000; legal: 0 - 1000] +Contrast=1000 + +# Link the key lights to the backlight? [default: on; legal: on, off] +#LinkLights=off + +# Light the keys? [default: on; legal: on, off] +Keylights=on + +# If Keylights is on, the you can unlight specific keys below: +# Key0 is the directional pad. Key1 - Key5 correspond to the F1 - F5 keys. +# There is no LED for the +/- keys. This is a handy way to indicate to users +# which keys are disabled. [default: on; legal: on, off] +Key0Light=on +Key1Light=on +Key2Light=on +Key3Light=on +Key4Light=on +Key5Light=on + +# Host name or IP address of the LIRC instance that is to receive IR codes +# If not set, or set to an empty value, IR support is disabled. +#LircHost=127.0.0.1 + +# UDP port on which LIRC is listening [default: 8765; legal: 1 - 65535] +LircPort=8765 + +# UDP data time unit for LIRC [default: off; legal: on, off] +# On: times sent in microseconds (requires LIRC UDP driver that accepts this). +# Off: times sent in 'jiffies' (1/16384s) (supported by standard LIRC UDP driver). +LircTime_us=on + +# Threshold in microseconds of the gap that triggers flushing the IR data +# to lirc [default: 8000; legal: 1000 - ] +# If LircTime_us is on values greater than 32.767ms will disable the flush +# If LircTime_us is off values greater than 1.999938s will disable the flush +LircFlushThreshold=10000 + + + +## Pyramid LCD driver ## +[pyramid] + +# device to connect to [default: /dev/lcd] +Device=/dev/ttyUSB0 + + + +## rawserial driver ## +[rawserial] + +# Select the output device to use [default: /dev/cuaU0] +Device=/dev/ttyS0 + +# Serial port baudrate [default: 9600] +Speed=9600 + +# Specifies the size of the LCD. If this driver is loaded as a secondary driver +# it always adopts to the size of the primary driver. If loaded as the only +# (or primary) driver, the size can be set. [default: 40x4] +#Size=16x2 + +# How often to dump the LCD contents out the port, in Hertz (times per second) +# 1 = once per second, 4 is 4 times per second, 0.1 is once every 10 seconds. +# [default: 1; legal: 0.0005 - 10] +UpdateRate=1 + + + +## SDEC driver for Watchguard Firebox ## +[sdeclcd] +# No options + + +## Seiko Epson 1330 driver ## +[sed1330] + +# Port where the LPT is. Common values are 0x278, 0x378 and 0x3BC +Port=0x378 + +# Type of LCD module (legal: G321D, G121C, G242C, G191D, G2446, SP14Q002) +# Note: Currently only tested with G321D & SP14Q002. +Type=G321D + +# Width x Height of a character cell in pixels [legal: 6x7 - 8x16; default: 6x10] +CellSize=6x10 + +# Select what type of connection [legal: classic, bitshaker; default: classic] +ConnectionType=classic + + + +## Seiko Epson 1520 driver ## +[sed1520] + +# Port where the LPT is. Usual values are 0x278, 0x378 and 0x3BC +Port=0x378 + +# Select the interface type (wiring) for the display. Supported values are +# 68 for 68-style connection (RESET level high) and 80 for 80-style connection +# (RESET level low). [legal: 68, 80; default: 80] +InterfaceType=80 + +# On fast machines it may be necessary to slow down transfer to the display. +# If this value is set to zero, delay is disabled. Any value greater than +# zero slows down each write by one microsecond. [legal: 0-1000; default: 1] +DelayMult=0 + +# The original wiring used an inverter to drive the control lines. If you do +# not use an inverter set haveInverter to no. [default: yes; legal: yes, no] +HaveInverter=no + +# On some displays column data in memory is mapped to segment lines from right +# to left. This is called inverted mapping (not to be confused with +# 'haveInverter' from above). [default: no; legal: yes, no] +#InvertedMapping=yes + +# At least one display is reported (Everbouquet MG1203D) that requires sending +# three times 0xFF before a reset during initialization. +# [default: no; legal: yes, no] +#UseHardReset=yes + + +## serial POS display driver ## +[serialPOS] + +# Device to use in serial mode [default: /dev/ttyS0] +Device=/dev/ttyS0 + +# Specifies the size of the display in characters. [default: 16x2] +Size=16x2 + +# Specifies the cell size of each character cell on the display in characters. +# [default: 5x8] +Cellsize=5x8 + +# Specifies the number of custom characters supported by the display. +# [default: 0] +Custom_chars=0 + +# Set the communication protocol to use with the POS display. +# [default: AEDEX; legal: AEDEX, CD5220, Epson, Emax, LogicControls, Ultimate] +Type=AEDEX + +# communication baud rate with the display [default: 9600; legal: 1200, 2400, +# 4800, 9600, 19200, 115200] +Speed=9600 + + + +## Serial VFD driver ## +## Drives various (see below) serial 5x7dot VFD's. ## +[serialVFD] + +# Specifies the displaytype.[default: 0] +# 0 NEC (FIPC8367 based) VFDs. +# 1 KD Rev 2.1. +# 2 Noritake VFDs (*). +# 3 Futaba VFDs +# 4 IEE S03601-95B +# 5 IEE S03601-96-080 (*) +# 6 Futaba NA202SD08FA (allmost IEE compatible) +# 7 Samsung 20S207DA4 and 20S207DA6 +# 8 Nixdorf BA6x / VT100 +# (* most should work, not tested yet.) +Type=0 + +# "no" if display connected serial, "yes" if connected parallel. [default: no] +# I.e. serial by default +use_parallel=no + +# Number of Custom-Characters. default is display type dependent +#Custom-Characters=0 + +# Portaddress where the LPT is. Used in parallel mode only. Usual values are +# 0x278, 0x378 and 0x3BC. +Port=0x378 + +# Set parallel port timing delay (us). Used in parallel mode only. +# [default: 2; legal: 0 - 255] +#PortWait=2 + +# Device to use in serial mode. Usual values are /dev/ttyS0 and /dev/ttyS1 +Device=/dev/ttyS1 + +# Specifies the size of the VFD. +Size=20x2 + +# Set the initial brightness [default: 1000; legal: 0 - 1000] +# (4 steps 0-250, 251-500, 501-750, 751-1000) +Brightness=1000 +# Set the initial off-brightness [default: 0; legal: 0 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +# (4 steps 0-250, 251-500, 501-750, 751-1000) +OffBrightness=0 + +# set the serial port speed [default: 9600; legal: 1200, 2400, 9600, 19200, 115200] +Speed=9600 + +# enable ISO 8859 1 compatibility [default: yes; legal: yes, no] +#ISO_8859_1=yes + + + +## shuttleVFD driver ## +[shuttleVFD] +# No options + + + +## stv5730 driver ## +[stv5730] + +# Port the device is connected to [default: 0x378] +Port=0x378 + + +[SureElec] + +# Port the device is connected to (by default first USB serial port) +Device=/dev/ttyUSB0 + +# Edition level of the device (can be 1, 2 or 3) [default: 2] +#Edition=1 + +# set display size +# Note: The size can be obtained directly from device for edition 2 & 3. +#Size=16x2 + +# Set the initial contrast [default: 480; legal: 0 - 1000] +#Contrast=200 + +# Set the initial brightness [default: 480; legal: 1 - 1000] +#Brightness=480 + +# Set the initial off-brightness [default: 100; legal: 1 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +#OffBrightness=100 + + +## SVGAlib driver ## +[svga] + +# svgalib mode to use [default: G320x240x256 ] +# legal values are supported svgalib modes +#Mode=G640x480x256 + +# set display size [default: 20x4] +Size=20x4 + +# Set the initial contrast [default: 500; legal: 0 - 1000] +# Can be set but does not change anything internally +Contrast=500 + +# Set the initial brightness [default: 1000; legal: 1 - 1000] +Brightness=1000 + +# Set the initial off-brightness [default: 500; legal: 1 - 1000] +# This value is used when the display is normally +# switched off in case LCDd is inactive +OffBrightness=500 + + + +## Text driver ## +[text] +# Set the display size [default: 20x4] +Size=20x4 + + + +## Toshiba T6963 driver ## +[t6963] + +# set display size in pixels [default: 128x64] +Size=128x64 + +# port to use [default: 0x378; legal: 0x200 - 0x400] +Port=0x378 + +# Use LPT port in bi-directional mode. This should work on most LPT port and +# is required for proper timing! [default: yes; legal: yes, no] +#bidirectional=yes + +# Insert additional delays into reads / writes. [default: no; legal: yes, no] +#delayBus=no + +# Clear graphic memory on start-up. [default: no; legal: yes, no] +#ClearGraphic=no + + + +## Tyan Barebones LCD driver (GS10 & GS12 series) ## +[tyan] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/lcd + +# Set the communication speed [default: 9600; legal: 4800, 9600] +Speed=9600 + +# set display size [default: 16x2] +Size=16x2 + + + +## ELV ula200 driver ## +[ula200] + +# Select the LCD size [default: 20x4] +Size=20x4 + +# If you have a non standard keypad you can associate any keystrings to keys. +# There are 6 input key in the CwLnx hardware that generate characters +# from 'A' to 'F'. +# +# The following it the built-in default mapping hardcoded in the driver. +# You can leave those unchanged if you have a standard keypad. +# You can change it if you want to report other keystrings or have a non +# standard keypad. +# KeyMap_A=Up +# KeyMap_B=Down +# KeyMap_C=Left +# KeyMap_D=Right +# KeyMap_E=Enter +# KeyMap_F=Escape + + + +## Wirz SLI LCD driver ## +[sli] + +# Select the output device to use [default: /dev/lcd] +Device=/dev/lcd + +# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200, +# 38400, 57600, 115200] +Speed=19200 + + + +## vlsys_m428 for VFD/IR combination in Moneual MonCaso 320 ## +[vlsys_m428] + +# Select the output device to use [default: /dev/ttyUSB0] +#Device=/dev/ttyUSB0 + + + +## OnScreen Display using libxosd ## +[xosd] + +# set display size [default: 20x4] +Size=20x4 + +# Offset in pixels from the top-left corner of the monitor [default: 0x0] +Offset=200x200 + +# X font to use, in XLFD format, as given by "xfontsel" +Font=-*-terminus-*-r-*-*-*-320-*-*-*-*-* + +## Y.A.R.D.2 LCD section +[yard2LCD] +Size=20x4 +# If rendering rate is too high, change in server\main.h #define RENDER_FREQ 8 to "1" + +# EOF diff --git a/data/templates/system-display/lcdproc.conf.tmpl b/data/templates/system-display/lcdproc.conf.tmpl new file mode 100644 index 000000000..2fe491e63 --- /dev/null +++ b/data/templates/system-display/lcdproc.conf.tmpl @@ -0,0 +1,177 @@ +### autogenerated by system-display.py ### + +# system display show host (CPU|SMP-CPU|CPU-Graph|Load|Memory|Proc-Size|Disk|Uptime) +# network interface alias +# units (bps|Bps|pps) +# clock (big|mini|date-time) + +# LCDproc client configuration file + +## general options ## +[lcdproc] +# address of the LCDd server to connect to +Server=127.0.0.1 + +# Port of the server to connect to +Port=13666 + +# set reporting level +#ReportLevel=2 + +# report to to syslog ? +ReportToSyslog=true + +# run in foreground [default: false; legal: true, false] +#Foreground=true + +# PidFile location when running as daemon [default: /var/run/lcdproc.pid] +#PidFile=/var/run/lcdproc.pid + +# slow down initial announcement of modes (in 1/100s) +#delay=2 + + +## screen specific configuration options ## +{%- if show %} +# display name for the main menu [default: LCDproc HOST] +DisplayName="{%- if show['title'] %}{{ show['title'] }}{%- else %}VyOS{%- endif %}" + +{%- if show['host'] %} +{#%- for s in show['host'] %#} + +[CPU] +# Show screen +Active={%- if 'cpu' in show['host'] %}true{%- else %}false{%- endif %} +OnTime=1 +OffTime=2 +ShowInvisible=false + +[SMP-CPU] +# Show screen +Active={%- if 'cpu-all' in show['host'] %}true{%- else %}false{%- endif %} + +[Memory] +# Show screen +Active={%- if 'memory' in show['host'] %}true{%- else %}false{%- endif %} + +[Load] +# Show screen +Active={%- if 'load-hist' in show['host'] %}true{%- else %}false{%- endif %} +# Min Load Avg at which the backlight will be turned off [default: 0.05] +LowLoad=0.05 +# Max Load Avg at which the backlight will start blinking [default: 1.3] +HighLoad=1.3 + +[Uptime] +# Show screen +Active={%- if 'uptime' in show['host'] %}true{%- else %}false{%- endif %} + +[CPUGraph] +# Show screen +Active={%- if 'cpu-hist' in show['host'] %}true{%- else %}false{%- endif %} + +[ProcSize] +# Show screen +Active={%- if 'proc' in show['host'] %}true{%- else %}false{%- endif %} + +[Disk] +# Show screen +Active={%- if 'disk' in show['host'] %}true{%- else %}false{%- endif %} +{#%- endfor %#} +{%- else %} {# if show['host'] #} +{# Turn off sections that default active #} + +[CPU] +Active=false + +[Memory] +Active=false + +[Load] +Active=false + +[TimeDate] +Active=false +{%- endif %} {# if show['host'] #} + +[TimeDate] +# Show screen +Active={%- if show['clock'] == 'date-time' %}true{%- else %}false{%- endif %} +# time format [default: %H:%M:%S; legal: see strftime(3)] +TimeFormat="%H:%M:%S" +# date format [default: %x; legal: see strftime(3)] +DateFormat="%x" + +[BigClock] +# Show screen +Active={%- if show['clock'] == 'big' %}true{%- else %}false{%- endif %} + +[MiniClock] +# Show screen +Active={%- if show['clock'] == 'mini' %}true{%- else %}false{%- endif %} +# time format [default: %H:%M; legal: see strftime(3)] +TimeFormat="%H:%M" + +{%- if show['network'] %} +[Iface] +# Show screen +Active={%- if show['network']['interface'] %}true{%- else %}false{%- endif %} +{%- for i in show['network']['interface'] %} +# Show stats for Interface {{ i }} +Interface0={{ i }} +{%- if show['network']['interface'][i]['alias'] %} +# Interface alias name to display [default: ] +Alias0={{ show['network']['interface'][i]['alias'] }} +{%- endif %} +{%- endfor %} + +# Units to display [default: byte; legal: byte, bit, packet] +{%- if show['network']['units'] == 'bps' %} +unit=bit +{%- elif show['network']['units'] == 'Bps'%} +unit=byte +{%- elif show['network']['units'] == 'pps' %} +unit=packet +{%- else %} +unit=bit +{%- endif %} +# add screen with transferred traffic +#transfer=TRUE +{%- endif %} {# if show['network'] #} + +{%- else %} {# if show #} +{# Turn off sections that default active #} + +[CPU] +Active=false + +[Memory] +Active=false + +[Load] +Active=false + +[TimeDate] +Active=false +{%- endif %} + +[Battery] +# Show screen +Active=false + +[About] +# Show screen +Active=false + +[OldTime] +# Show screen +Active=false +# time format [default: %H:%M:%S; legal: see strftime(3)] +TimeFormat="%H:%M:%S" +# date format [default: %x; legal: see strftime(3)] +DateFormat="%x" +# Display the title bar in two-line mode. Note that with four lines or more +# the title is always shown. [default: true; legal: true, false] +#ShowTitle=false + +# EOF diff --git a/interface-definitions/system-display.xml.in b/interface-definitions/system-display.xml.in new file mode 100644 index 000000000..7a5cf9283 --- /dev/null +++ b/interface-definitions/system-display.xml.in @@ -0,0 +1,245 @@ + + + + + + + + System display LCD/VFD/LED + 400 + + + + + + Model of the display attached to this system [REQUIRED] + + SDEC EZIO + + + (SDEC|EZIO) + + Invalid system display model + + SDEC + Display model for Lanner, Watchguard, Nexcom NSA, Sophos UTM appliances with built-in SDEC LCD + + + EZIO + Display model for Portwell, Caswell appliances with built-in EZIO-100 or EZIO-300 LCD + + + + + + + Configuration status + + enabled disabled + + + (enabled|disabled) + + Invalid system display config status + + enabled + Turn system display configuration ON + + + disabled + Turn system display configuration OFF + + + + + + Select the screens for the system display [REQUIRED] + + + + + + + Select host screens for the system display + + cpu cpu-all cpu-hist disk load-hist memory proc uptime + + + (cpu|cpu-all|cpu-hist|disk|load-hist|memory|proc|uptime) + + Invalid host screen + + cpu + Detailed CPU usage + + + cpu-all + CPU usage overview (one line per CPU) + + + cpu-hist + CPU usage histogram + + + disk + File systems fill level + + + load-hist + Load histogram + + + memory + Memory and swap usage + + + proc + Top processes by size + + + uptime + System uptime + + + + + + + Network settings for system display + + + + + + Show network traffic on the system display [Max 3 interfaces] + + + + + + + + Interface alias + + [A-Za-z0-9]{1,10} + + Invalid alias, must be 1 to 10 char or digit + + + + + + + + Unit for network details + + bps Bps pps + + + (bps|Bps|pps) + + Invalid network detail unit + + bps + Bit(s) per second + + + Bps + Byte(s) per second + + + pps + Packet(s) per second + + + + + + + + + + Show a clock on the system display + + big mini date-time + + + (big|mini|date-time) + + Invalid clock format + + big + Multi-line clock + + + mini + Minimal clock + + + date-time + Clock with Date and Time + + + + + + + Screen title to show on the system display + + [A-Za-z0-9]{1,16} + + Invalid title, must be 1 to 16 char or digit + + + + + + + + Time in sec to show each screen on the system display + + 1-30 + Numer of seconds + + + + + + + + + + Message to show when system display first starts + + .{1,16} + + Hello message must be 1 to 16 char + + + + + + Message to show when system display stops + + .{1,16} + + Bye message must be 1 to 16 char + + + + + + + diff --git a/src/conf_mode/system-display.py b/src/conf_mode/system-display.py new file mode 100755 index 000000000..4db375d8e --- /dev/null +++ b/src/conf_mode/system-display.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 Francois Mertz fireboxled at 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 . + +import os +import re + +from sys import exit + +from vyos.config import Config +from vyos import ConfigError +from vyos.util import run +from vyos.template import render + +from vyos import airbag +airbag.enable() + +def get_config(): + c = Config() + + if not c.exists('system display'): + return None + + c.set_level('system display') + + return c.get_config_dict([]) + +def generate(c): + if c == None: + return None + # Render config file for daemon LCDd + render('/etc/LCDd.conf', 'system-display/LCDd.conf.tmpl', c) + # Render config file for client lcdproc + render('/etc/lcdproc.conf', 'system-display/lcdproc.conf.tmpl', c) + + return None + +def verify(c): + if c == None: + return None + + if c.get('model') == None: + raise ConfigError('For system display, a model is [REQUIRED]') + + if c.get('show') == None: + raise ConfigError('For system display, show cannot be empty') + + if 'network' in c['show'] and 'interface' not in c['show']['network']: + raise ConfigError('system display show network must have at least one interface') + + if 'network' in c['show'] and 'interface' in c['show']['network'] and len(c['show']['network']['interface']) > 3: + raise ConfigError('system display show network interface cannot have more than 3 entries') + + return None + +def apply(c): + if not c or c['config'] == 'disabled': + # Stop client first + run('systemctl stop lcdproc.service') + # Stop server next + return run('systemctl stop LCDd.service') + + # Stop client first + run('systemctl stop lcdproc.service') + # Restart server next + run('systemctl restart LCDd.service') + # Start client + run('systemctl start lcdproc.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/systemd/lcdproc.service b/src/systemd/lcdproc.service new file mode 100644 index 000000000..ea9178de9 --- /dev/null +++ b/src/systemd/lcdproc.service @@ -0,0 +1,10 @@ +[Unit] +Description=LCDproc system status information viewer +Documentation=man:lcdproc(8) http://www.lcdproc.org/ + +[Service] +User=root +ExecStart=/usr/bin/lcdproc -f -c /etc/lcdproc.conf + +[Install] +WantedBy=multi-user.target \ No newline at end of file -- cgit v1.2.3 From 3e8b6841db6ab803b898376754b469954d212cf7 Mon Sep 17 00:00:00 2001 From: fmertz Date: Sun, 28 Jun 2020 18:27:11 +0000 Subject: system display: T2564: Lowercase model names * data/templates/system-display/LCDd.conf.tmpl Lowercase sdec ezio * interface-definitions/system-display.xml.in Lowercase sdec ezio --- data/templates/system-display/LCDd.conf.tmpl | 6 +++--- interface-definitions/system-display.xml.in | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/data/templates/system-display/LCDd.conf.tmpl b/data/templates/system-display/LCDd.conf.tmpl index 0b605e09d..8079dcb76 100644 --- a/data/templates/system-display/LCDd.conf.tmpl +++ b/data/templates/system-display/LCDd.conf.tmpl @@ -52,11 +52,11 @@ DriverPath=/usr/lib/x86_64-linux-gnu/lcdproc/ # Olimex_MOD_LCD1x9, picolcd, pyramid, rawserial, sdeclcd, sed1330, # sed1520, serialPOS, serialVFD, shuttleVFD, sli, stv5730, svga, t6963, # text, tyan, ula200, vlsys_m428, xosd, yard2LCD -{%- if model == 'SDEC' %} +{%- if model == 'sdec' %} Driver=sdeclcd {%- endif %} -{%- if model == 'EZIO' %} +{%- if model == 'ezio' %} Driver=hd44780 {%- endif %} @@ -544,7 +544,7 @@ Speed=19200 ## Hitachi HD44780 driver ## [hd44780] -{%- if model == 'EZIO' %} +{%- if model == 'ezio' %} ConnectionType=ezio Device=/dev/ttyS1 Keypad=yes diff --git a/interface-definitions/system-display.xml.in b/interface-definitions/system-display.xml.in index 7a5cf9283..ac9f17b6c 100644 --- a/interface-definitions/system-display.xml.in +++ b/interface-definitions/system-display.xml.in @@ -1,7 +1,7 @@ + Model of the display attached to this system [REQUIRED] - SDEC EZIO + sdec ezio - (SDEC|EZIO) + (sdec|ezio) Invalid system display model - SDEC + sdec Display model for Lanner, Watchguard, Nexcom NSA, Sophos UTM appliances with built-in SDEC LCD - EZIO + ezio Display model for Portwell, Caswell appliances with built-in EZIO-100 or EZIO-300 LCD -- cgit v1.2.3 From f3e0a409ee01e1b3ffa0c9a267434a7725f61231 Mon Sep 17 00:00:00 2001 From: fmertz Date: Sun, 28 Jun 2020 20:36:19 +0000 Subject: system display: T2564: Replace "config (enabled|disabled)" with "display disabled" * interface-definitions/system-display.xml.in system disply disabled * src/conf_mode/system-display.py system display disabled --- interface-definitions/system-display.xml.in | 26 ++++++-------------------- src/conf_mode/system-display.py | 2 +- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/interface-definitions/system-display.xml.in b/interface-definitions/system-display.xml.in index ac9f17b6c..afa6dba10 100644 --- a/interface-definitions/system-display.xml.in +++ b/interface-definitions/system-display.xml.in @@ -2,7 +2,6 @@ @@ -42,27 +42,13 @@ - - + + - Configuration status - - enabled disabled - - - (enabled|disabled) - - Invalid system display config status - - enabled - Turn system display configuration ON - - - disabled - Turn system display configuration OFF - + Disable sytem display + - + Select the screens for the system display [REQUIRED] diff --git a/src/conf_mode/system-display.py b/src/conf_mode/system-display.py index 4db375d8e..e30d591cc 100755 --- a/src/conf_mode/system-display.py +++ b/src/conf_mode/system-display.py @@ -66,7 +66,7 @@ def verify(c): return None def apply(c): - if not c or c['config'] == 'disabled': + if not c or 'disabled' in c.keys(): # Stop client first run('systemctl stop lcdproc.service') # Stop server next -- cgit v1.2.3 From 440d0fb1115bbfb428132ef91804ae213ffca30f Mon Sep 17 00:00:00 2001 From: fmertz Date: Sun, 28 Jun 2020 21:21:27 +0000 Subject: system display: T2564: py code cleanup * src/conf_mode/system-display.py cleanup use of dictionary * data/templates/system-display/lcdproc.conf.tmpl cleanup, removed extra DateTime --- data/templates/system-display/lcdproc.conf.tmpl | 12 ++-- src/conf_mode/system-display.py | 75 +++++++++++++------------ 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/data/templates/system-display/lcdproc.conf.tmpl b/data/templates/system-display/lcdproc.conf.tmpl index 2fe491e63..92aee8efe 100644 --- a/data/templates/system-display/lcdproc.conf.tmpl +++ b/data/templates/system-display/lcdproc.conf.tmpl @@ -37,7 +37,6 @@ ReportToSyslog=true DisplayName="{%- if show['title'] %}{{ show['title'] }}{%- else %}VyOS{%- endif %}" {%- if show['host'] %} -{#%- for s in show['host'] %#} [CPU] # Show screen @@ -77,7 +76,6 @@ Active={%- if 'proc' in show['host'] %}true{%- else %}false{%- endif %} [Disk] # Show screen Active={%- if 'disk' in show['host'] %}true{%- else %}false{%- endif %} -{#%- endfor %#} {%- else %} {# if show['host'] #} {# Turn off sections that default active #} @@ -90,8 +88,6 @@ Active=false [Load] Active=false -[TimeDate] -Active=false {%- endif %} {# if show['host'] #} [TimeDate] @@ -118,10 +114,10 @@ TimeFormat="%H:%M" Active={%- if show['network']['interface'] %}true{%- else %}false{%- endif %} {%- for i in show['network']['interface'] %} # Show stats for Interface {{ i }} -Interface0={{ i }} +Interface{{ loop.index0 }}={{ i }} {%- if show['network']['interface'][i]['alias'] %} # Interface alias name to display [default: ] -Alias0={{ show['network']['interface'][i]['alias'] }} +Alias{{ loop.index0 }}={{ show['network']['interface'][i]['alias'] }} {%- endif %} {%- endfor %} @@ -139,7 +135,7 @@ unit=bit #transfer=TRUE {%- endif %} {# if show['network'] #} -{%- else %} {# if show #} +{%- else %}{# if show #} {# Turn off sections that default active #} [CPU] @@ -153,7 +149,7 @@ Active=false [TimeDate] Active=false -{%- endif %} +{%- endif %}{# if show #} [Battery] # Show screen diff --git a/src/conf_mode/system-display.py b/src/conf_mode/system-display.py index e30d591cc..557b7cf6a 100755 --- a/src/conf_mode/system-display.py +++ b/src/conf_mode/system-display.py @@ -15,7 +15,6 @@ # along with this program. If not, see . import os -import re from sys import exit @@ -28,53 +27,57 @@ from vyos import airbag airbag.enable() def get_config(): - c = Config() + # Return a (possibly empty) configuration dictionary + return Config().get_config_dict(['system', 'display']) - if not c.exists('system display'): - return None - - c.set_level('system display') - - return c.get_config_dict([]) - -def generate(c): - if c == None: +def generate(config_dict): + if not config_dict: return None # Render config file for daemon LCDd - render('/etc/LCDd.conf', 'system-display/LCDd.conf.tmpl', c) + render('/etc/LCDd.conf', 'system-display/LCDd.conf.tmpl', config_dict) # Render config file for client lcdproc - render('/etc/lcdproc.conf', 'system-display/lcdproc.conf.tmpl', c) + render('/etc/lcdproc.conf', 'system-display/lcdproc.conf.tmpl', config_dict) return None -def verify(c): - if c == None: +def verify(config_dict): + if not config_dict: return None - if c.get('model') == None: - raise ConfigError('For system display, a model is [REQUIRED]') + if 'model' not in config_dict: + raise ConfigError('Display model is [REQUIRED]') - if c.get('show') == None: - raise ConfigError('For system display, show cannot be empty') + if ( 'show' not in config_dict + or ( 'clock' not in config_dict['show'] + and 'network' not in config_dict['show'] + and 'host' not in config_dict['show'] + ) + ): + raise ConfigError('Display show must have a clock, host or network') - if 'network' in c['show'] and 'interface' not in c['show']['network']: - raise ConfigError('system display show network must have at least one interface') + if ( 'network' in config_dict['show'] + and 'interface' not in config_dict['show']['network'] + ): + raise ConfigError('Display show network must have an interface') - if 'network' in c['show'] and 'interface' in c['show']['network'] and len(c['show']['network']['interface']) > 3: - raise ConfigError('system display show network interface cannot have more than 3 entries') + if ( 'network' in config_dict['show'] + and 'interface' in config_dict['show']['network'] + and len(config_dict['show']['network']['interface']) > 3 + ): + raise ConfigError('Display show network cannot have > 3 interfaces') return None -def apply(c): - if not c or 'disabled' in c.keys(): - # Stop client first - run('systemctl stop lcdproc.service') - # Stop server next - return run('systemctl stop LCDd.service') - - # Stop client first +def apply(config_dict): + # Stop client run('systemctl stop lcdproc.service') - # Restart server next + + if not config_dict or 'disabled' in config_dict: + # Stop server + run('systemctl stop LCDd.service') + return None + + # Restart server run('systemctl restart LCDd.service') # Start client run('systemctl start lcdproc.service') @@ -83,10 +86,10 @@ def apply(c): if __name__ == '__main__': try: - c = get_config() - verify(c) - generate(c) - apply(c) + config_dict = get_config() + verify(config_dict) + generate(config_dict) + apply(config_dict) except ConfigError as e: print(e) exit(1) -- cgit v1.2.3 From d4199a17adeac422882013a8951d18b7c3b74f2a Mon Sep 17 00:00:00 2001 From: fmertz Date: Mon, 29 Jun 2020 14:45:57 +0000 Subject: system display: T2564: Changed "duration" to "time" * data/templates/system-display/LCDd.conf.tmpl duration to time * interface-definitions/system-display.xml.in duration to time --- data/templates/system-display/LCDd.conf.tmpl | 2 +- interface-definitions/system-display.xml.in | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/templates/system-display/LCDd.conf.tmpl b/data/templates/system-display/LCDd.conf.tmpl index 8079dcb76..0ef796c76 100644 --- a/data/templates/system-display/LCDd.conf.tmpl +++ b/data/templates/system-display/LCDd.conf.tmpl @@ -92,7 +92,7 @@ GoodBye="{%- if bye %}{{ bye }}{%- else %}Bye from VyOS{%- endif %}" #FrameInterval=125000 # Sets the default time in seconds to displays a screen. [default: 4] -WaitTime={%- if duration %}{{ duration }}{%- else%}4{%- endif %} +WaitTime={%- if time %}{{ time }}{%- else%}4{%- endif %} # If set to no, LCDd will start with screen rotation disabled. This has the # same effect as if the ToggleRotateKey had been pressed. Rotation will start diff --git a/interface-definitions/system-display.xml.in b/interface-definitions/system-display.xml.in index afa6dba10..0439288a0 100644 --- a/interface-definitions/system-display.xml.in +++ b/interface-definitions/system-display.xml.in @@ -8,7 +8,7 @@ clock (big|mini|date-time) title - system display duration + system display time system display hello system display bye system display disabled @@ -191,8 +191,8 @@ - - + + Time in sec to show each screen on the system display @@ -203,7 +203,7 @@ - + -- cgit v1.2.3 From 14e0a2339451492e226869cb977ab858bb1e7f17 Mon Sep 17 00:00:00 2001 From: fmertz Date: Thu, 2 Jul 2020 05:08:20 +0000 Subject: system display: T2564: Conf files to /run * src/conf_mode/system-display.py conf files to /run + src/systemd/LCDd@.service lo - src/systemd/lcdproc.service + src/systemd/lcdproc@.service lo --- src/conf_mode/system-display.py | 12 ++++++------ src/systemd/LCDd@.service | 10 ++++++++++ src/systemd/lcdproc.service | 10 ---------- src/systemd/lcdproc@.service | 10 ++++++++++ 4 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 src/systemd/LCDd@.service delete mode 100644 src/systemd/lcdproc.service create mode 100644 src/systemd/lcdproc@.service diff --git a/src/conf_mode/system-display.py b/src/conf_mode/system-display.py index 557b7cf6a..9ab35a331 100755 --- a/src/conf_mode/system-display.py +++ b/src/conf_mode/system-display.py @@ -34,9 +34,9 @@ def generate(config_dict): if not config_dict: return None # Render config file for daemon LCDd - render('/etc/LCDd.conf', 'system-display/LCDd.conf.tmpl', config_dict) + render('/run/LCDd/LCDd.lo.conf', 'system-display/LCDd.conf.tmpl', config_dict) # Render config file for client lcdproc - render('/etc/lcdproc.conf', 'system-display/lcdproc.conf.tmpl', config_dict) + render('/run/lcdproc/lcdproc.lo.conf', 'system-display/lcdproc.conf.tmpl', config_dict) return None @@ -70,17 +70,17 @@ def verify(config_dict): def apply(config_dict): # Stop client - run('systemctl stop lcdproc.service') + run('systemctl stop lcdproc@lo.service') if not config_dict or 'disabled' in config_dict: # Stop server - run('systemctl stop LCDd.service') + run('systemctl stop LCDd@lo.service') return None # Restart server - run('systemctl restart LCDd.service') + run('systemctl restart LCDd@lo.service') # Start client - run('systemctl start lcdproc.service') + run('systemctl start lcdproc@lo.service') return None diff --git a/src/systemd/LCDd@.service b/src/systemd/LCDd@.service new file mode 100644 index 000000000..a4604cf21 --- /dev/null +++ b/src/systemd/LCDd@.service @@ -0,0 +1,10 @@ +[Unit] +Description=LCD display daemon on %I +Documentation=man:LCDd(8) http://www.lcdproc.org/ + +[Service] +User=root +ExecStart=/usr/sbin/LCDd -s 1 -f -c /run/LCDd/LCDd.%I.conf + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/src/systemd/lcdproc.service b/src/systemd/lcdproc.service deleted file mode 100644 index ea9178de9..000000000 --- a/src/systemd/lcdproc.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=LCDproc system status information viewer -Documentation=man:lcdproc(8) http://www.lcdproc.org/ - -[Service] -User=root -ExecStart=/usr/bin/lcdproc -f -c /etc/lcdproc.conf - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/src/systemd/lcdproc@.service b/src/systemd/lcdproc@.service new file mode 100644 index 000000000..9a1723dba --- /dev/null +++ b/src/systemd/lcdproc@.service @@ -0,0 +1,10 @@ +[Unit] +Description=LCDproc system status information viewer on %I +Documentation=man:lcdproc(8) http://www.lcdproc.org/ + +[Service] +User=root +ExecStart=/usr/bin/lcdproc -f -c /run/lcdproc/lcdproc.%I.conf + +[Install] +WantedBy=multi-user.target \ No newline at end of file -- cgit v1.2.3 From 48ddea3755c72db55e5366997a82d725e106c3aa Mon Sep 17 00:00:00 2001 From: fmertz Date: Sun, 5 Jul 2020 15:01:21 +0000 Subject: system display: T2564: Dictionary code update * src/conf_mode/system-display.py added get_first_key to dictionary call --- src/conf_mode/system-display.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/conf_mode/system-display.py b/src/conf_mode/system-display.py index 9ab35a331..3eafc30c0 100755 --- a/src/conf_mode/system-display.py +++ b/src/conf_mode/system-display.py @@ -27,18 +27,11 @@ from vyos import airbag airbag.enable() def get_config(): + conf = Config() + base = ['system', 'display'] + display = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) # Return a (possibly empty) configuration dictionary - return Config().get_config_dict(['system', 'display']) - -def generate(config_dict): - if not config_dict: - return None - # Render config file for daemon LCDd - render('/run/LCDd/LCDd.lo.conf', 'system-display/LCDd.conf.tmpl', config_dict) - # Render config file for client lcdproc - render('/run/lcdproc/lcdproc.lo.conf', 'system-display/lcdproc.conf.tmpl', config_dict) - - return None + return display def verify(config_dict): if not config_dict: @@ -68,6 +61,16 @@ def verify(config_dict): return None +def generate(config_dict): + if not config_dict: + return None + # Render config file for daemon LCDd + render('/run/LCDd/LCDd.lo.conf', 'system-display/LCDd.conf.tmpl', config_dict) + # Render config file for client lcdproc + render('/run/lcdproc/lcdproc.lo.conf', 'system-display/lcdproc.conf.tmpl', config_dict) + + return None + def apply(config_dict): # Stop client run('systemctl stop lcdproc@lo.service') -- cgit v1.2.3 From b5e0a1796cc7887245ae79c759f709e1705f5e93 Mon Sep 17 00:00:00 2001 From: fmertz Date: Sun, 5 Jul 2020 15:58:33 +0000 Subject: system display: T2564: Added test model * data/templates/system-display/LCDd.conf.tmpl * interface-definitions/system-display.xml.in May need to ba backed out, test code only for USB CrystalFontz CF533 --- data/templates/system-display/LCDd.conf.tmpl | 45 +++++++--------------------- interface-definitions/system-display.xml.in | 10 +++++-- 2 files changed, 17 insertions(+), 38 deletions(-) diff --git a/data/templates/system-display/LCDd.conf.tmpl b/data/templates/system-display/LCDd.conf.tmpl index 0ef796c76..1dd646202 100644 --- a/data/templates/system-display/LCDd.conf.tmpl +++ b/data/templates/system-display/LCDd.conf.tmpl @@ -60,6 +60,10 @@ Driver=sdeclcd Driver=hd44780 {%- endif %} +{%- if model == 'test' %} +Driver=CFontzPacket +{%- endif %} + # Tells the driver to bind to the given interface. [default: 127.0.0.1] #Bind=127.0.0.1 @@ -191,44 +195,15 @@ Reboot=no ## CrystalFontz packet driver (for CFA533, CFA631, CFA633 & CFA635) ## [CFontzPacket] - -# Select the LCD model [default: 633; legal: 533, 631, 633, 635] -Model=633 - -# Select the output device to use [default: /dev/lcd] -Device=/dev/ttyS1 - -# Set the initial contrast [default: 560; legal: 0 - 1000] +{%- if model == 'test' %} +Model=533 +Device=/dev/serial/by-bus/usb0b1.1p1.0 Contrast=350 - -# Set the initial brightness [default: 1000; legal: 0 - 1000] Brightness=1000 - -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive OffBrightness=50 - -# Reinitialize the LCD's BIOS on driver start. [default: no; legal: yes, no] Reboot=yes - -# Enable the USB flag if the device is connected to an USB port. For -# serial ports leave it disabled. [default: no; legal: yes, no] -#USB=yes - -# Very old 633 firmware versions do not support partial screen updates using -# 'Send Data to LCD' command (31). For those devices it may be necessary to -# enable this flag. [default: no; legal: yes, no] -#OldFirmware=yes - -# Override the LCD size known for the selected model. Usually setting this -# value should not be necessary. -#Size=20x4 - -# Override the default communication speed known for the selected model. -# Default value depends on model [legal: 19200, 115200] -#Speed=115200 - +USB=yes +{%- endif %} ## Curses driver ## @@ -546,7 +521,7 @@ Speed=19200 [hd44780] {%- if model == 'ezio' %} ConnectionType=ezio -Device=/dev/ttyS1 +Device=/dev/ttyUSB0 Keypad=yes Size=16x2 KeyMatrix_4_1=Enter diff --git a/interface-definitions/system-display.xml.in b/interface-definitions/system-display.xml.in index 0439288a0..fbd897996 100644 --- a/interface-definitions/system-display.xml.in +++ b/interface-definitions/system-display.xml.in @@ -1,7 +1,7 @@ -- cgit v1.2.3 From 2cea49cef987e5c6e73744d310857a2e592e409e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 9 Jul 2020 21:46:35 +0200 Subject: ssh: loglevel is now lowercase --- scripts/cli/test_service_ssh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cli/test_service_ssh.py b/scripts/cli/test_service_ssh.py index 072984ca8..ad56fb53b 100755 --- a/scripts/cli/test_service_ssh.py +++ b/scripts/cli/test_service_ssh.py @@ -51,7 +51,7 @@ class TestServiceSSH(unittest.TestCase): self.session.set(base_path + ['port', '1234']) self.session.set(base_path + ['disable-host-validation']) self.session.set(base_path + ['disable-password-authentication']) - self.session.set(base_path + ['loglevel', 'VERBOSE']) + self.session.set(base_path + ['loglevel', 'verbose']) self.session.set(base_path + ['client-keepalive-interval', '100']) self.session.set(base_path + ['listen-address', '127.0.0.1']) -- cgit v1.2.3 From 10b5196ddfb9a83b17f90f93524ea077275a8557 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 11 Jul 2020 18:03:36 +0200 Subject: snmp: adjust to latest CLI version --- scripts/cli/test_service_snmp.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/cli/test_service_snmp.py b/scripts/cli/test_service_snmp.py index d4bbdc0b1..983eadf4c 100755 --- a/scripts/cli/test_service_snmp.py +++ b/scripts/cli/test_service_snmp.py @@ -84,7 +84,7 @@ class TestSNMPService(unittest.TestCase): def test_snmpv3(self): """ Check if SNMPv3 can be configured and service runs""" - self.session.set(base_path + ['v3', 'engineid', '0xaffedeadbeef']) + self.session.set(base_path + ['v3', 'engineid', '000000000000000000000002']) self.session.set(base_path + ['v3', 'group', 'default', 'mode', 'ro']) # check validate() - a view must be created before this can be comitted with self.assertRaises(ConfigSessionError): @@ -95,9 +95,10 @@ class TestSNMPService(unittest.TestCase): self.session.commit() # create user - for authpriv in ['auth', 'privacy']: - self.session.set(base_path + ['v3', 'user', 'vyos', authpriv, 'plaintext-key', 'vyos1234']) - + self.session.set(base_path + ['v3', 'user', 'vyos', 'auth', 'plaintext-password', 'vyos12345678']) + self.session.set(base_path + ['v3', 'user', 'vyos', 'auth', 'type', 'sha']) + self.session.set(base_path + ['v3', 'user', 'vyos', 'privacy', 'plaintext-password', 'vyos12345678']) + self.session.set(base_path + ['v3', 'user', 'vyos', 'privacy', 'type', 'aes']) self.session.set(base_path + ['v3', 'user', 'vyos', 'group', 'default']) # TODO: read in config file and check values -- cgit v1.2.3 From 9ac84f533b9dfd80dbf00708ac1f58d260bfdfa7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 11 Jul 2020 20:22:13 +0200 Subject: snmp: test that encrypted-password is properly inserted into CLI --- scripts/cli/test_service_snmp.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/cli/test_service_snmp.py b/scripts/cli/test_service_snmp.py index 983eadf4c..f4f20a3cb 100755 --- a/scripts/cli/test_service_snmp.py +++ b/scripts/cli/test_service_snmp.py @@ -92,7 +92,6 @@ class TestSNMPService(unittest.TestCase): self.session.set(base_path + ['v3', 'view', 'default', 'oid', '1']) self.session.set(base_path + ['v3', 'group', 'default', 'view', 'default']) - self.session.commit() # create user self.session.set(base_path + ['v3', 'user', 'vyos', 'auth', 'plaintext-password', 'vyos12345678']) @@ -101,6 +100,16 @@ class TestSNMPService(unittest.TestCase): self.session.set(base_path + ['v3', 'user', 'vyos', 'privacy', 'type', 'aes']) self.session.set(base_path + ['v3', 'user', 'vyos', 'group', 'default']) + self.session.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', 'vyos', 'auth', 'encrypted-password']).split()[1] + self.assertEqual(tmp, hashed_password) + + tmp = self.session.show_config(base_path + ['v3', 'user', 'vyos', 'privacy', 'encrypted-password']).split()[1] + self.assertEqual(tmp, hashed_password) + # TODO: read in config file and check values # Check for running process -- cgit v1.2.3 From f2c05556b051126b9bc676f3427114e9889f3b61 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Jul 2020 12:34:33 +0200 Subject: nat: initial simple test --- scripts/cli/test_nat.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100755 scripts/cli/test_nat.py diff --git a/scripts/cli/test_nat.py b/scripts/cli/test_nat.py new file mode 100755 index 000000000..fdc1ba7de --- /dev/null +++ b/scripts/cli/test_nat.py @@ -0,0 +1,63 @@ +#!/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 . + +import os +import jmespath +import json +import unittest + +from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.util import cmd + +base_path = ['nat'] +snat_pattern = 'nftables[?rule].rule[?chain].{chain: chain, comment: comment, address: { network: expr[].match.right.prefix.addr | [0], prefix: expr[].match.right.prefix.len | [0]}}' + +class TestNAT(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + self.session.delete(base_path) + + def tearDown(self): + del self.session + + def test_source_nat(self): + """ Check if SNMP can be configured and service runs """ + + path = base_path + ['source'] + network = '192.168.0.0/16' + self.session.set(path + ['rule', '1', 'destination', 'address', network]) + self.session.set(path + ['rule', '1', 'exclude']) + + # check validate() - outbound-interface must be defined + with self.assertRaises(ConfigSessionError): + self.session.commit() + + self.session.set(path + ['rule', '1', 'outbound-interface', 'any']) + self.session.commit() + + tmp = cmd('sudo nft -j list table nat') + nftable_json = json.loads(tmp) + condensed_json = jmespath.search(snat_pattern, nftable_json)[0] + + self.assertEqual(condensed_json['comment'], 'DST-NAT-1') + self.assertEqual(condensed_json['address']['network'], network.split('/')[0]) + self.assertEqual(str(condensed_json['address']['prefix']), network.split('/')[1]) + +if __name__ == '__main__': + unittest.main() + -- cgit v1.2.3 From 497690e812d820c42d56c642358da310eb9459ab Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Jul 2020 13:08:08 +0200 Subject: nat: delete config after test finishes --- scripts/cli/test_nat.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/cli/test_nat.py b/scripts/cli/test_nat.py index fdc1ba7de..416810e40 100755 --- a/scripts/cli/test_nat.py +++ b/scripts/cli/test_nat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# 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 @@ -27,16 +27,17 @@ snat_pattern = 'nftables[?rule].rule[?chain].{chain: chain, comment: comment, ad class TestNAT(unittest.TestCase): def setUp(self): - self.session = ConfigSession(os.getpid()) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) + self.session = ConfigSession(os.getpid()) self.session.delete(base_path) def tearDown(self): - del self.session + self.session.delete(base_path) + self.session.commit() def test_source_nat(self): - """ Check if SNMP can be configured and service runs """ + """ Configure and validate source NAT rule(s) """ path = base_path + ['source'] network = '192.168.0.0/16' @@ -60,4 +61,3 @@ class TestNAT(unittest.TestCase): if __name__ == '__main__': unittest.main() - -- cgit v1.2.3 From 01d7ff6ad2919775e6813eeafdcf2f5bb860a1b0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Jul 2020 14:55:11 +0200 Subject: snmp: add v3 test with MD5 keys --- scripts/cli/test_service_snmp.py | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/scripts/cli/test_service_snmp.py b/scripts/cli/test_service_snmp.py index f4f20a3cb..fb5f5393f 100755 --- a/scripts/cli/test_service_snmp.py +++ b/scripts/cli/test_service_snmp.py @@ -81,8 +81,8 @@ class TestSNMPService(unittest.TestCase): self.assertTrue("snmpd" in (p.name() for p in process_iter())) - def test_snmpv3(self): - """ Check if SNMPv3 can be configured and service runs""" + def test_snmpv3_sha(self): + """ Check if SNMPv3 can be configured with SHA authentication and service runs""" self.session.set(base_path + ['v3', 'engineid', '000000000000000000000002']) self.session.set(base_path + ['v3', 'group', 'default', 'mode', 'ro']) @@ -115,6 +115,41 @@ class TestSNMPService(unittest.TestCase): # Check for running process self.assertTrue("snmpd" in (p.name() for p in process_iter())) + def test_snmpv3_md5(self): + """ Check if SNMPv3 can be configured with MD5 authentication and service runs""" + + self.session.set(base_path + ['v3', 'engineid', '000000000000000000000002']) + self.session.set(base_path + ['v3', 'group', 'default', 'mode', 'ro']) + # check validate() - a view must be created before this can be comitted + with self.assertRaises(ConfigSessionError): + self.session.commit() + + self.session.set(base_path + ['v3', 'view', 'default', 'oid', '1']) + self.session.set(base_path + ['v3', 'group', 'default', 'view', 'default']) + + # create user + self.session.set(base_path + ['v3', 'user', 'vyos', 'auth', 'plaintext-password', 'vyos12345678']) + self.session.set(base_path + ['v3', 'user', 'vyos', 'auth', 'type', 'md5']) + self.session.set(base_path + ['v3', 'user', 'vyos', 'privacy', 'plaintext-password', 'vyos12345678']) + self.session.set(base_path + ['v3', 'user', 'vyos', 'privacy', 'type', 'des']) + self.session.set(base_path + ['v3', 'user', 'vyos', 'group', 'default']) + + self.session.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) + + tmp = self.session.show_config(base_path + ['v3', 'user', 'vyos', 'privacy', 'encrypted-password']).split()[1] + self.assertEqual(tmp, hashed_password) + + # TODO: read in config file and check values + + # Check for running process + self.assertTrue("snmpd" in (p.name() for p in process_iter())) + + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From a06b0051ff3643fdf532db71cceb3a5c9cc40dea Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 17 Jul 2020 19:42:41 +0200 Subject: ethernet: support testing via SSH Instead of running the smoketest on all available interfaces it is now possible to specify a whitespace separated list as environment variable where the test is performed. This is especially usefull when developing/testing via SSH and to limit the test to an interface you are not connected to. Test only with interface eth1: $ TEST_ETH='eth1' /usr/libexec/vyos/tests/smoke/cli/test_interfaces_ethernet.py --- scripts/cli/base_interfaces_test.py | 5 ++++- scripts/cli/test_interfaces_ethernet.py | 12 ++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 53fe553bc..00a6f16e4 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -38,8 +38,11 @@ class BasicInterfaceTest: def tearDown(self): # we should not remove ethernet from the overall CLI if 'ethernet' in self._base_path: - self.session.delete(self._base_path) for intf 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.session.delete(self._base_path + [intf]) self.session.set(self._base_path + [intf]) else: self.session.delete(self._base_path) diff --git a/scripts/cli/test_interfaces_ethernet.py b/scripts/cli/test_interfaces_ethernet.py index 373c81680..919caab8c 100755 --- a/scripts/cli/test_interfaces_ethernet.py +++ b/scripts/cli/test_interfaces_ethernet.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os import unittest from base_interfaces_test import BasicInterfaceTest @@ -29,10 +30,13 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! - for tmp in Section.interfaces("ethernet"): - if not '.' in tmp: - self._interfaces.append(tmp) - + if 'TEST_ETH' in os.environ: + tmp = os.environ['TEST_ETH'].split() + self._interfaces = tmp + else: + for tmp in Section.interfaces("ethernet"): + if not '.' in tmp: + self._interfaces.append(tmp) if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 1316e493983fd14e2282dfc6b107ddf68b16ed8a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 17 Jul 2020 19:55:24 +0200 Subject: interfaces: assume minimum MTU 1280 bytes which always works --- scripts/cli/base_interfaces_test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 00a6f16e4..489871b8b 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -117,8 +117,9 @@ class BasicInterfaceTest: if self._test_mtu is False: return None - # choose a MTU which works on every interface - mtu = '1400' + # choose MTU which works on every interface - 1280 is minimum for IPv6 so + # it will always work. + mtu = '1280' for intf in self._interfaces: self.session.set(self._base_path + [intf, 'mtu', mtu]) for option in self._options.get(intf, []): @@ -130,4 +131,4 @@ class BasicInterfaceTest: for intf in self._interfaces: with open('/sys/class/net/{}/mtu'.format(intf), 'r') as f: tmp = f.read().rstrip() - self.assertTrue(tmp == mtu) + self.assertEqual(tmp, mtu) -- cgit v1.2.3 From 9579cc1a7d5bf74d6dfa0f55b198dc8a3bdfcfe1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 17 Jul 2020 20:14:38 +0200 Subject: vlan: add basic test --- scripts/cli/base_interfaces_test.py | 31 ++++++++++++++++++++++++++++++- scripts/cli/test_interfaces_ethernet.py | 1 + 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 489871b8b..242f9e44c 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -114,7 +114,7 @@ class BasicInterfaceTest: Check if MTU can be changed on interface. Test MTU size will be 1400 bytes. """ - if self._test_mtu is False: + if not self._test_mtu: return None # choose MTU which works on every interface - 1280 is minimum for IPv6 so @@ -132,3 +132,32 @@ class BasicInterfaceTest: with open('/sys/class/net/{}/mtu'.format(intf), 'r') as f: tmp = f.read().rstrip() self.assertEqual(tmp, mtu) + + + def test_8021q_vlan(self): + if not self._test_vlan: + return None + + vlan_range = ['100', '200', '300', '2000'] + for intf in self._interfaces: + for vlan in vlan_range: + address = '192.0.2.1/24' + # choose MTU which works on every interface - 1280 is minimum for IPv6 so + # it will always work. + mtu = '1280' + + base = self._base_path + [intf, 'vif', vlan] + self.session.set(base + ['address', address]) + self.session.set(base + ['mtu', mtu]) + + self.session.commit() + + # Validate interface description + for intf in self._interfaces: + for vlan in vlan_range: + vif = f'{intf}.{vlan}' + with open(f'/sys/class/net/{vif}/mtu', 'r') as f: + tmp = f.read().rstrip() + self.assertEqual(tmp, mtu) + + self.assertTrue(is_intf_addr_assigned(vif, address)) diff --git a/scripts/cli/test_interfaces_ethernet.py b/scripts/cli/test_interfaces_ethernet.py index 919caab8c..9ca8a7a02 100755 --- a/scripts/cli/test_interfaces_ethernet.py +++ b/scripts/cli/test_interfaces_ethernet.py @@ -26,6 +26,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): self._base_path = ['interfaces', 'ethernet'] self._test_mtu = True + self._test_vlan = True self._interfaces = [] # we need to filter out VLAN interfaces identified by a dot (.) -- cgit v1.2.3 From 8822783cabeae4b38f881c752a4f2fc3e72d2d70 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 17 Jul 2020 20:32:39 +0200 Subject: interfaces: make single tests reusable, e.g. MTU/VLAN --- scripts/cli/base_interfaces_test.py | 67 ++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 242f9e44c..e73bc6138 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -23,9 +23,15 @@ from vyos.ifconfig import Interface class BasicInterfaceTest: class BaseTest(unittest.TestCase): _test_mtu = False + _test_vlan = False + _test_qinq = False _base_path = [] + _options = {} _interfaces = [] + _vlan_range = ['100', '200', '300', '2000'] + # choose IPv6 minimum MTU value for tests - this must always work + _mtu = '1280' def setUp(self): self.session = ConfigSession(os.getpid()) @@ -108,56 +114,49 @@ class BasicInterfaceTest: self.assertTrue(is_intf_addr_assigned(intf, addr['addr'])) + def _mtu_test(self, intf): + """ helper function to verify MTU size """ + with open('/sys/class/net/{}/mtu'.format(intf), 'r') as f: + tmp = f.read().rstrip() + self.assertEqual(tmp, self._mtu) def test_change_mtu(self): - """ - Check if MTU can be changed on interface. - Test MTU size will be 1400 bytes. - """ + """ Testcase if MTU can be changed on interface """ if not self._test_mtu: return None - - # choose MTU which works on every interface - 1280 is minimum for IPv6 so - # it will always work. - mtu = '1280' for intf in self._interfaces: - self.session.set(self._base_path + [intf, 'mtu', mtu]) + base = self._base_path + [intf] + self.session.set(base + ['mtu', self._mtu]) for option in self._options.get(intf, []): - self.session.set(self._base_path + [intf] + option.split()) + self.session.set(base + option.split()) self.session.commit() - - # Validate interface description for intf in self._interfaces: - with open('/sys/class/net/{}/mtu'.format(intf), 'r') as f: - tmp = f.read().rstrip() - self.assertEqual(tmp, mtu) + self._mtu_test(intf) + + def _vlan_config(self, intf): + for vlan in self._vlan_range: + base = self._base_path + [intf, 'vif', vlan] + self.session.set(base + ['mtu', self._mtu]) + for address in self._test_addr: + self.session.set(base + ['address', address]) + def _vlan_test(self, intf): + for vlan in self._vlan_range: + vif = f'{intf}.{vlan}' + for address in self._test_addr: + self.assertTrue(is_intf_addr_assigned(vif, address)) + with open(f'/sys/class/net/{vif}/mtu', 'r') as f: + tmp = f.read().rstrip() + self.assertEqual(tmp, self._mtu) def test_8021q_vlan(self): if not self._test_vlan: return None - vlan_range = ['100', '200', '300', '2000'] for intf in self._interfaces: - for vlan in vlan_range: - address = '192.0.2.1/24' - # choose MTU which works on every interface - 1280 is minimum for IPv6 so - # it will always work. - mtu = '1280' - - base = self._base_path + [intf, 'vif', vlan] - self.session.set(base + ['address', address]) - self.session.set(base + ['mtu', mtu]) - + self._vlan_config(intf) self.session.commit() - - # Validate interface description for intf in self._interfaces: - for vlan in vlan_range: - vif = f'{intf}.{vlan}' - with open(f'/sys/class/net/{vif}/mtu', 'r') as f: - tmp = f.read().rstrip() - self.assertEqual(tmp, mtu) + self._vlan_test(intf) - self.assertTrue(is_intf_addr_assigned(vif, address)) -- cgit v1.2.3 From a76433c1fde7e7a2c2ee7d5e4078815fb7211184 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 17 Jul 2020 20:38:22 +0200 Subject: interfaces: VLAN tests can not be "functionized" any further --- scripts/cli/base_interfaces_test.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index e73bc6138..20899b9b1 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -134,29 +134,24 @@ class BasicInterfaceTest: for intf in self._interfaces: self._mtu_test(intf) - def _vlan_config(self, intf): - for vlan in self._vlan_range: - base = self._base_path + [intf, 'vif', vlan] - self.session.set(base + ['mtu', self._mtu]) - for address in self._test_addr: - self.session.set(base + ['address', address]) - - def _vlan_test(self, intf): - for vlan in self._vlan_range: - vif = f'{intf}.{vlan}' - for address in self._test_addr: - self.assertTrue(is_intf_addr_assigned(vif, address)) - with open(f'/sys/class/net/{vif}/mtu', 'r') as f: - tmp = f.read().rstrip() - self.assertEqual(tmp, self._mtu) - def test_8021q_vlan(self): + """ Testcase for 802.1q VLAN interfaces """ if not self._test_vlan: return None for intf in self._interfaces: - self._vlan_config(intf) + for vlan in self._vlan_range: + base = self._base_path + [intf, 'vif', vlan] + self.session.set(base + ['mtu', self._mtu]) + for address in self._test_addr: + self.session.set(base + ['address', address]) + self.session.commit() for intf in self._interfaces: - self._vlan_test(intf) + 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._mtu_test(vif) + -- cgit v1.2.3 From 964ef004065cc0227bfbc36ea655067fe88ad20f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 17 Jul 2020 20:43:42 +0200 Subject: interfaces: add basic, reusable 802.1ad (Q-in-Q) testcase --- scripts/cli/base_interfaces_test.py | 22 ++++++++++++++++++++++ scripts/cli/test_interfaces_ethernet.py | 1 + 2 files changed, 23 insertions(+) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 20899b9b1..72696f780 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -29,6 +29,7 @@ class BasicInterfaceTest: _options = {} _interfaces = [] + _qinq_range = ['10', '20', '30'] _vlan_range = ['100', '200', '300', '2000'] # choose IPv6 minimum MTU value for tests - this must always work _mtu = '1280' @@ -155,3 +156,24 @@ class BasicInterfaceTest: self._mtu_test(vif) + def test_8021ad_qinq_vlan(self): + """ Testcase for 802.1ad Q-in-Q VLAN interfaces """ + if not self._test_qinq: + return None + + for interface in self._interfaces: + 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.session.set(base + ['mtu', self._mtu]) + for address in self._test_addr: + self.session.set(base + ['address', address]) + + self.session.commit() + for interface in self._interfaces: + 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.assertTrue(is_intf_addr_assigned(vif, address)) + self._mtu_test(vif) diff --git a/scripts/cli/test_interfaces_ethernet.py b/scripts/cli/test_interfaces_ethernet.py index 9ca8a7a02..7f706aabb 100755 --- a/scripts/cli/test_interfaces_ethernet.py +++ b/scripts/cli/test_interfaces_ethernet.py @@ -27,6 +27,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): self._base_path = ['interfaces', 'ethernet'] self._test_mtu = True self._test_vlan = True + self._test_qinq = True self._interfaces = [] # we need to filter out VLAN interfaces identified by a dot (.) -- cgit v1.2.3 From ded73c194266ee0121b2c4c06dcd78fed0758f81 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 17 Jul 2020 21:53:49 +0200 Subject: bridge: support specifying bridge members via environment variable This eases remote-testing via SSH. --- scripts/cli/test_interfaces_bridge.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/scripts/cli/test_interfaces_bridge.py b/scripts/cli/test_interfaces_bridge.py index 03c78c210..4402cad68 100755 --- a/scripts/cli/test_interfaces_bridge.py +++ b/scripts/cli/test_interfaces_bridge.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os import unittest from base_interfaces_test import BasicInterfaceTest @@ -30,21 +31,23 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): members = [] # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! - for tmp in Section.interfaces("ethernet"): - if not '.' in tmp: - members.append(tmp) + if 'TEST_ETH' in os.environ: + members = os.environ['TEST_ETH'].split() + else: + for tmp in Section.interfaces("ethernet"): + if not '.' in tmp: members.append(tmp) for intf in self._interfaces: + base = self._base_path + [intf] + self.session.set(base + ['stp']) + cost = 1000 priority = 10 - - self.session.set(self._base_path + [intf, 'stp']) - # assign members to bridge interface for member in members: - self.session.set(self._base_path + [intf, 'member', 'interface', member]) - self.session.set(self._base_path + [intf, 'member', 'interface', member, 'cost', str(cost)]) - self.session.set(self._base_path + [intf, 'member', 'interface', member, 'priority', str(priority)]) + base_member = base + ['member', 'interface', member] + self.session.set(base_member + ['cost', str(cost)]) + self.session.set(base_member + ['priority', str(priority)]) cost += 1 priority += 1 -- cgit v1.2.3 From 511e5aac08e84864dd9c7c1f7992bdbc7d613d47 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 17 Jul 2020 22:06:45 +0200 Subject: bond: verify member interfaces are properly enslaved --- scripts/cli/test_interfaces_bonding.py | 35 ++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/scripts/cli/test_interfaces_bonding.py b/scripts/cli/test_interfaces_bonding.py index bfadc4a9d..b4251fa15 100755 --- a/scripts/cli/test_interfaces_bonding.py +++ b/scripts/cli/test_interfaces_bonding.py @@ -14,46 +14,49 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os import unittest from base_interfaces_test import BasicInterfaceTest from vyos.ifconfig import Section from vyos.configsession import ConfigSessionError +from vyos.util import read_file class BondingInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): super().setUp() self._base_path = ['interfaces', 'bonding'] - self._test_mtu = True self._interfaces = ['bond0'] + self._test_mtu = True + self._test_vlan = True + self._test_qinq = True - def test_add_remove_member(self): + def test_add_member(self): members = [] # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! - for tmp in Section.interfaces("ethernet"): - if not '.' in tmp: - members.append(tmp) - - for intf in self._interfaces: + if 'TEST_ETH' in os.environ: + members = os.environ['TEST_ETH'].split() + else: + for tmp in Section.interfaces("ethernet"): + if not '.' in tmp: + members.append(tmp) + + for interface in self._interfaces: + base = self._base_path + [interface] for member in members: # We can not enslave an interface when there is an address # assigned - take care here - or find them dynamically if a user # runs vyos-smoketest on his production device? - self.session.set(self._base_path + [intf, 'member', 'interface', member]) + self.session.set(base + ['member', 'interface', member]) self.session.commit() - # check validate() - we can only add existing interfaces - self.session.set(self._base_path + [intf, 'member', 'interface', 'eth99']) - with self.assertRaises(ConfigSessionError): - self.session.commit() - - # check if member deletion works as expected - self.session.delete(self._base_path + [intf, 'member']) - self.session.commit() + for interface in self._interfaces: + slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split() + self.assertListEqual(slaves, members) if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 744c656994d87f5ca3b6ab06e072d8ab80a38c39 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 19 Jul 2020 10:50:09 +0200 Subject: service: bcast-relay: add initial tests --- scripts/cli/test_service_bcast-relay.py | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100755 scripts/cli/test_service_bcast-relay.py diff --git a/scripts/cli/test_service_bcast-relay.py b/scripts/cli/test_service_bcast-relay.py new file mode 100755 index 000000000..fe4531c3b --- /dev/null +++ b/scripts/cli/test_service_bcast-relay.py @@ -0,0 +1,71 @@ +#!/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 . + +import os +import unittest + +from psutil import process_iter +from vyos.configsession import ConfigSession, ConfigSessionError + +base_path = ['service', 'broadcast-relay'] + +class TestServiceBroadcastRelay(unittest.TestCase): + _address1 = '192.0.2.1/24' + _address2 = '192.0.2.1/24' + + def setUp(self): + self.session = ConfigSession(os.getpid()) + self.session.set(['interfaces', 'dummy', 'dum1001', 'address', self._address1]) + self.session.set(['interfaces', 'dummy', 'dum1002', 'address', self._address2]) + self.session.commit() + + def tearDown(self): + self.session.delete(['interfaces', 'dummy', 'dum1001']) + self.session.delete(['interfaces', 'dummy', 'dum1002']) + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_service(self): + """ Check if broadcast relay service can be configured and runs """ + ids = range(1, 5) + for id in ids: + base = base_path + ['id', str(id)] + self.session.set(base + ['description', 'vyos']) + self.session.set(base + ['port', str(10000 + id)]) + + # check validate() - two interfaces must be present + with self.assertRaises(ConfigSessionError): + self.session.commit() + + self.session.set(base + ['interface', 'dum1001']) + self.session.set(base + ['interface', 'dum1002']) + self.session.set(base + ['address', self._address1.split('/')[0]]) + + self.session.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() -- cgit v1.2.3 From 842f642c3e64800e786bde4ea4b52bcf26254f9d Mon Sep 17 00:00:00 2001 From: Robert Schindler Date: Tue, 21 Jul 2020 16:27:56 +0200 Subject: vyos.util: T2720: Add file descriptor support to chmod and chown Extended the chmod() and chown() functions in vyos.util to also operate on open file descriptors in addition to paths. This allows code that creates files to quickly change mode and owner even before anything has actually been written to the file. --- python/vyos/util.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/python/vyos/util.py b/python/vyos/util.py index 924df6b3a..6c20a865a 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -240,7 +240,8 @@ def chown(path, user, group): if user is None or group is None: return False - if not os.path.exists(path): + # 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 @@ -250,7 +251,8 @@ def chown(path, user, group): def chmod(path, bitmask): - if not os.path.exists(path): + # 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 @@ -261,28 +263,25 @@ def chmod_600(path): """ make file only read/writable by owner """ from stat import S_IRUSR, S_IWUSR - if os.path.exists(path): - bitmask = S_IRUSR | S_IWUSR - os.chmod(path, bitmask) + 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 - if os.path.exists(path): - bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP - os.chmod(path, bitmask) + 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 - if os.path.exists(path): - bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | \ - S_IROTH | S_IXOTH - os.chmod(path, bitmask) + bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | \ + S_IROTH | S_IXOTH + chmod(path, bitmask) def makedir(path, user=None, group=None): -- cgit v1.2.3 From 3ec77c9c878939f8c6e4eca62c4a2cddfef20db1 Mon Sep 17 00:00:00 2001 From: Robert Schindler Date: Tue, 21 Jul 2020 16:29:59 +0200 Subject: vyos.template: T2720: Rework vyos.template Python library Apart from code cleanup, this allows any module that wants to render a template to register custom jinja2 template filters by using the new @register_filter decorator. Examples can be found in vyos.template itself. Using the new render_to_string function, the rendered template can also be returned as a string instead of being written to a file. --- python/vyos/template.py | 163 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 108 insertions(+), 55 deletions(-) diff --git a/python/vyos/template.py b/python/vyos/template.py index d9b0c749d..c88ab04a0 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -1,4 +1,4 @@ -# Copyright 2019 VyOS maintainers and contributors +# Copyright 2019-2020 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 @@ -13,78 +13,131 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . +import functools import os +from ipaddress import ip_network from jinja2 import Environment from jinja2 import FileSystemLoader from vyos.defaults import directories from vyos.util import chmod, chown, makedir -# reuse the same Environment to improve performance -_templates_env = { - False: Environment(loader=FileSystemLoader(directories['templates'])), - True: Environment(loader=FileSystemLoader(directories['templates']), trim_blocks=True), -} -_templates_mem = { - False: {}, - True: {}, -} -def vyos_address_from_cidr(text): - """ 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 ip_network(text).network_address +# Holds template filters registered via register_filter() +_FILTERS = {} -def vyos_netmask_from_cidr(text): - """ Take an IPv4/IPv6 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 ip_network(text).netmask -def render(destination, template, content, trim_blocks=False, formater=None, permission=None, user=None, group=None): +# reuse Environments with identical trim_blocks setting to improve performance +@functools.lru_cache(maxsize=2) +def _get_environment(trim_blocks): + 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=FileSystemLoader(directories["templates"]), + trim_blocks=trim_blocks, + ) + env.filters.update(_FILTERS) + 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 """ - render a template from the template directory, it will raise on any errors - destination: the file where the rendered template must be saved - template: the path to the template relative to the template folder - content: the dictionary to use to render the template - - This classes cache the renderer, so rendering the same file multiple time - does not cause as too much overhead. If use everywhere, it could be changed - and load the template from python environement variables from an import - python module generated when the debian package is build - (recovering the load time and overhead caused by having the file out of the code) + 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 render_to_string(template, content, trim_blocks=False, formater=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 trim_blocks: controls the trim_blocks jinja2 feature + :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(bool(trim_blocks)).get_template(template) + rendered = template.render(content) + if formater is not None: + rendered = formater(rendered) + return rendered + + +def render( + destination, + template, + content, + trim_blocks=False, + formater=None, + permission=None, + user=None, + group=None, +): + """Render a template from the template directory to a file, raise on any errors. - # Create the directory if it does not exists + :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) - # Setup a renderer for the given template - # This is cached and re-used for performance - if template not in _templates_mem[trim_blocks]: - _env = _templates_env[trim_blocks] - _env.filters['address_from_cidr'] = vyos_address_from_cidr - _env.filters['netmask_from_cidr'] = vyos_netmask_from_cidr - _templates_mem[trim_blocks][template] = _env.get_template(template) + # 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, trim_blocks, formater) - template = _templates_mem[trim_blocks][template] + # Write to file + with open(destination, "w") as file: + chmod(file.fileno(), permission) + chown(file.fileno(), user, group) + file.write(rendered) - # As we are opening the file with 'w', we are performing the rendering - # before calling open() to not accidentally erase the file if the - # templating fails - content = template.render(content) - if formater: - content = formater(content) +################################## +# Custom template filters follow # +################################## - # Write client config file - with open(destination, 'w') as f: - f.write(content) - chmod(destination, permission) - chown(destination, user, group) +@register_filter("address_from_cidr") +def vyos_address_from_cidr(text): + """ 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:: + """ + return ip_network(text).network_address + + +@register_filter("netmask_from_cidr") +def vyos_netmask_from_cidr(text): + """ Take an IPv4/IPv6 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:: + """ + return ip_network(text).netmask -- cgit v1.2.3 From 142495eae746a689473a29fa4854eb713d42e59e Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 22 Jul 2020 13:14:54 -0500 Subject: config: T2707: add ConfigSource classes for alternative init data --- python/vyos/configsource.py | 318 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 python/vyos/configsource.py diff --git a/python/vyos/configsource.py b/python/vyos/configsource.py new file mode 100644 index 000000000..50222e385 --- /dev/null +++ b/python/vyos/configsource.py @@ -0,0 +1,318 @@ + +# Copyright 2020 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 . + +import os +import re +import subprocess + +from vyos.configtree import ConfigTree + +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 os.path.isfile('/tmp/vyos-config-status'): + 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('ascii') + + 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. + """ + 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)}") -- cgit v1.2.3 From d37652e0848e4d4a3e06c544c8857aff0d728bbc Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 22 Jul 2020 13:15:38 -0500 Subject: http-api: remove unneeded check for VyOSError The only calls to config (return_value, return_values, exists) do not throw VyOSError; remove unneeded except. --- src/services/vyos-http-api-server | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 4c41fa96d..d5730d86c 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -32,7 +32,6 @@ from waitress import serve from functools import wraps from vyos.configsession import ConfigSession, ConfigSessionError -from vyos.config import VyOSError DEFAULT_CONFIG_FILE = '/etc/vyos/http-api.conf' @@ -231,8 +230,6 @@ def retrieve_op(command): return error(400, "\"{0}\" is not a valid config format".format(config_format)) else: return error(400, "\"{0}\" is not a valid operation".format(op)) - except VyOSError as e: - return error(400, str(e)) except ConfigSessionError as e: return error(400, str(e)) except Exception as e: -- cgit v1.2.3 From 929f7b1713b16c72b0baab2e01dc363dc3928850 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 22 Jul 2020 13:16:17 -0500 Subject: load-config: subclass ConfigSourceSession instead of Config --- src/helpers/vyos-load-config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/helpers/vyos-load-config.py b/src/helpers/vyos-load-config.py index a9fa15778..c2da1bb11 100755 --- a/src/helpers/vyos-load-config.py +++ b/src/helpers/vyos-load-config.py @@ -27,12 +27,12 @@ import sys import tempfile import vyos.defaults import vyos.remote -from vyos.config import Config, VyOSError +from vyos.configsource import ConfigSourceSession, VyOSError from vyos.migrator import Migrator, VirtualMigrator, MigratorError -class LoadConfig(Config): +class LoadConfig(ConfigSourceSession): """A subclass for calling 'loadFile'. - This does not belong in config.py, and only has a single caller. + 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]) -- cgit v1.2.3 From 280044183e086c1ef1fbe9e8a7291f738bb7e504 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 22 Jul 2020 13:16:42 -0500 Subject: config: T2707: use ConfigSource and refactor Config methods --- python/vyos/config.py | 152 +++++++------------------------------------------- 1 file changed, 20 insertions(+), 132 deletions(-) diff --git a/python/vyos/config.py b/python/vyos/config.py index 5d58316e7..884d6d947 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -63,23 +63,13 @@ In operational mode, all functions return values from the running config. """ -import os import re import json -import subprocess from copy import deepcopy import vyos.util import vyos.configtree - -class VyOSError(Exception): - """ - Raised on config access errors, most commonly if the type of a config tree node - in the system does not match the type of operation. - - """ - pass - +from vyos.configsource import ConfigSource, ConfigSourceSession class Config(object): """ @@ -89,51 +79,18 @@ class Config(object): the only state it keeps is relative *config path* for convenient access to config subtrees. """ - def __init__(self, session_env=None): - self._cli_shell_api = "/bin/cli-shell-api" - self._level = [] - self._dict_cache = {} - - 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 os.path.isfile('/tmp/vyos-config-status'): - 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 = vyos.configtree.ConfigTree(running_config_text) - else: - self._running_config = None - - if session_config_text: - self._session_config = vyos.configtree.ConfigTree(session_config_text) + def __init__(self, session_env=None, config_source=None): + if config_source is None: + self._config_source = ConfigSourceSession(session_env) else: - self._session_config = None + if not isinstance(config_source, ConfigSource): + raise TypeError("config_source not of type ConfigSource") + self._config_source = config_source - def _make_command(self, op, path): - args = path.split() - cmd = [self._cli_shell_api, op] + args - return cmd + self._level = [] + self._dict_cache = {} + (self._running_config, + self._session_config) = self._config_source.get_configtree_tuple() def _make_path(self, path): # Backwards-compatibility stuff: original implementation used string paths @@ -149,19 +106,6 @@ class Config(object): raise TypeError("Path must be a whitespace-separated string or a list") return (self._level + path) - 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('ascii') - def set_level(self, path): """ Set the *edit level*, that is, a relative config tree path. @@ -229,22 +173,14 @@ class Config(object): Returns: True if the config session has uncommited changes, False otherwise. """ - try: - self._run(self._make_command('sessionChanged', '')) - return True - except VyOSError: - return False + return self._config_source.session_changed() def in_session(self): """ Returns: True if called from a configuration session, False otherwise. """ - try: - self._run(self._make_command('inSession', '')) - return True - except VyOSError: - return False + return self._config_source.in_session() def show_config(self, path=[], default=None, effective=False): """ @@ -255,40 +191,7 @@ class Config(object): 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) + return self._config_source.show_config(path, default, effective) def get_cached_dict(self, effective=False): cached = self._dict_cache.get(effective, {}) @@ -346,12 +249,8 @@ class Config(object): 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 + self._config_source.set_level(self.get_level) + return self._config_source.is_multi(path) def is_tag(self, path): """ @@ -364,12 +263,8 @@ class Config(object): 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 + self._config_source.set_level(self.get_level) + return self._config_source.is_tag(path) def is_leaf(self, path): """ @@ -382,12 +277,8 @@ class Config(object): 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 + self._config_source.set_level(self.get_level) + return self._config_source.is_leaf(path) def return_value(self, path, default=None): """ @@ -548,9 +439,6 @@ class Config(object): Returns: str list: child node names - - Raises: - VyOSError: if the node is not a tag node """ if self._running_config: try: -- cgit v1.2.3 From faaad2e029fd3520b62983dd78c7692434138360 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Wed, 22 Jul 2020 23:36:44 +0100 Subject: xml: T2582: use xml for is_tag and is_leaf --- python/vyos/validate.py | 7 ++++--- python/vyos/xml/__init__.py | 15 +++++++++++++++ python/vyos/xml/definition.py | 23 +++++++++++++++++++---- python/vyos/xml/test_xml.py | 2 +- src/tests/test_initial_setup.py | 10 ++++++---- 5 files changed, 45 insertions(+), 12 deletions(-) diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 9072c5817..a0620e4dd 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -19,6 +19,7 @@ import netifaces import ipaddress from vyos.util import cmd +from vyos import xml # Important note when you are adding new validation functions: # @@ -293,12 +294,12 @@ def is_member(conf, interface, intftype=None): for it in intftype: base = 'interfaces ' + it for intf in conf.list_nodes(base): - memberintf = f'{base} {intf} member interface' - if conf.is_tag(memberintf): + memberintf = [base, intf, 'member', 'interface'] + if xml.is_tag(memberintf): if interface in conf.list_nodes(memberintf): ret_val = intf break - elif conf.is_leaf(memberintf): + elif xml.is_leaf(memberintf): if ( conf.exists(memberintf) and interface in conf.return_values(memberintf) ): ret_val = intf diff --git a/python/vyos/xml/__init__.py b/python/vyos/xml/__init__.py index 6e0e73b1b..0f914fed2 100644 --- a/python/vyos/xml/__init__.py +++ b/python/vyos/xml/__init__.py @@ -35,6 +35,18 @@ def load_configuration(cache=[]): return xml +# def is_multi(lpath): +# return load_configuration().is_multi(lpath) + + +def is_tag(lpath): + return load_configuration().is_tag(lpath) + + +def is_leaf(lpath, flat=True): + return load_configuration().is_leaf(lpath, flat) + + def defaults(lpath, flat=False): return load_configuration().defaults(lpath, flat) @@ -42,3 +54,6 @@ def defaults(lpath, flat=False): if __name__ == '__main__': print(defaults(['service'], flat=True)) print(defaults(['service'], flat=False)) + + print(is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) + print(is_tag(['protocols', 'static', 'multicast', 'route', '0.0.0.0/0', 'next-hop'])) diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py index b0339b228..098e64f7e 100644 --- a/python/vyos/xml/definition.py +++ b/python/vyos/xml/definition.py @@ -126,10 +126,12 @@ class XML(dict): elif word: if data_node != kw.plainNode or len(passed) == 1: self.options = [_ for _ in self.tree if _.startswith(word)] + self.options.sort() else: self.options = [] else: self.options = named_options + self.options.sort() self.plain = not is_dataNode @@ -143,6 +145,7 @@ class XML(dict): self.word = '' if self.tree.get(kw.node,'') not in (kw.tagNode, kw.leafNode): self.options = [_ for _ in self.tree if not kw.found(_)] + self.options.sort() def checks(self, cmd): # as we move thought the named node twice @@ -300,16 +303,28 @@ class XML(dict): return tree def _get(self, lpath, tag, with_tag=True): - return self._tree(lpath + [tag], with_tag) + tree = self._tree(lpath, with_tag) + if tree is None: + return None + return tree.get(tag, None) def is_multi(self, lpath, with_tag=True): - return self._get(lpath, kw.multi, with_tag) is True + tree = self._get(lpath, kw.multi, with_tag) + if tree is None: + return None + return tree is True def is_tag(self, lpath, with_tag=True): - return self._get(lpath, kw.node, with_tag) == kw.tagNode + tree = self._get(lpath, kw.node, with_tag) + if tree is None: + return None + return tree == kw.tagNode def is_leaf(self, lpath, with_tag=True): - return self._get(lpath, kw.node, with_tag) == kw.leafNode + tree = self._get(lpath, kw.node, with_tag) + if tree is None: + return None + return tree == kw.leafNode def exists(self, lpath, with_tag=True): return self._get(lpath, kw.node, with_tag) is not None diff --git a/python/vyos/xml/test_xml.py b/python/vyos/xml/test_xml.py index ac0620d99..ff55151d2 100644 --- a/python/vyos/xml/test_xml.py +++ b/python/vyos/xml/test_xml.py @@ -33,7 +33,7 @@ class TestSearch(TestCase): last = self.xml.traverse("") self.assertEqual(last, '') self.assertEqual(self.xml.inside, []) - self.assertEqual(self.xml.options, ['protocols', 'service', 'system', 'firewall', 'interfaces', 'vpn', 'nat', 'vrf', 'high-availability']) + self.assertEqual(self.xml.options, ['firewall', 'high-availability', 'interfaces', 'nat', 'protocols', 'service', 'system', 'vpn', 'vrf']) self.assertEqual(self.xml.filling, False) self.assertEqual(self.xml.word, last) self.assertEqual(self.xml.check, False) diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py index c4c59b827..1597025e8 100644 --- a/src/tests/test_initial_setup.py +++ b/src/tests/test_initial_setup.py @@ -21,6 +21,7 @@ import tempfile import unittest from unittest import TestCase, mock +from vyos import xml import vyos.configtree import vyos.initialsetup as vis @@ -30,6 +31,7 @@ class TestInitialSetup(TestCase): with open('tests/data/config.boot.default', 'r') as f: config_string = f.read() self.config = vyos.configtree.ConfigTree(config_string) + self.xml = xml.load_configuration() def test_set_user_password(self): vis.set_user_password(self.config, 'vyos', 'vyosvyos') @@ -56,7 +58,7 @@ class TestInitialSetup(TestCase): self.assertEqual(key_type, 'ssh-rsa') self.assertEqual(key_data, 'fakedata') - self.assertTrue(self.config.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) + self.assertTrue(self.xml.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) def test_set_ssh_key_without_name(self): # If key file doesn't include a name, the function will use user name for the key name @@ -69,7 +71,7 @@ class TestInitialSetup(TestCase): self.assertEqual(key_type, 'ssh-rsa') self.assertEqual(key_data, 'fakedata') - self.assertTrue(self.config.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) + self.assertTrue(self.xml.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) def test_create_user(self): vis.create_user(self.config, 'jrandomhacker', password='qwerty', key=" ssh-rsa fakedata jrandomhacker@foovax ") @@ -95,8 +97,8 @@ class TestInitialSetup(TestCase): vis.set_default_gateway(self.config, '192.0.2.1') self.assertTrue(self.config.exists(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '192.0.2.1'])) - self.assertTrue(self.config.is_tag(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop'])) - self.assertTrue(self.config.is_tag(['protocols', 'static', 'route'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route', '0.0.0.0/0', 'next-hop'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route'])) if __name__ == "__main__": unittest.main() -- cgit v1.2.3 From 8361090d15d8022c1e04fa36ee90d69d72d9a48b Mon Sep 17 00:00:00 2001 From: jack9603301 Date: Fri, 24 Jul 2020 12:03:28 +0800 Subject: traceroute: T2723: Support tcptraceroute --- op-mode-definitions/traceroute.xml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/op-mode-definitions/traceroute.xml b/op-mode-definitions/traceroute.xml index 340d28280..efbaaeb2f 100644 --- a/op-mode-definitions/traceroute.xml +++ b/op-mode-definitions/traceroute.xml @@ -25,6 +25,24 @@ /usr/bin/traceroute -4 "$3" + + + + Route tracing and port detection using TCP + + + + + Perform operations on TCP ports + + 0-65535 + + + sudo /usr/sbin/tcptraceroute "$3" $6 + + + + @@ -34,6 +52,24 @@ /usr/bin/traceroute -6 "$3" + + + + Use TCP/IPv6 packets to perform a traceroute + + + + + Perform operations on TCP ports + + 0-65535 + + + sudo /usr/sbin/tcptraceroute6 "$3" $6 + + + + -- cgit v1.2.3 From 61dccd81a1037c06ae883020db51409dda3e41f9 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Fri, 24 Jul 2020 13:20:00 +0300 Subject: T2727: add a dotted decimal validator. Since it's relatively rarely used, Python's startup time should't be much of a problem. --- src/validators/dotted-decimal | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100755 src/validators/dotted-decimal diff --git a/src/validators/dotted-decimal b/src/validators/dotted-decimal new file mode 100755 index 000000000..652110346 --- /dev/null +++ b/src/validators/dotted-decimal @@ -0,0 +1,33 @@ +#!/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 . + +import re +import sys + +area = sys.argv[1] + +res = re.match(r'^(\d+)\.(\d+)\.(\d+)\.(\d+)$', area) +if not res: + print("\'{0}\' is not a valid dotted decimal value".format(area)) + sys.exit(1) +else: + components = res.groups() + for n in range(0, 4): + if (int(components[n]) > 255): + print("Invalid component of a dotted decimal value: {0} exceeds 255".format(components[n])) + sys.exit(1) + +sys.exit(0) -- cgit v1.2.3 From ed4369ca1ed6a5504cbf181722df11c444dd8555 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 17:06:01 +0200 Subject: pppoe: use assertEqual() --- scripts/cli/test_interfaces_pppoe.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/scripts/cli/test_interfaces_pppoe.py b/scripts/cli/test_interfaces_pppoe.py index 3c11da795..d2f6c9b16 100755 --- a/scripts/cli/test_interfaces_pppoe.py +++ b/scripts/cli/test_interfaces_pppoe.py @@ -53,7 +53,7 @@ class PPPoEInterfaceTest(unittest.TestCase): self.session.commit() del self.session - def test_pppoe_1(self): + def test_pppoe(self): """ Check if PPPoE dialer can be configured and runs """ for interface in self._interfaces: user = 'VyOS-user-' + interface @@ -80,13 +80,13 @@ class PPPoEInterfaceTest(unittest.TestCase): password = 'VyOS-passwd-' + interface tmp = get_config_value(interface, 'mtu')[1] - self.assertTrue(tmp in mtu) + self.assertEqual(tmp, mtu) tmp = get_config_value(interface, 'user')[1].replace('"', '') - self.assertTrue(tmp in user) + self.assertEqual(tmp, user) tmp = get_config_value(interface, 'password')[1].replace('"', '') - self.assertTrue(tmp in password) + self.assertEqual(tmp, password) tmp = get_config_value(interface, 'ifname')[1] - self.assertTrue(tmp in interface) + self.assertEqual(tmp, interface) # Check if ppp process is running in the interface in question running = False @@ -98,7 +98,7 @@ class PPPoEInterfaceTest(unittest.TestCase): self.assertTrue(running) def test_pppoe_dhcpv6pd(self): - """ Check if PPPoE dialer can be configured and runs """ + """ Check if PPPoE dialer can be configured with DHCPv6-PD """ address = '1' sla_id = '0' sla_len = '8' @@ -122,28 +122,28 @@ class PPPoEInterfaceTest(unittest.TestCase): # verify "normal" PPPoE value - 1492 is default MTU tmp = get_config_value(interface, 'mtu')[1] - self.assertTrue(tmp in '1492') + self.assertEqual(tmp, '1492') tmp = get_config_value(interface, 'user')[1].replace('"', '') - self.assertTrue(tmp in 'vyos') + self.assertEqual(tmp, 'vyos') tmp = get_config_value(interface, 'password')[1].replace('"', '') - self.assertTrue(tmp in 'vyos') + self.assertEqual(tmp, 'vyos') for param in ['+ipv6', 'ipv6cp-use-ipaddr']: tmp = get_config_value(interface, param)[0] - self.assertTrue(tmp in param) + self.assertEqual(tmp, param) # verify DHCPv6 prefix delegation # will return: ['delegation', '::/56 infinity;'] tmp = get_dhcp6c_config_value(interface, 'prefix')[1].split()[0] # mind the whitespace - self.assertTrue(tmp in '::/56') + self.assertEqual(tmp, '::/56') tmp = get_dhcp6c_config_value(interface, 'prefix-interface')[0].split()[0] - self.assertTrue(tmp in self._source_interface) + self.assertEqual(tmp, self._source_interface) tmp = get_dhcp6c_config_value(interface, 'ifid')[0] - self.assertTrue(tmp in address) + self.assertEqual(tmp, address) tmp = get_dhcp6c_config_value(interface, 'sla-id')[0] - self.assertTrue(tmp in sla_id) + self.assertEqual(tmp, sla_id) tmp = get_dhcp6c_config_value(interface, 'sla-len')[0] - self.assertTrue(tmp in sla_len) + self.assertEqual(tmp, sla_len) # Check if ppp process is running in the interface in question running = False -- cgit v1.2.3 From ded9d1b9372cdee3066fc62fa59c4fd2a0ea3ad5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 17:06:25 +0200 Subject: ethernet: test "ip" subtree of interface for e.g. ARP settings --- scripts/cli/base_interfaces_test.py | 50 +++++++++++++++++++++++++++++++++ scripts/cli/test_interfaces_ethernet.py | 1 + 2 files changed, 51 insertions(+) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 72696f780..fa3e8a0c1 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -19,9 +19,11 @@ from vyos.configsession import ConfigSession from netifaces import ifaddresses, AF_INET, AF_INET6 from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local from vyos.ifconfig import Interface +from vyos.util import read_file class BasicInterfaceTest: class BaseTest(unittest.TestCase): + _test_ip = False _test_mtu = False _test_vlan = False _test_qinq = False @@ -177,3 +179,51 @@ class BasicInterfaceTest: for address in self._test_addr: self.assertTrue(is_intf_addr_assigned(vif, address)) self._mtu_test(vif) + + def test_ip_options(self): + """ test IP options like arp """ + if not self._test_ip: + return None + + for interface in self._interfaces: + arp_tmo = '300' + path = self._base_path + [interface] + for option in self._options.get(interface, []): + self.session.set(path + option.split()) + + # Options + self.session.set(path + ['ip', 'arp-cache-timeout', arp_tmo]) + self.session.set(path + ['ip', 'disable-arp-filter']) + self.session.set(path + ['ip', 'enable-arp-accept']) + self.session.set(path + ['ip', 'enable-arp-announce']) + self.session.set(path + ['ip', 'enable-arp-ignore']) + self.session.set(path + ['ip', 'enable-proxy-arp']) + self.session.set(path + ['ip', 'proxy-arp-pvlan']) + self.session.set(path + ['ip', 'source-validation', 'loose']) + + self.session.commit() + + for interface in self._interfaces: + 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 + + tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/arp_filter') + self.assertEqual('0', tmp) + + tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/arp_accept') + self.assertEqual('1', tmp) + + tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/arp_announce') + self.assertEqual('1', tmp) + + tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/arp_ignore') + self.assertEqual('1', tmp) + + tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/proxy_arp') + self.assertEqual('1', tmp) + + tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/proxy_arp_pvlan') + self.assertEqual('1', tmp) + + tmp = read_file(f'/proc/sys/net/ipv4/conf/{interface}/rp_filter') + self.assertEqual('2', tmp) diff --git a/scripts/cli/test_interfaces_ethernet.py b/scripts/cli/test_interfaces_ethernet.py index 7f706aabb..ccc4238e2 100755 --- a/scripts/cli/test_interfaces_ethernet.py +++ b/scripts/cli/test_interfaces_ethernet.py @@ -25,6 +25,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): super().setUp() self._base_path = ['interfaces', 'ethernet'] + self._test_ip = True self._test_mtu = True self._test_vlan = True self._test_qinq = True -- cgit v1.2.3 From 132b0a424730fca39e1ea3b3ab5c177ec330a057 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 21:29:19 +0200 Subject: wireless: use library function for loading kernel modules --- scripts/cli/test_interfaces_wireless.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/cli/test_interfaces_wireless.py b/scripts/cli/test_interfaces_wireless.py index 5d04a1c44..ff293ac84 100755 --- a/scripts/cli/test_interfaces_wireless.py +++ b/scripts/cli/test_interfaces_wireless.py @@ -18,6 +18,7 @@ import os import unittest from base_interfaces_test import BasicInterfaceTest +from vyos.util import check_kmod class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): @@ -47,8 +48,6 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): # commit changes self.session.commit() - - if __name__ == '__main__': - os.system('sudo modprobe mac80211_hwsim') + check_kmod('mac80211_hwsim') unittest.main() -- cgit v1.2.3 From 1a85e758b105d493bb9d95916816bd206345bc5d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Jul 2020 15:59:06 +0200 Subject: vyos.util: add common helper to load kernel modules l2tpv3, wireguard, wirelessmodem, nat all require additional Kernel modules to be present on the system. Each and every interface implemented their own way of loading a module - by copying code. Use a generic function, vyos.util.check_kmod() to load any arbitrary kernel module passed as string or list. --- python/vyos/util.py | 9 +++++++++ src/conf_mode/interfaces-l2tpv3.py | 12 ++++-------- src/conf_mode/interfaces-wireguard.py | 12 +++--------- src/conf_mode/interfaces-wirelessmodem.py | 9 ++------- src/conf_mode/nat.py | 17 ++++++----------- src/op_mode/wireguard.py | 17 ++++++----------- 6 files changed, 30 insertions(+), 46 deletions(-) diff --git a/python/vyos/util.py b/python/vyos/util.py index 924df6b3a..7234be6cb 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -652,3 +652,12 @@ def get_bridge_member_config(conf, br, intf): conf.set_level(old_level) return memberconf + +def check_kmod(k_mod): + """ Common utility function to load required kernel modules on demand """ + 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') diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 4ff0bcb57..866419f2c 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -24,11 +24,14 @@ from vyos.config import Config from vyos.ifconfig import L2TPv3If, Interface from vyos import ConfigError from vyos.util import call +from vyos.util import check_kmod from vyos.validate import is_member, is_addr_assigned from vyos import airbag airbag.enable() +k_mod = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] + default_config_data = { 'address': [], 'deleted': False, @@ -53,13 +56,6 @@ default_config_data = { 'tunnel_id': '' } -def check_kmod(): - modules = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') - def get_config(): l2tpv3 = deepcopy(default_config_data) conf = Config() @@ -283,7 +279,7 @@ def apply(l2tpv3): if __name__ == '__main__': try: - check_kmod() + check_kmod(k_mod) c = get_config() verify(c) generate(c) diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index c24c9a7ce..982aefa5f 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -25,6 +25,7 @@ from vyos.config import Config from vyos.configdict import list_diff from vyos.ifconfig import WireGuardIf from vyos.util import chown, chmod_750, call +from vyos.util import check_kmod from vyos.validate import is_member, is_ipv6 from vyos import ConfigError @@ -32,6 +33,7 @@ from vyos import airbag airbag.enable() kdir = r'/config/auth/wireguard' +k_mod = 'wireguard' default_config_data = { 'intfc': '', @@ -50,14 +52,6 @@ default_config_data = { 'vrf': '' } -def _check_kmod(): - modules = ['wireguard'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') - - def _migrate_default_keys(): if os.path.exists(f'{kdir}/private.key') and not os.path.exists(f'{kdir}/default/private.key'): location = f'{kdir}/default' @@ -315,7 +309,7 @@ def apply(wg): if __name__ == '__main__': try: - _check_kmod() + check_kmod(k_mod) _migrate_default_keys() c = get_config() verify(c) diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index ec5a85e54..0964a8f4d 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -29,12 +29,7 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def check_kmod(): - modules = ['option', 'usb_wwan', 'usbserial'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') +k_mod = ['option', 'usb_wwan', 'usbserial'] def find_device_file(device): """ Recurively search /dev for the given device file and return its full path. @@ -153,7 +148,7 @@ def apply(wwan): if __name__ == '__main__': try: - check_kmod() + check_kmod(k_mod) c = get_config() verify(c) generate(c) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 2299717a8..dd34dfd66 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -24,13 +24,17 @@ from netifaces import interfaces from vyos.config import Config from vyos.template import render -from vyos.util import call, cmd +from vyos.util import call +from vyos.util import cmd +from vyos.util import check_kmod from vyos.validate import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() +k_mod = ['nft_nat', 'nft_chain_nat_ipv4'] + default_config_data = { 'deleted': False, 'destination': [], @@ -44,15 +48,6 @@ default_config_data = { iptables_nat_config = '/tmp/vyos-nat-rules.nft' -def _check_kmod(): - """ load required Kernel modules """ - modules = ['nft_nat', 'nft_chain_nat_ipv4'] - for module in modules: - if not os.path.exists(f'/sys/module/{module}'): - if call(f'modprobe {module}') != 0: - raise ConfigError(f'Loading Kernel module {module} failed') - - def get_handler(json, chain, target): """ Get nftable rule handler number of given chain/target combination. Handler is required when adding NAT/Conntrack helper targets """ @@ -269,7 +264,7 @@ def apply(nat): if __name__ == '__main__': try: - _check_kmod() + check_kmod(k_mod) c = get_config() verify(c) generate(c) diff --git a/src/op_mode/wireguard.py b/src/op_mode/wireguard.py index 15bf63e81..e08bc983a 100755 --- a/src/op_mode/wireguard.py +++ b/src/op_mode/wireguard.py @@ -21,22 +21,17 @@ import shutil import syslog as sl import re +from vyos.config import Config from vyos.ifconfig import WireGuardIf - +from vyos.util import cmd +from vyos.util import run +from vyos.util import check_kmod from vyos import ConfigError -from vyos.config import Config -from vyos.util import cmd, run dir = r'/config/auth/wireguard' psk = dir + '/preshared.key' -def check_kmod(): - """ check if kmod is loaded, if not load it """ - if not os.path.exists('/sys/module/wireguard'): - sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod") - if run('sudo modprobe wireguard') != 0: - sl.syslog(sl.LOG_ERR, "modprobe wireguard failed") - raise ConfigError("modprobe wireguard failed") +k_mod = 'wireguard' def generate_keypair(pk, pub): """ generates a keypair which is stored in /config/auth/wireguard """ @@ -106,7 +101,7 @@ def del_key_dir(kname): if __name__ == '__main__': - check_kmod() + check_kmod(k_mod) parser = argparse.ArgumentParser(description='wireguard key management') parser.add_argument( '--genkey', action="store_true", help='generate key-pair') -- cgit v1.2.3 From 6d2ffb9badcd15d431b8bbb6b28d2171d06e6dc4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 30 Jun 2020 20:38:17 +0200 Subject: ifconfig: T2653: set arp-cache-timeout default value of 30ms --- interface-definitions/include/interface-arp-cache-timeout.xml.i | 1 + 1 file changed, 1 insertion(+) diff --git a/interface-definitions/include/interface-arp-cache-timeout.xml.i b/interface-definitions/include/interface-arp-cache-timeout.xml.i index 81d35f593..e65321158 100644 --- a/interface-definitions/include/interface-arp-cache-timeout.xml.i +++ b/interface-definitions/include/interface-arp-cache-timeout.xml.i @@ -10,4 +10,5 @@ ARP cache entry timeout must be between 1 and 86400 seconds + 30 -- cgit v1.2.3 From ebefa38b9fa946fde82a4c9b55122c037598143b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 1 Jul 2020 19:06:52 +0200 Subject: ethernet: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. While providing a new update() method in vyos.ifconfig.interfaces() this is extended for ethernet based interfaces which also supports 802.1q, 802.1ad VLANs. This commit migrates the existing codebase for an ethernet based interfaces and implements the missing parts for VLANs. Adding or migrating other interfaces (e.g. bridge or bond) will become much easier as they must reuse the entire functionality - we now walk towards a single codepath. Thanks for all who made this combined effort possible! Signed-off-by: Christian Poessinger --- interface-definitions/interfaces-ethernet.xml.in | 2 + python/vyos/configdict.py | 29 +- python/vyos/configverify.py | 53 +++- python/vyos/ifconfig/ethernet.py | 101 ++++++- python/vyos/ifconfig/interface.py | 106 +++++++- python/vyos/ifconfig_vlan.py | 24 ++ src/conf_mode/interfaces-ethernet.py | 329 +++++------------------ 7 files changed, 371 insertions(+), 273 deletions(-) diff --git a/interface-definitions/interfaces-ethernet.xml.in b/interface-definitions/interfaces-ethernet.xml.in index 1e32a15f8..e8f3f09f1 100644 --- a/interface-definitions/interfaces-ethernet.xml.in +++ b/interface-definitions/interfaces-ethernet.xml.in @@ -56,6 +56,7 @@ duplex must be auto, half or full + auto #include @@ -265,6 +266,7 @@ Speed must be auto, 10, 100, 1000, 2500, 5000, 10000, 25000, 40000, 50000 or 100000 + auto #include #include diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 0dc7578d8..682caed8f 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -102,12 +102,35 @@ def dict_merge(source, destination): return tmp def list_diff(first, second): - """ - Diff two dictionaries and return only unique items - """ + """ Diff two dictionaries and return only unique items """ second = set(second) return [item for item in first if item not in second] +def T2665_default_dict_cleanup(dict): + """ Cleanup default keys for tag nodes https://phabricator.vyos.net/T2665. """ + # Cleanup + for vif in ['vif', 'vif_s']: + if vif in dict.keys(): + for key in ['ip', 'mtu']: + if key in dict[vif].keys(): + del dict[vif][key] + + # cleanup VIF-S defaults + if 'vif_c' in dict[vif].keys(): + for key in ['ip', 'mtu']: + if key in dict[vif]['vif_c'].keys(): + del dict[vif]['vif_c'][key] + # If there is no vif-c defined and we just cleaned the default + # keys - we can clean the entire vif-c dict as it's useless + if not dict[vif]['vif_c']: + del dict[vif]['vif_c'] + + # If there is no real vif/vif-s defined and we just cleaned the default + # keys - we can clean the entire vif dict as it's useless + if not dict[vif]: + del dict[vif] + + return dict def get_ethertype(ethertype_val): if ethertype_val == '0x88A8': diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 32129a048..36b10c956 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -41,14 +41,14 @@ def verify_vrf(config): def verify_address(config): """ - Common helper function used by interface implementations to - perform recurring validation of IP address assignmenr - when interface also is part of a bridge. + 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): raise ConfigError( - f'Cannot assign address to interface "{ifname}" as it is a ' - f'member of bridge "{is_bridge_member}"!'.format(**config)) + 'Cannot assign address to interface "{ifname}" as it is a ' + 'member of bridge "{is_bridge_member}"!'.format(**config)) def verify_bridge_delete(config): @@ -62,6 +62,15 @@ def verify_bridge_delete(config): 'Interface "{ifname}" cannot be deleted as it is a ' 'member of bridge "{is_bridge_member}"!'.format(**config)) +def verify_interface_exists(config): + """ + Common helper function used by interface implementations to perform + recurring validation if an interface actually exists. + """ + from netifaces import interfaces + if not config['ifname'] in interfaces(): + raise ConfigError(f'Interface "{ifname}" does not exist!' + .format(**config)) def verify_source_interface(config): """ @@ -76,3 +85,37 @@ def verify_source_interface(config): if not config['source_interface'] in interfaces(): raise ConfigError(f'Source interface {source_interface} does not ' f'exist'.format(**config)) + +def verify_dhcpv6(config): + """ + Common helper function used by interface implementations to perform + recurring validation of DHCPv6 options which are mutually exclusive. + """ + if {'parameters_only', 'temporary'} <= set(config.get('dhcpv6_options', {})): + raise ConfigError('DHCPv6 temporary and parameters-only options ' + 'are mutually exclusive!') + +def verify_vlan_config(config): + """ + Common helper function used by interface implementations to perform + recurring validation of interface VLANs + """ + # 802.1q VLANs + for vlan in config.get('vif', {}).keys(): + vlan = config['vif'][vlan] + verify_dhcpv6(vlan) + verify_address(vlan) + verify_vrf(vlan) + + # 802.1ad (Q-in-Q) VLANs + for vlan in config.get('vif_s', {}).keys(): + vlan = config['vif_s'][vlan] + verify_dhcpv6(vlan) + verify_address(vlan) + verify_vrf(vlan) + + for vlan in config.get('vif_s', {}).get('vif_c', {}).keys(): + vlan = config['vif_c'][vlan] + verify_dhcpv6(vlan) + verify_address(vlan) + verify_vrf(vlan) diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 5b18926c9..8a50a8699 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -15,13 +15,14 @@ import os import re +import jmespath +from vyos.configdict import get_ethertype from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN from vyos.validate import assert_list from vyos.util import run - @Interface.register @VLAN.enable class EthernetIf(Interface): @@ -252,3 +253,101 @@ class EthernetIf(Interface): >>> i.set_udp_offload('on') """ return self.set_interface('ufo', state) + + + 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. """ + + # now call the regular function from within our base class + super().update(config) + + # disable ethernet flow control (pause frames) + value = 'off' if 'disable_flow_control' in config.keys() else 'on' + self.set_flow_control(value) + + # GRO (generic receive offload) + tmp = jmespath.search('offload_options.generic_receive', config) + value = tmp if (tmp != None) else 'off' + self.set_gro(value) + + # GSO (generic segmentation offload) + tmp = jmespath.search('offload_options.generic_segmentation', config) + value = tmp if (tmp != None) else 'off' + self.set_gso(value) + + # scatter-gather option + tmp = jmespath.search('offload_options.scatter_gather', config) + value = tmp if (tmp != None) else 'off' + self.set_sg(value) + + # TSO (TCP segmentation offloading) + tmp = jmespath.search('offload_options.udp_fragmentation', config) + value = tmp if (tmp != None) else 'off' + self.set_tso(value) + + # UDP fragmentation offloading + tmp = jmespath.search('offload_options.udp_fragmentation', config) + value = tmp if (tmp != None) else 'off' + self.set_ufo(value) + + # Set physical interface speed and duplex + if {'speed', 'duplex'} <= set(config): + speed = config.get('speed') + duplex = config.get('duplex') + self.set_speed_duplex(speed, duplex) + + # Delete old IPv6 EUI64 addresses before changing MAC + + # 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) + + # Add IPv6 EUI-based addresses + tmp = jmespath.search('ipv6.address.eui64', config) + if tmp: + # XXX: T2636 workaround: convert string to a list with one element + if isinstance(tmp, str): + tmp = [tmp] + for addr in tmp: + self.add_ipv6_eui64_address(addr) + + # re-add ourselves to any bridge we might have fallen out of + if 'is_bridge_member' in config: + bridge = config.get('is_bridge_member') + self.add_to_bridge(bridge) + + # remove no longer required 802.1ad (Q-in-Q VLANs) + for vif_s_id in config.get('vif_s_remove', {}): + self.del_vlan(vif_s_id) + + # create/update 802.1ad (Q-in-Q VLANs) + for vif_s_id, vif_s in config.get('vif_s', {}).items(): + tmp=get_ethertype(vif_s.get('ethertype', '0x88A8')) + s_vlan = self.add_vlan(vif_s_id, ethertype=tmp) + s_vlan.update(vif_s) + + # remove no longer required client VLAN (vif-c) + for vif_c_id in vif_s.get('vif_c_remove', {}): + s_vlan.del_vlan(vif_c_id) + + # create/update client VLAN (vif-c) interface + for vif_c_id, vif_c in vif_s.get('vif_c', {}).items(): + c_vlan = s_vlan.add_vlan(vif_c_id) + c_vlan.update(vif_c) + + # remove no longer required 802.1q VLAN interfaces + for vif_id in config.get('vif_remove', {}): + self.del_vlan(vif_id) + + # create/update 802.1q VLAN interfaces + for vif_id, vif in config.get('vif', {}).items(): + vlan = self.add_vlan(vif_id) + vlan.update(vif) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 8d7b247fc..689faa22b 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -16,6 +16,7 @@ import os import re import json +import jmespath from copy import deepcopy from ipaddress import IPv4Network @@ -322,11 +323,11 @@ class Interface(Control): 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 set_vrf(self, vrf=''): """ Add/Remove interface from given VRF instance. @@ -773,14 +774,17 @@ class Interface(Control): on any interface. """ # Update interface description - self.set_alias(config.get('description', None)) + 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', []) - # XXX workaround for T2636, convert IP address string to a list - # with one element + # XXX: T2636 workaround: convert string to a list with one element if isinstance(new_addr, str): new_addr = [new_addr] @@ -800,6 +804,96 @@ class Interface(Control): # Bind interface instance into VRF self.set_vrf(config.get('vrf', '')) + # DHCP options + if 'dhcp_options' in config: + dhcp_options = config.get('dhcp_options') + if 'client_id' in dhcp_options: + self.dhcp.v4.options['client_id'] = dhcp_options.get('client_id') + + if 'host_name' in dhcp_options: + self.dhcp.v4.options['hostname'] = dhcp_options.get('host_name') + + if 'vendor_class_id' in dhcp_options: + self.dhcp.v4.options['vendor_class_id'] = dhcp_options.get('vendor_class_id') + + # DHCPv6 options + if 'dhcpv6_options' in config: + dhcpv6_options = config.get('dhcpv6_options') + if 'parameters_only' in dhcpv6_options: + self.dhcp.v6.options['dhcpv6_prm_only'] = True + + if 'temporary' in dhcpv6_options: + self.dhcp.v6.options['dhcpv6_temporary'] = True + + if 'prefix_delegation' in dhcpv6_options: + prefix_delegation = dhcpv6_options.get('prefix_delegation') + if 'length' in prefix_delegation: + self.dhcp.v6.options['dhcpv6_pd_length'] = prefix_delegation.get('length') + + if 'interface' in prefix_delegation: + self.dhcp.v6.options['dhcpv6_pd_interfaces'] = prefix_delegation.get('interface') + + # Configure ARP cache timeout in milliseconds - has default value + tmp = jmespath.search('ip.arp_cache_timeout', config) + value = tmp if (tmp != None) else '30' + self.set_arp_cache_tmo(value) + + # Configure ARP filter configuration + tmp = jmespath.search('ip.disable_arp_filter', config) + value = '0' if (tmp != None) else '1' + self.set_arp_filter(value) + + # Configure ARP accept + tmp = jmespath.search('ip.enable_arp_accept', config) + value = '1' if (tmp != None) else '0' + self.set_arp_accept(value) + + # Configure ARP announce + tmp = jmespath.search('ip.enable_arp_announce', config) + value = '1' if (tmp != None) else '0' + self.set_arp_announce(value) + + # Configure ARP ignore + tmp = jmespath.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 = jmespath.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 = jmespath.search('ip.proxy_arp_pvlan', config) + value = '1' if (tmp != None) else '0' + self.set_proxy_arp_pvlan(value) + + # IPv6 forwarding + tmp = jmespath.search('ipv6.disable_forwarding', config) + value = '0' if (tmp != None) else '1' + self.set_ipv6_forwarding(value) + + # IPv6 router advertisements + tmp = jmespath.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 = jmespath.search('ipv6.address.autoconf', config) + value = '1' if (tmp != None) else '0' + self.set_ipv6_autoconf(value) + + # IPv6 Duplicate Address Detection (DAD) tries + tmp = jmespath.search('ipv6.dup_addr_detect_transmits', config) + value = tmp if (tmp != None) else '1' + self.set_ipv6_dad_messages(value) + + # MTU - Maximum Transfer Unit + if 'mtu' in config: + self.set_mtu(config.get('mtu')) + # Interface administrative state - state = 'down' if 'disable' in config.keys() else 'up' + state = 'down' if 'disable' in config else 'up' self.set_admin_state(state) diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index 442cb0db8..ecb6796fa 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -16,6 +16,30 @@ from netifaces import interfaces from vyos import ConfigError +def get_removed_vlans(conf, 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(['vif'], expand_nodes=Diff.DELETE)['delete'].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(['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys() + dict['vif_s_remove'] = [*keys] + + for vif in dict.get('vif_s', {}).keys(): + keys = D.get_child_nodes_diff(['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys() + dict['vif_s'][vif]['vif_c_remove'] = [*keys] + + return dict + def apply_all_vlans(intf, intfconfig): """ Function applies all VLANs to the passed interface. diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 8b895c4d2..60aafae32 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -17,295 +17,108 @@ import os from sys import exit -from copy import deepcopy -from netifaces import interfaces +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.configdict import T2665_default_dict_cleanup +from vyos.configverify import verify_interface_exists +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_address +from vyos.configverify import verify_vrf +from vyos.configverify import verify_vlan_config from vyos.ifconfig import EthernetIf -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data +from vyos.ifconfig_vlan import get_removed_vlans from vyos.validate import is_member -from vyos.config import Config +from vyos.xml import defaults from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'deleted': False, - 'duplex': 'auto', - 'flow_control': 'on', - 'hw_id': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp_pvlan': 0, - 'is_bond_member': False, - 'intf': '', - 'offload_gro': 'off', - 'offload_gso': 'off', - 'offload_sg': 'off', - 'offload_tso': 'off', - 'offload_ufo': 'off', - 'speed': 'auto', - 'vif_s': {}, - 'vif_s_remove': [], - 'vif': {}, - 'vif_remove': [], - 'vrf': '' -} - def get_config(): + """ Retrive CLI config as dictionary. Dictionary can never be empty, + as at least the interface name will be added or a deleted flag """ + conf = Config() + # 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'] - conf = Config() - - # check if ethernet interface has been removed - cfg_base = ['interfaces', 'ethernet', ifname] - if not conf.exists(cfg_base): - eth = deepcopy(default_config_data) - eth['intf'] = ifname - eth['deleted'] = True - # we can not bail out early as ethernet interface can not be removed - # Kernel will complain with: RTNETLINK answers: Operation not supported. - # Thus we need to remove individual settings - return eth - - # set new configuration level - conf.set_level(cfg_base) - - eth, disabled = intf_to_dict(conf, default_config_data) - - # disable ethernet flow control (pause frames) - if conf.exists('disable-flow-control'): - eth['flow_control'] = 'off' - - # retrieve real hardware address - if conf.exists('hw-id'): - eth['hw_id'] = conf.return_value('hw-id') - - # interface duplex - if conf.exists('duplex'): - eth['duplex'] = conf.return_value('duplex') + # retrieve interface default values + base = ['interfaces', 'ethernet'] + default_values = defaults(base) - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - eth['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # Enable private VLAN proxy ARP on this interface - if conf.exists('ip proxy-arp-pvlan'): - eth['ip_proxy_arp_pvlan'] = 1 - - # check if we are a member of any bond - eth['is_bond_member'] = is_member(conf, eth['intf'], 'bonding') - - # GRO (generic receive offload) - if conf.exists('offload-options generic-receive'): - eth['offload_gro'] = conf.return_value('offload-options generic-receive') - - # GSO (generic segmentation offload) - if conf.exists('offload-options generic-segmentation'): - eth['offload_gso'] = conf.return_value('offload-options generic-segmentation') - - # scatter-gather option - if conf.exists('offload-options scatter-gather'): - eth['offload_sg'] = conf.return_value('offload-options scatter-gather') - - # TSO (TCP segmentation offloading) - if conf.exists('offload-options tcp-segmentation'): - eth['offload_tso'] = conf.return_value('offload-options tcp-segmentation') - - # UDP fragmentation offloading - if conf.exists('offload-options udp-fragmentation'): - eth['offload_ufo'] = conf.return_value('offload-options udp-fragmentation') - - # interface speed - if conf.exists('speed'): - eth['speed'] = conf.return_value('speed') - - # remove default IPv6 link-local address if member of a bond - if eth['is_bond_member'] and 'fe80::/64' in eth['ipv6_eui64_prefix']: - eth['ipv6_eui64_prefix'].remove('fe80::/64') - eth['ipv6_eui64_prefix_remove'].append('fe80::/64') - - add_to_dict(conf, disabled, eth, 'vif', 'vif') - add_to_dict(conf, disabled, eth, 'vif-s', 'vif_s') - - return eth - - -def verify(eth): - if eth['deleted']: + ifname = os.environ['VYOS_TAGNODE_VALUE'] + base = base + [ifname] + # setup config level which is extracted in get_removed_vlans() + conf.set_level(base) + ethernet = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + + # Check if interface has been removed + if ethernet == {}: + ethernet.update({'deleted' : ''}) + + # We have gathered the dict representation of the CLI, but there are + # default options which we need to update into the dictionary + # retrived. + ethernet = dict_merge(default_values, ethernet) + + # Add interface instance name into dictionary + ethernet.update({'ifname': ifname}) + + # Check if we are a member of a bridge device + bridge = is_member(conf, ifname, 'bridge') + if bridge: + tmp = {'is_bridge_member' : bridge} + ethernet.update(tmp) + + # Check if we are a member of a bond device + bond = is_member(conf, ifname, 'bonding') + if bond: + tmp = {'is_bond_member' : bond} + ethernet.update(tmp) + + ethernet = T2665_default_dict_cleanup( ethernet ) + # Check vif, vif-s/vif-c VLAN interfaces for removal + ethernet = get_removed_vlans( conf, ethernet ) + return ethernet + +def verify(ethernet): + if 'deleted' in ethernet.keys(): return None - if eth['intf'] not in interfaces(): - raise ConfigError(f"Interface ethernet {eth['intf']} does not exist") + verify_interface_exists(ethernet) - if eth['speed'] == 'auto': - if eth['duplex'] != 'auto': + if ethernet.get('speed', None) == 'auto': + if ethernet.get('duplex', None) != 'auto': raise ConfigError('If speed is hardcoded, duplex must be hardcoded, too') - if eth['duplex'] == 'auto': - if eth['speed'] != 'auto': + if ethernet.get('duplex', None) == 'auto': + if ethernet.get('speed', None) != 'auto': raise ConfigError('If duplex is hardcoded, speed must be hardcoded, too') - if eth['dhcpv6_prm_only'] and eth['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + verify_dhcpv6(ethernet) + verify_address(ethernet) + verify_vrf(ethernet) - memberof = eth['is_bridge_member'] if eth['is_bridge_member'] else eth['is_bond_member'] - - if ( memberof - and ( eth['address'] - or eth['ipv6_eui64_prefix'] - or eth['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{eth["intf"]}" ' - f'as it is a member of "{memberof}"!')) - - if eth['vrf']: - if eth['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{eth["vrf"]}" does not exist') - - if memberof: - raise ConfigError(( - f'Interface "{eth["intf"]}" cannot be member of VRF "{eth["vrf"]}" ' - f'and "{memberof}" at the same time!')) - - if eth['mac'] and eth['is_bond_member']: - print('WARNING: "mac {0}" command will be ignored because {1} is a part of {2}'\ - .format(eth['mac'], eth['intf'], eth['is_bond_member'])) + if {'is_bond_member', 'mac'} <= set(ethernet): + print(f'WARNING: changing mac address "{mac}" will be ignored as "{ifname}" ' + f'is a member of bond "{is_bond_member}"'.format(**ethernet)) # use common function to verify VLAN configuration - verify_vlan_config(eth) + verify_vlan_config(ethernet) return None -def generate(eth): +def generate(ethernet): return None -def apply(eth): - e = EthernetIf(eth['intf']) - if eth['deleted']: - # apply all vlans to interface (they need removing too) - apply_all_vlans(e, eth) - +def apply(ethernet): + e = EthernetIf(ethernet['ifname']) + if 'deleted' in ethernet.keys(): # delete interface e.remove() else: - # update interface description used e.g. within SNMP - e.set_alias(eth['description']) - - if eth['dhcp_client_id']: - e.dhcp.v4.options['client_id'] = eth['dhcp_client_id'] - - if eth['dhcp_hostname']: - e.dhcp.v4.options['hostname'] = eth['dhcp_hostname'] - - if eth['dhcp_vendor_class_id']: - e.dhcp.v4.options['vendor_class_id'] = eth['dhcp_vendor_class_id'] - - if eth['dhcpv6_prm_only']: - e.dhcp.v6.options['dhcpv6_prm_only'] = True - - if eth['dhcpv6_temporary']: - e.dhcp.v6.options['dhcpv6_temporary'] = True - - if eth['dhcpv6_pd_length']: - e.dhcp.v6.options['dhcpv6_pd_length'] = eth['dhcpv6_pd_length'] - - if eth['dhcpv6_pd_interfaces']: - e.dhcp.v6.options['dhcpv6_pd_interfaces'] = eth['dhcpv6_pd_interfaces'] - - # ignore link state changes - e.set_link_detect(eth['disable_link_detect']) - # disable ethernet flow control (pause frames) - e.set_flow_control(eth['flow_control']) - # configure ARP cache timeout in milliseconds - e.set_arp_cache_tmo(eth['ip_arp_cache_tmo']) - # configure ARP filter configuration - e.set_arp_filter(eth['ip_disable_arp_filter']) - # configure ARP accept - e.set_arp_accept(eth['ip_enable_arp_accept']) - # configure ARP announce - e.set_arp_announce(eth['ip_enable_arp_announce']) - # configure ARP ignore - e.set_arp_ignore(eth['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - e.set_proxy_arp(eth['ip_proxy_arp']) - # Enable private VLAN proxy ARP on this interface - e.set_proxy_arp_pvlan(eth['ip_proxy_arp_pvlan']) - # IPv6 accept RA - e.set_ipv6_accept_ra(eth['ipv6_accept_ra']) - # IPv6 address autoconfiguration - e.set_ipv6_autoconf(eth['ipv6_autoconf']) - # IPv6 forwarding - e.set_ipv6_forwarding(eth['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - e.set_ipv6_dad_messages(eth['ipv6_dup_addr_detect']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in eth['ipv6_eui64_prefix_remove']: - e.del_ipv6_eui64_address(addr) - - # Change interface MAC address - re-set to real hardware address (hw-id) - # if custom mac is removed. Skip if bond member. - if not eth['is_bond_member']: - if eth['mac']: - e.set_mac(eth['mac']) - elif eth['hw_id']: - e.set_mac(eth['hw_id']) - - # Add IPv6 EUI-based addresses - for addr in eth['ipv6_eui64_prefix']: - e.add_ipv6_eui64_address(addr) - - # Maximum Transmission Unit (MTU) - e.set_mtu(eth['mtu']) - - # GRO (generic receive offload) - e.set_gro(eth['offload_gro']) - - # GSO (generic segmentation offload) - e.set_gso(eth['offload_gso']) - - # scatter-gather option - e.set_sg(eth['offload_sg']) - - # TSO (TCP segmentation offloading) - e.set_tso(eth['offload_tso']) - - # UDP fragmentation offloading - e.set_ufo(eth['offload_ufo']) - - # Set physical interface speed and duplex - e.set_speed_duplex(eth['speed'], eth['duplex']) - - # Enable/Disable interface - if eth['disable']: - e.set_admin_state('down') - else: - e.set_admin_state('up') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in eth['address_remove']: - e.del_addr(addr) - for addr in eth['address']: - e.add_addr(addr) - - # assign/remove VRF (ONLY when not a member of a bridge or bond, - # otherwise 'nomaster' removes it from it) - if not ( eth['is_bridge_member'] or eth['is_bond_member'] ): - e.set_vrf(eth['vrf']) - - # re-add ourselves to any bridge we might have fallen out of - if eth['is_bridge_member']: - e.add_to_bridge(eth['is_bridge_member']) - - # apply all vlans to interface - apply_all_vlans(e, eth) + e.update(ethernet) if __name__ == '__main__': -- cgit v1.2.3 From 2b1c3dc86fe4033030855d61bf453aa730b6c230 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 19 Jul 2020 13:55:51 +0200 Subject: vlan: ifconfig: T2653: only enable interface when lower interface is up A VLAN interface can only be placed in admin up state when the lower interface is up, too. If this is not the case the operating system will throw and exception. --- python/vyos/ifconfig/interface.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 689faa22b..be3617f7d 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -17,7 +17,9 @@ import os import re import json import jmespath + from copy import deepcopy +from glob import glob from ipaddress import IPv4Network from ipaddress import IPv6Address @@ -73,8 +75,12 @@ class Interface(Control): _command_get = { 'admin_state': { 'shellcmd': 'ip -json link show dev {ifname}', - 'format': lambda j: 'up' if 'UP' in json.loads(j)[0]['flags'] else 'down', - } + 'format': lambda j: 'up' if 'UP' in jmespath.search('[*].flags | [0]', json.loads(j)) else 'down', + }, + 'vlan_protocol': { + 'shellcmd': 'ip -json -details link show dev {ifname}', + 'format': lambda j: jmespath.search('[*].linkinfo.info_data.protocol | [0]', json.loads(j)), + }, } _command_set = { @@ -544,6 +550,17 @@ class Interface(Control): """ self.set_interface('alias', ifalias) + def get_vlan_protocol(self): + """ + Retrieve VLAN protocol in use, this can be 802.1Q, 802.1ad or None + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth0.10').get_vlan_protocol() + '802.1Q' + """ + return self.get_interface('vlan_protocol') + def get_admin_state(self): """ Get interface administrative state. Function will return 'up' or 'down' @@ -565,6 +582,17 @@ class Interface(Control): >>> Interface('eth0').get_admin_state() 'down' """ + # A VLAN interface can only be placed in admin up state when + # the lower interface is up, too + if self.get_vlan_protocol(): + 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 self.set_interface('admin_state', state) def set_proxy_arp(self, enable): -- cgit v1.2.3 From a25d7095e009469d8ef60b63deddd94d30921723 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 19 Jul 2020 20:45:29 +0200 Subject: bridge: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. While providing a new update() method in vyos.ifconfig.interfaces() this is extended for bridge interfaces in the derived bridge class. Signed-off-by: Christian Poessinger --- interface-definitions/interfaces-bridge.xml.in | 7 + python/vyos/configdict.py | 97 ++++++ python/vyos/ifconfig/bridge.py | 68 +++- python/vyos/ifconfig/ethernet.py | 20 -- python/vyos/ifconfig/interface.py | 25 ++ python/vyos/ifconfig_vlan.py | 9 +- python/vyos/util.py | 2 +- src/conf_mode/interfaces-bridge.py | 413 +++++-------------------- src/conf_mode/interfaces-ethernet.py | 54 +--- 9 files changed, 296 insertions(+), 399 deletions(-) diff --git a/interface-definitions/interfaces-bridge.xml.in b/interface-definitions/interfaces-bridge.xml.in index 6b610e623..92356d696 100644 --- a/interface-definitions/interfaces-bridge.xml.in +++ b/interface-definitions/interfaces-bridge.xml.in @@ -32,6 +32,7 @@ + 300 #include #include @@ -51,6 +52,7 @@ Forwarding delay must be between 0 and 200 seconds + 14 @@ -64,6 +66,7 @@ Bridge Hello interval must be between 1 and 10 seconds + 2 @@ -107,6 +110,7 @@ Bridge max aging value must be between 1 and 40 seconds + 20 @@ -133,6 +137,7 @@ Path cost value must be between 1 and 65535 + 100 @@ -146,6 +151,7 @@ Port priority value must be between 0 and 63 + 32 @@ -163,6 +169,7 @@ Bridge priority must be between 0 and 65535 (multiples of 4096) + 32768 diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 682caed8f..4fca426cd 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -17,6 +17,7 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion. """ +import jmespath from enum import Enum from copy import deepcopy @@ -132,6 +133,102 @@ def T2665_default_dict_cleanup(dict): return dict +def leaf_node_changed(conf, key): + """ + Check if a leaf node was altered. If it has been altered - values has been + changed, or it was added/removed, we will return the old value. If nothing + has been changed, None is returned + """ + from vyos.configdiff import get_config_diff + + D = get_config_diff(conf, key_mangling=('-', '_')) + D.set_level(conf.get_level()) + (new, old) = D.get_value_diff(key) + if new != old: + if isinstance(old, str): + return old + elif isinstance(old, list): + if isinstance(new, str): + new = [new] + elif isinstance(new, type(None)): + new = [] + return list_diff(old, new) + + return None + +def get_interface_dict(config, base, ifname): + """ + Common utility function to retrieve and mandgle the interfaces available + in CLI configuration. All interfaces have a common base ground where the + value retrival is identical - so it can and should be reused + + Will return a dictionary with the necessary interface configuration + """ + from vyos.xml import defaults + from vyos.ifconfig_vlan import get_removed_vlans + + # retrieve interface default values + default_values = defaults(base) + + # setup config level which is extracted in get_removed_vlans() + config.set_level(base + [ifname]) + dict = config.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + + # Check if interface has been removed + if dict == {}: + dict.update({'deleted' : ''}) + + # Add interface instance name into dictionary + dict.update({'ifname': ifname}) + + # We have gathered the dict representation of the CLI, but there are + # default options which we need to update into the dictionary + # retrived. + dict = dict_merge(default_values, dict) + + # 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 we are a member of a bond device + bond = is_member(config, ifname, 'bonding') + if bond: + dict.update({'is_bond_member' : bond}) + + mac = leaf_node_changed(config, ['mac']) + if mac: + dict.update({'mac_old' : mac}) + + eui64 = leaf_node_changed(config, ['ipv6', 'address', 'eui64']) + if eui64: + # XXX: T2636 workaround: convert string to a list with one element + if isinstance(eui64, str): + eui64 = [eui64] + tmp = jmespath.search('ipv6.address', dict) + if not tmp: + dict.update({'ipv6': {'address': {'eui64_old': eui64}}}) + else: + dict['ipv6']['address'].update({'eui64_old': eui64}) + + # remove wrongly inserted values + dict = T2665_default_dict_cleanup(dict) + + # The values are identical for vif, vif-s and vif-c as the all include the same + # XML definitions which hold the defaults + default_vif_values = defaults(base + ['vif']) + for vif, vif_config in dict.get('vif', {}).items(): + vif_config.update(default_vif_values) + for vif_s, vif_s_config in dict.get('vif_s', {}).items(): + vif_s_config.update(default_vif_values) + for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): + vif_c_config.update(default_vif_values) + + # Check vif, vif-s/vif-c VLAN interfaces for removal + dict = get_removed_vlans(config, dict) + + return dict + def get_ethertype(ethertype_val): if ethertype_val == '0x88A8': return '802.1ad' diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 44b92c1db..af950b35d 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -13,12 +13,13 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . +import jmespath from vyos.ifconfig.interface import Interface - +from vyos.ifconfig.stp import STP from vyos.validate import assert_boolean from vyos.validate import assert_positive - +from vyos.util import cmd @Interface.register class BridgeIf(Interface): @@ -187,3 +188,66 @@ class BridgeIf(Interface): >>> BridgeIf('br0').del_port('eth1') """ return self.set_interface('del_port', interface) + + 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. """ + + # now call the regular function from within our base class + super().update(config) + + # 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 IGMP querier + tmp = jmespath.search('igmp.querier', config) + value = '1' if (tmp != None) else '0' + self.set_multicast_querier(value) + + # remove interface from bridge + tmp = jmespath.search('member.interface_remove', config) + if tmp: + for member in tmp: + self.del_port(member) + + STPBridgeIf = STP.enable(BridgeIf) + tmp = jmespath.search('member.interface', config) + if tmp: + for interface, interface_config in tmp.items(): + # if we've come here we already verified the interface doesn't + # have addresses configured so just flush any remaining ones + cmd(f'ip addr flush dev "{interface}"') + # enslave interface port to bridge + self.add_port(interface) + + tmp = STPBridgeIf(interface) + # set bridge port path cost + value = interface_config.get('cost') + tmp.set_path_cost(value) + + # set bridge port path priority + value = interface_config.get('priority') + tmp.set_path_priority(value) diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 8a50a8699..1725116e2 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -299,26 +299,6 @@ class EthernetIf(Interface): duplex = config.get('duplex') self.set_speed_duplex(speed, duplex) - # Delete old IPv6 EUI64 addresses before changing MAC - - # 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) - - # Add IPv6 EUI-based addresses - tmp = jmespath.search('ipv6.address.eui64', config) - if tmp: - # XXX: T2636 workaround: convert string to a list with one element - if isinstance(tmp, str): - tmp = [tmp] - for addr in tmp: - self.add_ipv6_eui64_address(addr) - # re-add ourselves to any bridge we might have fallen out of if 'is_bridge_member' in config: bridge = config.get('is_bridge_member') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index be3617f7d..ea770af23 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -922,6 +922,31 @@ class Interface(Control): if 'mtu' in config: self.set_mtu(config.get('mtu')) + # Delete old IPv6 EUI64 addresses before changing MAC + tmp = jmespath.search('ipv6.address.eui64_old', config) + if tmp: + for addr in tmp: + self.del_ipv6_eui64_address(addr) + + # 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) + + # Add IPv6 EUI-based addresses + tmp = jmespath.search('ipv6.address.eui64', config) + if tmp: + # XXX: T2636 workaround: convert string to a list with one element + if isinstance(tmp, str): + tmp = [tmp] + for addr in tmp: + self.add_ipv6_eui64_address(addr) + + # Interface administrative state state = 'down' if 'disable' in config else 'up' self.set_admin_state(state) diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index ecb6796fa..0e4ecda53 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -28,15 +28,18 @@ def get_removed_vlans(conf, dict): 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(['vif'], expand_nodes=Diff.DELETE)['delete'].keys() - dict['vif_remove'] = [*keys] + if keys: + dict.update({'vif_remove': [*keys]}) # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 keys = D.get_child_nodes_diff(['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys() - dict['vif_s_remove'] = [*keys] + if keys: + dict.update({'vif_s_remove': [*keys]}) for vif in dict.get('vif_s', {}).keys(): keys = D.get_child_nodes_diff(['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys() - dict['vif_s'][vif]['vif_c_remove'] = [*keys] + if keys: + dict.update({'vif_s': { vif : {'vif_c_remove': [*keys]}}}) return dict diff --git a/python/vyos/util.py b/python/vyos/util.py index 7234be6cb..7078762df 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -242,7 +242,7 @@ def chown(path, user, group): if not os.path.exists(path): return False - + uid = getpwnam(user).pw_uid gid = getgrnam(group).gr_gid os.chown(path, uid, gid) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 1e4fa5816..7998a251a 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -16,251 +16,116 @@ import os -from copy import deepcopy from sys import exit from netifaces import interfaces -from vyos.ifconfig import BridgeIf, Section -from vyos.ifconfig.stp import STP -from vyos.configdict import list_diff, interface_default_data -from vyos.validate import is_member, has_address_configured from vyos.config import Config -from vyos.util import cmd, get_bridge_member_config +from vyos.configdict import get_interface_dict +from vyos.configdiff import get_config_diff, Diff +from vyos.configverify import verify_dhcpv6 +from vyos.configverify import verify_vrf +from vyos.ifconfig import BridgeIf +from vyos.validate import is_member, has_address_configured +from vyos.xml import defaults + +from vyos.util import cmd from vyos import ConfigError from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'aging': 300, - 'arp_cache_tmo': 30, - 'deleted': False, - 'forwarding_delay': 14, - 'hello_time': 2, - 'igmp_querier': 0, - 'intf': '', - 'max_age': 20, - 'member': [], - 'member_remove': [], - 'priority': 32768, - 'stp': 0 -} +def get_removed_members(conf): + 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(['member', 'interface'], expand_nodes=Diff.DELETE)['delete'].keys() + return list(keys) def get_config(): - bridge = deepcopy(default_config_data) + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'bridge'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - bridge['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # Check if bridge has been removed - if not conf.exists('interfaces bridge ' + bridge['intf']): - bridge['deleted'] = True - return bridge - - # set new configuration level - conf.set_level('interfaces bridge ' + bridge['intf']) - - # retrieve configured interface addresses - if conf.exists('address'): - bridge['address'] = conf.return_values('address') - - # Determine interface addresses (currently effective) - to determine which - # address is no longer valid and needs to be removed - eff_addr = conf.return_effective_values('address') - bridge['address_remove'] = list_diff(eff_addr, bridge['address']) - - # retrieve aging - how long addresses are retained - if conf.exists('aging'): - bridge['aging'] = int(conf.return_value('aging')) - - # retrieve interface description - if conf.exists('description'): - bridge['description'] = conf.return_value('description') - - # get DHCP client identifier - if conf.exists('dhcp-options client-id'): - bridge['dhcp_client_id'] = conf.return_value('dhcp-options client-id') - - # DHCP client host name (overrides the system host name) - if conf.exists('dhcp-options host-name'): - bridge['dhcp_hostname'] = conf.return_value('dhcp-options host-name') - - # DHCP client vendor identifier - if conf.exists('dhcp-options vendor-class-id'): - bridge['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id') - - # DHCPv6 only acquire config parameters, no address - if conf.exists('dhcpv6-options parameters-only'): - bridge['dhcpv6_prm_only'] = True - - # DHCPv6 temporary IPv6 address - if conf.exists('dhcpv6-options temporary'): - bridge['dhcpv6_temporary'] = True - - # Disable this bridge interface - if conf.exists('disable'): - bridge['disable'] = True - - # Ignore link state changes - if conf.exists('disable-link-detect'): - bridge['disable_link_detect'] = 2 - - # Forwarding delay - if conf.exists('forwarding-delay'): - bridge['forwarding_delay'] = int(conf.return_value('forwarding-delay')) - - # Hello packet advertisment interval - if conf.exists('hello-time'): - bridge['hello_time'] = int(conf.return_value('hello-time')) - - # Enable Internet Group Management Protocol (IGMP) querier - if conf.exists('igmp querier'): - bridge['igmp_querier'] = 1 - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - bridge['arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # ARP filter configuration - if conf.exists('ip disable-arp-filter'): - bridge['ip_disable_arp_filter'] = 0 - - # ARP enable accept - if conf.exists('ip enable-arp-accept'): - bridge['ip_enable_arp_accept'] = 1 - - # ARP enable announce - if conf.exists('ip enable-arp-announce'): - bridge['ip_enable_arp_announce'] = 1 - - # ARP enable ignore - if conf.exists('ip enable-arp-ignore'): - bridge['ip_enable_arp_ignore'] = 1 - - # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) - if conf.exists('ipv6 address autoconf'): - bridge['ipv6_autoconf'] = 1 - - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - if conf.exists('ipv6 address eui64'): - bridge['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - - # Determine currently effective EUI64 addresses - to determine which - # address is no longer valid and needs to be removed - eff_addr = conf.return_effective_values('ipv6 address eui64') - bridge['ipv6_eui64_prefix_remove'] = list_diff(eff_addr, bridge['ipv6_eui64_prefix']) - - # Remove the default link-local address if set. - if conf.exists('ipv6 address no-default-link-local'): - bridge['ipv6_eui64_prefix_remove'].append('fe80::/64') - else: - # add the link-local by default to make IPv6 work - bridge['ipv6_eui64_prefix'].append('fe80::/64') - - # Disable IPv6 forwarding on this interface - if conf.exists('ipv6 disable-forwarding'): - bridge['ipv6_forwarding'] = 0 - - # IPv6 Duplicate Address Detection (DAD) tries - if conf.exists('ipv6 dup-addr-detect-transmits'): - bridge['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) - - # Media Access Control (MAC) address - if conf.exists('mac'): - bridge['mac'] = conf.return_value('mac') - - # Find out if MAC has changed - if so, we need to delete all IPv6 EUI64 addresses - # before re-adding them - if ( bridge['mac'] and bridge['intf'] in Section.interfaces(section='bridge') - and bridge['mac'] != BridgeIf(bridge['intf'], create=False).get_mac() ): - bridge['ipv6_eui64_prefix_remove'] += bridge['ipv6_eui64_prefix'] - - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, - # accept_ra must be 2 - if bridge['ipv6_autoconf'] or 'dhcpv6' in bridge['address']: - bridge['ipv6_accept_ra'] = 2 - - # Interval at which neighbor bridges are removed - if conf.exists('max-age'): - bridge['max_age'] = int(conf.return_value('max-age')) - - # Determine bridge member interface (currently configured) - for intf in conf.list_nodes('member interface'): - # defaults are stored in util.py (they can't be here as all interface - # scripts use the function) - memberconf = get_bridge_member_config(conf, bridge['intf'], intf) - if memberconf: - memberconf['name'] = intf - bridge['member'].append(memberconf) - - # Determine bridge member interface (currently effective) - to determine which - # interfaces is no longer assigend to the bridge and thus can be removed - eff_intf = conf.list_effective_nodes('member interface') - act_intf = conf.list_nodes('member interface') - bridge['member_remove'] = list_diff(eff_intf, act_intf) - - # Priority for this bridge - if conf.exists('priority'): - bridge['priority'] = int(conf.return_value('priority')) - - # Enable spanning tree protocol - if conf.exists('stp'): - bridge['stp'] = 1 - - # retrieve VRF instance - if conf.exists('vrf'): - bridge['vrf'] = conf.return_value('vrf') + ifname = os.environ['VYOS_TAGNODE_VALUE'] + bridge = get_interface_dict(conf, base, ifname) + + # determine which members have been removed + tmp = get_removed_members(conf) + if tmp: + if 'member' in bridge: + bridge['member'].update({'interface_remove': tmp }) + else: + bridge.update({'member': {'interface_remove': tmp }}) + + if 'member' in bridge and 'interface' in bridge['member']: + # XXX TT2665 we need a copy of the dict keys for iteration, else we will get: + # RuntimeError: dictionary changed size during iteration + for interface in list(bridge['member']['interface']): + for key in ['cost', 'priority']: + if interface == key: + del bridge['member']['interface'][key] + continue + + # the default dictionary is not properly paged into the dict (see T2665) + # thus we will ammend it ourself + default_member_values = defaults(base + ['member', 'interface']) + + for interface, interface_config in bridge['member']['interface'].items(): + interface_config.update(default_member_values) + + # Check if we are a member of another bridge device + tmp = is_member(conf, interface, 'bridge') + if tmp and tmp != ifname: + interface_config.update({'is_bridge_member' : tmp}) + + # Check if we are a member of a bond device + tmp = is_member(conf, interface, 'bonding') + if tmp: + interface_config.update({'is_bond_member' : tmp}) + + # Bridge members must not have an assigned address + tmp = has_address_configured(conf, interface) + if tmp: + interface_config.update({'has_address' : ''}) return bridge def verify(bridge): - if bridge['dhcpv6_prm_only'] and bridge['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + if 'deleted' in bridge: + return None - vrf_name = bridge['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + verify_dhcpv6(bridge) + verify_vrf(bridge) - conf = Config() - for intf in bridge['member']: - # the interface must exist prior adding it to a bridge - if intf['name'] not in interfaces(): - raise ConfigError(( - f'Cannot add nonexistent interface "{intf["name"]}" ' - f'to bridge "{bridge["intf"]}"')) + if 'member' in bridge: + member = bridge.get('member') + bridge_name = bridge['ifname'] + for interface, interface_config in member.get('interface', {}).items(): + error_msg = f'Can not add interface "{interface}" to bridge "{bridge_name}", ' - if intf['name'] == 'lo': - raise ConfigError('Loopback interface "lo" can not be added to a bridge') + if interface == 'lo': + raise ConfigError('Loopback interface "lo" can not be added to a bridge') - # bridge members aren't allowed to be members of another bridge - for br in conf.list_nodes('interfaces bridge'): - # it makes no sense to verify ourself in this case - if br == bridge['intf']: - continue + if interface not in interfaces(): + raise ConfigError(error_msg + 'it does not exist!') - tmp = conf.list_nodes(f'interfaces bridge {br} member interface') - if intf['name'] in tmp: - raise ConfigError(( - f'Cannot add interface "{intf["name"]}" to bridge ' - f'"{bridge["intf"]}", it is already a member of bridge "{br}"!')) + if 'is_bridge_member' in interface_config: + tmp = interface_config['is_bridge_member'] + raise ConfigError(error_msg + f'it is already a member of bridge "{tmp}"!') - # bridge members are not allowed to be bond members - tmp = is_member(conf, intf['name'], 'bonding') - if tmp: - raise ConfigError(( - f'Cannot add interface "{intf["name"]}" to bridge ' - f'"{bridge["intf"]}", it is already a member of bond "{tmp}"!')) + if 'is_bond_member' in interface_config: + tmp = interface_config['is_bond_member'] + raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') - # bridge members must not have an assigned address - if has_address_configured(conf, intf['name']): - raise ConfigError(( - f'Cannot add interface "{intf["name"]}" to bridge ' - f'"{bridge["intf"]}", it has an address assigned!')) + if 'has_address' in interface_config: + raise ConfigError(error_msg + 'it has an address assigned!') return None @@ -268,120 +133,12 @@ def generate(bridge): return None def apply(bridge): - br = BridgeIf(bridge['intf']) - - if bridge['deleted']: + br = BridgeIf(bridge['ifname']) + if 'deleted' in bridge: # delete interface br.remove() else: - # enable interface - br.set_admin_state('up') - # set ageing time - br.set_ageing_time(bridge['aging']) - # set bridge forward delay - br.set_forward_delay(bridge['forwarding_delay']) - # set hello time - br.set_hello_time(bridge['hello_time']) - # configure ARP filter configuration - br.set_arp_filter(bridge['ip_disable_arp_filter']) - # configure ARP accept - br.set_arp_accept(bridge['ip_enable_arp_accept']) - # configure ARP announce - br.set_arp_announce(bridge['ip_enable_arp_announce']) - # configure ARP ignore - br.set_arp_ignore(bridge['ip_enable_arp_ignore']) - # IPv6 accept RA - br.set_ipv6_accept_ra(bridge['ipv6_accept_ra']) - # IPv6 address autoconfiguration - br.set_ipv6_autoconf(bridge['ipv6_autoconf']) - # IPv6 forwarding - br.set_ipv6_forwarding(bridge['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - br.set_ipv6_dad_messages(bridge['ipv6_dup_addr_detect']) - # set max message age - br.set_max_age(bridge['max_age']) - # set bridge priority - br.set_priority(bridge['priority']) - # turn stp on/off - br.set_stp(bridge['stp']) - # enable or disable IGMP querier - br.set_multicast_querier(bridge['igmp_querier']) - # update interface description used e.g. within SNMP - br.set_alias(bridge['description']) - - if bridge['dhcp_client_id']: - br.dhcp.v4.options['client_id'] = bridge['dhcp_client_id'] - - if bridge['dhcp_hostname']: - br.dhcp.v4.options['hostname'] = bridge['dhcp_hostname'] - - if bridge['dhcp_vendor_class_id']: - br.dhcp.v4.options['vendor_class_id'] = bridge['dhcp_vendor_class_id'] - - if bridge['dhcpv6_prm_only']: - br.dhcp.v6.options['dhcpv6_prm_only'] = True - - if bridge['dhcpv6_temporary']: - br.dhcp.v6.options['dhcpv6_temporary'] = True - - if bridge['dhcpv6_pd_length']: - br.dhcp.v6.options['dhcpv6_pd_length'] = br['dhcpv6_pd_length'] - - if bridge['dhcpv6_pd_interfaces']: - br.dhcp.v6.options['dhcpv6_pd_interfaces'] = br['dhcpv6_pd_interfaces'] - - # assign/remove VRF - br.set_vrf(bridge['vrf']) - - # Delete old IPv6 EUI64 addresses before changing MAC - # (adding members to a fresh bridge changes its MAC too) - for addr in bridge['ipv6_eui64_prefix_remove']: - br.del_ipv6_eui64_address(addr) - - # remove interface from bridge - for intf in bridge['member_remove']: - br.del_port(intf) - - # add interfaces to bridge - for member in bridge['member']: - # if we've come here we already verified the interface doesn't - # have addresses configured so just flush any remaining ones - cmd(f'ip addr flush dev "{member["name"]}"') - br.add_port(member['name']) - - # Change interface MAC address - if bridge['mac']: - br.set_mac(bridge['mac']) - - # Add IPv6 EUI-based addresses (must be done after adding the - # 1st bridge member or setting its MAC) - for addr in bridge['ipv6_eui64_prefix']: - br.add_ipv6_eui64_address(addr) - - # up/down interface - if bridge['disable']: - br.set_admin_state('down') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in bridge['address_remove']: - br.del_addr(addr) - for addr in bridge['address']: - br.add_addr(addr) - - STPBridgeIf = STP.enable(BridgeIf) - # configure additional bridge member options - for member in bridge['member']: - i = STPBridgeIf(member['name']) - # configure ARP cache timeout - i.set_arp_cache_tmo(member['arp_cache_tmo']) - # ignore link state changes - i.set_link_detect(member['disable_link_detect']) - # set bridge port path cost - i.set_path_cost(member['cost']) - # set bridge port path priority - i.set_path_priority(member['priority']) + br.update(bridge) return None diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 60aafae32..d43552e50 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -19,72 +19,36 @@ import os from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.configdict import T2665_default_dict_cleanup +from vyos.configdict import get_interface_dict from vyos.configverify import verify_interface_exists from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_address from vyos.configverify import verify_vrf from vyos.configverify import verify_vlan_config from vyos.ifconfig import EthernetIf -from vyos.ifconfig_vlan import get_removed_vlans -from vyos.validate import is_member -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() def get_config(): - """ Retrive CLI config as dictionary. Dictionary can never be empty, - as at least the interface name will be added or a deleted flag """ + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'ethernet'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - # retrieve interface default values - base = ['interfaces', 'ethernet'] - default_values = defaults(base) - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = base + [ifname] - # setup config level which is extracted in get_removed_vlans() - conf.set_level(base) - ethernet = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) - - # Check if interface has been removed - if ethernet == {}: - ethernet.update({'deleted' : ''}) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary - # retrived. - ethernet = dict_merge(default_values, ethernet) - - # Add interface instance name into dictionary - ethernet.update({'ifname': ifname}) - - # Check if we are a member of a bridge device - bridge = is_member(conf, ifname, 'bridge') - if bridge: - tmp = {'is_bridge_member' : bridge} - ethernet.update(tmp) - - # Check if we are a member of a bond device - bond = is_member(conf, ifname, 'bonding') - if bond: - tmp = {'is_bond_member' : bond} - ethernet.update(tmp) - - ethernet = T2665_default_dict_cleanup( ethernet ) - # Check vif, vif-s/vif-c VLAN interfaces for removal - ethernet = get_removed_vlans( conf, ethernet ) + ethernet = get_interface_dict(conf, base, ifname) return ethernet def verify(ethernet): - if 'deleted' in ethernet.keys(): + if 'deleted' in ethernet: return None verify_interface_exists(ethernet) @@ -114,7 +78,7 @@ def generate(ethernet): def apply(ethernet): e = EthernetIf(ethernet['ifname']) - if 'deleted' in ethernet.keys(): + if 'deleted' in ethernet: # delete interface e.remove() else: -- cgit v1.2.3 From c8cd7951e38ae2819d4c9f87089fcf59b7e6b70d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 19 Jul 2020 22:25:12 +0200 Subject: pseudo-ethernet: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. While providing a new update() method in vyos.ifconfig.interfaces() this is extended for pdeudo-ethernet interfaces in the derived class. --- .../interfaces-pseudo-ethernet.xml.in | 1 + src/conf_mode/interfaces-pseudo-ethernet.py | 216 +++------------------ 2 files changed, 32 insertions(+), 185 deletions(-) diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index d5f9ca661..0ef45e2c2 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -70,6 +70,7 @@ mode must be private, vepa, bridge or passthru + private #include #include diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index fb8237bee..cce9b020b 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -18,115 +18,52 @@ import os from copy import deepcopy from sys import exit -from netifaces import interfaces from vyos.config import Config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data -from vyos.ifconfig import MACVLANIf, Section -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config +from vyos.configdict import get_interface_dict +from vyos.configdict import leaf_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.ifconfig import MACVLANIf from vyos import ConfigError from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'deleted': False, - 'intf': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp_pvlan': 0, - 'source_interface': '', - 'recreating_required': False, - 'mode': 'private', - 'vif_s': {}, - 'vif_s_remove': [], - 'vif': {}, - 'vif_remove': [], - 'vrf': '' -} - def get_config(): - peth = deepcopy(default_config_data) + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'pseudo-ethernet'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - peth['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # Check if interface has been removed - cfg_base = ['interfaces', 'pseudo-ethernet', peth['intf']] - if not conf.exists(cfg_base): - peth['deleted'] = True - return peth - - # set new configuration level - conf.set_level(cfg_base) - - peth, disabled = intf_to_dict(conf, default_config_data) - - # ARP cache entry timeout in seconds - if conf.exists(['ip', 'arp-cache-timeout']): - peth['ip_arp_cache_tmo'] = int(conf.return_value(['ip', 'arp-cache-timeout'])) - - # Enable private VLAN proxy ARP on this interface - if conf.exists(['ip', 'proxy-arp-pvlan']): - peth['ip_proxy_arp_pvlan'] = 1 - - # Physical interface - if conf.exists(['source-interface']): - peth['source_interface'] = conf.return_value(['source-interface']) - tmp = conf.return_effective_value(['source-interface']) - if tmp != peth['source_interface']: - peth['recreating_required'] = True - - # MACvlan mode - if conf.exists(['mode']): - peth['mode'] = conf.return_value(['mode']) - tmp = conf.return_effective_value(['mode']) - if tmp != peth['mode']: - peth['recreating_required'] = True + ifname = os.environ['VYOS_TAGNODE_VALUE'] + peth = get_interface_dict(conf, base, ifname) - add_to_dict(conf, disabled, peth, 'vif', 'vif') - add_to_dict(conf, disabled, peth, 'vif-s', 'vif_s') + mode = leaf_node_changed(conf, ['mode']) + if mode: + peth.update({'mode_old' : mode}) + import pprint + pprint.pprint(peth) return peth def verify(peth): - if peth['deleted']: - if peth['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{peth["intf"]}" as it is a ' - f'member of bridge "{peth["is_bridge_member"]}"!')) - + if 'deleted' in peth: + verify_bridge_delete(peth) return None - if not peth['source_interface']: - raise ConfigError(( - f'Link device must be set for pseudo-ethernet "{peth["intf"]}"')) - - if not peth['source_interface'] in interfaces(): - raise ConfigError(( - f'Pseudo-ethernet "{peth["intf"]}" link device does not exist')) - - if ( peth['is_bridge_member'] - and ( peth['address'] - or peth['ipv6_eui64_prefix'] - or peth['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{peth["intf"]}" ' - f'as it is a member of bridge "{peth["is_bridge_member"]}"!')) - - if peth['vrf']: - if peth['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{peth["vrf"]}" does not exist') - - if peth['is_bridge_member']: - raise ConfigError(( - f'Interface "{peth["intf"]}" cannot be member of VRF ' - f'"{peth["vrf"]}" and bridge {peth["is_bridge_member"]} ' - f'at the same time!')) + verify_source_interface(peth) + verify_vrf(peth) + verify_address(peth) # use common function to verify VLAN configuration verify_vlan_config(peth) @@ -136,17 +73,16 @@ def generate(peth): return None def apply(peth): - if peth['deleted']: + if 'deleted' in peth: # delete interface - MACVLANIf(peth['intf']).remove() + MACVLANIf(peth['ifname']).remove() return None # Check if MACVLAN interface already exists. Parameters like the underlaying # source-interface device or mode can not be changed on the fly and the interface # needs to be recreated from the bottom. - if peth['intf'] in interfaces(): - if peth['recreating_required']: - MACVLANIf(peth['intf']).remove() + if 'mode_old' in peth: + MACVLANIf(peth['ifname']).remove() # MACVLAN interface needs to be created on-block instead of passing a ton # of arguments, I just use a dict that is managed by vyos.ifconfig @@ -158,98 +94,8 @@ def apply(peth): # 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 - p = MACVLANIf(peth['intf'], **conf) - - # update interface description used e.g. within SNMP - p.set_alias(peth['description']) - - if peth['dhcp_client_id']: - p.dhcp.v4.options['client_id'] = peth['dhcp_client_id'] - - if peth['dhcp_hostname']: - p.dhcp.v4.options['hostname'] = peth['dhcp_hostname'] - - if peth['dhcp_vendor_class_id']: - p.dhcp.v4.options['vendor_class_id'] = peth['dhcp_vendor_class_id'] - - if peth['dhcpv6_prm_only']: - p.dhcp.v6.options['dhcpv6_prm_only'] = True - - if peth['dhcpv6_temporary']: - p.dhcp.v6.options['dhcpv6_temporary'] = True - - if peth['dhcpv6_pd_length']: - p.dhcp.v6.options['dhcpv6_pd_length'] = peth['dhcpv6_pd_length'] - - if peth['dhcpv6_pd_interfaces']: - p.dhcp.v6.options['dhcpv6_pd_interfaces'] = peth['dhcpv6_pd_interfaces'] - - # ignore link state changes - p.set_link_detect(peth['disable_link_detect']) - # configure ARP cache timeout in milliseconds - p.set_arp_cache_tmo(peth['ip_arp_cache_tmo']) - # configure ARP filter configuration - p.set_arp_filter(peth['ip_disable_arp_filter']) - # configure ARP accept - p.set_arp_accept(peth['ip_enable_arp_accept']) - # configure ARP announce - p.set_arp_announce(peth['ip_enable_arp_announce']) - # configure ARP ignore - p.set_arp_ignore(peth['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - p.set_proxy_arp(peth['ip_proxy_arp']) - # Enable private VLAN proxy ARP on this interface - p.set_proxy_arp_pvlan(peth['ip_proxy_arp_pvlan']) - # IPv6 accept RA - p.set_ipv6_accept_ra(peth['ipv6_accept_ra']) - # IPv6 address autoconfiguration - p.set_ipv6_autoconf(peth['ipv6_autoconf']) - # IPv6 forwarding - p.set_ipv6_forwarding(peth['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - p.set_ipv6_dad_messages(peth['ipv6_dup_addr_detect']) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not peth['is_bridge_member']: - p.set_vrf(peth['vrf']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in peth['ipv6_eui64_prefix_remove']: - p.del_ipv6_eui64_address(addr) - - # Change interface MAC address - if peth['mac']: - p.set_mac(peth['mac']) - - # Add IPv6 EUI-based addresses - for addr in peth['ipv6_eui64_prefix']: - p.add_ipv6_eui64_address(addr) - - # Change interface mode - p.set_mode(peth['mode']) - - # Enable/Disable interface - if peth['disable']: - p.set_admin_state('down') - else: - p.set_admin_state('up') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in peth['address_remove']: - p.del_addr(addr) - for addr in peth['address']: - p.add_addr(addr) - - # re-add ourselves to any bridge we might have fallen out of - if peth['is_bridge_member']: - p.add_to_bridge(peth['is_bridge_member']) - - # apply all vlans to interface - apply_all_vlans(p, peth) - + p = MACVLANIf(peth['ifname'], **conf) + p.update(peth) return None if __name__ == '__main__': -- cgit v1.2.3 From c9ba8952ad7c373d633516933ddb97e178e339c8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 20 Jul 2020 21:45:24 +0200 Subject: interfaces: ifconfig: T2653: migrate to get_interface_dict() API After switching from raw parsing of the interface options to get_config_dict() this utilizes another utility function which wraps get_config_dict() and adds other common and reused parameters (like deleted or bridge member). Overall this drops redundant code (again) and makes the rest more maintainable as we only utilize a single function. --- src/conf_mode/interfaces-dummy.py | 26 ++++--------- src/conf_mode/interfaces-ethernet.py | 1 - src/conf_mode/interfaces-loopback.py | 24 +++++------- src/conf_mode/interfaces-macsec.py | 57 +++++++++-------------------- src/conf_mode/interfaces-pppoe.py | 43 ++++++++-------------- src/conf_mode/interfaces-pseudo-ethernet.py | 2 - src/conf_mode/interfaces-wirelessmodem.py | 40 +++++++------------- 7 files changed, 64 insertions(+), 129 deletions(-) diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 2d62420a6..6d2a78e30 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -19,41 +19,29 @@ import os 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.ifconfig import DummyIf -from vyos.validate import is_member from vyos import ConfigError from vyos import airbag airbag.enable() def get_config(): - """ Retrive CLI config as dictionary. Dictionary can never be empty, - as at least the interface name will be added or a deleted flag """ + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'dummy'] # 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'] - base = ['interfaces', 'dummy', ifname] - - dummy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Check if interface has been removed - if dummy == {}: - dummy.update({'deleted' : ''}) - - # store interface instance name in dictionary - dummy.update({'ifname': ifname}) - - # check if we are a member of any bridge - bridge = is_member(conf, ifname, 'bridge') - if bridge: - tmp = {'is_bridge_member' : bridge} - dummy.update(tmp) - + dummy = get_interface_dict(conf, base, ifname) return dummy def verify(dummy): diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index d43552e50..24ea0af7c 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -30,7 +30,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() - def get_config(): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index 2368f88a9..68a1392ff 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -18,31 +18,27 @@ import os from sys import exit -from vyos.ifconfig import LoopbackIf from vyos.config import Config -from vyos import ConfigError, airbag +from vyos.configdict import get_interface_dict +from vyos.ifconfig import LoopbackIf +from vyos import ConfigError +from vyos import airbag airbag.enable() def get_config(): - """ Retrive CLI config as dictionary. Dictionary can never be empty, - as at least the interface name will be added or a deleted flag """ + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'loopback'] # 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'] - base = ['interfaces', 'loopback', ifname] - - loopback = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Check if interface has been removed - if loopback == {}: - loopback.update({'deleted' : ''}) - - # store interface instance name in dictionary - loopback.update({'ifname': ifname}) - + loopback = get_interface_dict(conf, base, ifname) return loopback def verify(loopback): diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 56273f71a..06aee9ea0 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -20,16 +20,14 @@ from copy import deepcopy from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict from vyos.ifconfig import MACsecIf from vyos.template import render from vyos.util import call -from vyos.validate import is_member 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.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -38,50 +36,31 @@ airbag.enable() wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf' def get_config(): - """ Retrive CLI config as dictionary. Dictionary can never be empty, - as at least the interface name will be added or a deleted flag """ + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'macsec'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - # retrieve interface default values - base = ['interfaces', 'macsec'] - default_values = defaults(base) - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = base + [ifname] + macsec = get_interface_dict(conf, base, ifname) - macsec = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) # Check if interface has been removed - if macsec == {}: - tmp = { - 'deleted' : '', - 'source_interface' : conf.return_effective_value( + if 'deleted' in macsec: + source_interface = conf.return_effective_value( base + ['source-interface']) - } - macsec.update(tmp) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary - # retrived. - macsec = dict_merge(default_values, macsec) - - # Add interface instance name into dictionary - macsec.update({'ifname': ifname}) - - # Check if we are a member of any bridge - bridge = is_member(conf, ifname, 'bridge') - if bridge: - tmp = {'is_bridge_member' : bridge} - macsec.update(tmp) + macsec.update({'source_interface': source_interface}) return macsec def verify(macsec): - if 'deleted' in macsec.keys(): + if 'deleted' in macsec: verify_bridge_delete(macsec) return None @@ -89,18 +68,18 @@ def verify(macsec): verify_vrf(macsec) verify_address(macsec) - if not (('security' in macsec.keys()) and - ('cipher' in macsec['security'].keys())): + if not (('security' in macsec) and + ('cipher' in macsec['security'])): raise ConfigError( 'Cipher suite must be set for MACsec "{ifname}"'.format(**macsec)) - if (('security' in macsec.keys()) and - ('encrypt' in macsec['security'].keys())): + if (('security' in macsec) and + ('encrypt' in macsec['security'])): tmp = macsec.get('security') - if not (('mka' in tmp.keys()) and - ('cak' in tmp['mka'].keys()) and - ('ckn' in tmp['mka'].keys())): + if not (('mka' in tmp) and + ('cak' in tmp['mka']) and + ('ckn' in tmp['mka'])): raise ConfigError('Missing mandatory MACsec security ' 'keys as encryption is enabled!') diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 3ee57e83c..6947cc1e2 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -22,51 +22,40 @@ from copy import deepcopy from netifaces import interfaces from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict from vyos.configverify import verify_source_interface from vyos.configverify import verify_vrf from vyos.template import render from vyos.util import call -from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() def get_config(): - """ Retrive CLI config as dictionary. Dictionary can never be empty, - as at least the interface name will be added or a deleted flag """ + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'pppoe'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - # retrieve interface default values - base = ['interfaces', 'pppoe'] - default_values = defaults(base) - # PPPoE is "special" the default MTU is 1492 - update accordingly - default_values['mtu'] = '1492' - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = base + [ifname] + pppoe = get_interface_dict(conf, base, ifname) - pppoe = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Check if interface has been removed - if pppoe == {}: - pppoe.update({'deleted' : ''}) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary - # retrived. - pppoe = dict_merge(default_values, pppoe) - - # Add interface instance name into dictionary - pppoe.update({'ifname': ifname}) + # PPPoE is "special" the default MTU is 1492 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + pppoe['mtu'] = '1492' return pppoe def verify(pppoe): - if 'deleted' in pppoe.keys(): + if 'deleted' in pppoe: # bail out early return None @@ -92,7 +81,7 @@ def generate(pppoe): config_files = [config_pppoe, script_pppoe_pre_up, script_pppoe_ip_up, script_pppoe_ip_down, script_pppoe_ipv6_up, config_wide_dhcp6c] - if 'deleted' in pppoe.keys(): + if 'deleted' in pppoe: # stop DHCPv6-PD client call(f'systemctl stop dhcp6c@{ifname}.service') # Hang-up PPPoE connection @@ -130,11 +119,11 @@ def generate(pppoe): return None def apply(pppoe): - if 'deleted' in pppoe.keys(): + if 'deleted' in pppoe: # bail out early return None - if 'disable' not in pppoe.keys(): + if 'disable' not in pppoe: # Dial PPPoE connection call('systemctl restart ppp@{ifname}.service'.format(**pppoe)) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index cce9b020b..55f11e65e 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -52,8 +52,6 @@ def get_config(): if mode: peth.update({'mode_old' : mode}) - import pprint - pprint.pprint(peth) return peth def verify(peth): diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 0964a8f4d..9a5dae9e0 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -20,11 +20,11 @@ from fnmatch import fnmatch from sys import exit from vyos.config import Config -from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict from vyos.configverify import verify_vrf from vyos.template import render from vyos.util import call -from vyos.xml import defaults +from vyos.util import check_kmod from vyos import ConfigError from vyos import airbag airbag.enable() @@ -42,44 +42,30 @@ def find_device_file(device): return None def get_config(): - """ Retrive CLI config as dictionary. Dictionary can never be empty, - as at least the interface name will be added or a deleted flag """ + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'wirelessmodem'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - # retrieve interface default values - base = ['interfaces', 'wirelessmodem'] - default_values = defaults(base) - ifname = os.environ['VYOS_TAGNODE_VALUE'] - base = base + [ifname] - - wwan = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Check if interface has been removed - if wwan == {}: - wwan.update({'deleted' : ''}) - - # We have gathered the dict representation of the CLI, but there are - # default options which we need to update into the dictionary - # retrived. - wwan = dict_merge(default_values, wwan) - - # Add interface instance name into dictionary - wwan.update({'ifname': ifname}) + wwan = get_interface_dict(conf, base, ifname) return wwan def verify(wwan): - if 'deleted' in wwan.keys(): + if 'deleted' in wwan: return None - if not 'apn' in wwan.keys(): + if not 'apn' in wwan: raise ConfigError('No APN configured for "{ifname}"'.format(**wwan)) - if not 'device' in wwan.keys(): + if not 'device' in wwan: raise ConfigError('Physical "device" must be configured') # we can not use isfile() here as Linux device files are no regular files @@ -136,11 +122,11 @@ def generate(wwan): return None def apply(wwan): - if 'deleted' in wwan.keys(): + if 'deleted' in wwan: # bail out early return None - if not 'disable' in wwan.keys(): + if not 'disable' in wwan: # "dial" WWAN connection call('systemctl start ppp@{ifname}.service'.format(**wwan)) -- cgit v1.2.3 From 0e2304e0ec903a8183307b51f275097cd87a6995 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Jul 2020 21:57:26 +0200 Subject: ifconfig: T2653: move bridge member check to base class This test is reused by a lot of instances and thus must be moved to the base class. --- python/vyos/ifconfig/ethernet.py | 5 ----- python/vyos/ifconfig/interface.py | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 1725116e2..7641e82fd 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -299,11 +299,6 @@ class EthernetIf(Interface): duplex = config.get('duplex') self.set_speed_duplex(speed, duplex) - # re-add ourselves to any bridge we might have fallen out of - if 'is_bridge_member' in config: - bridge = config.get('is_bridge_member') - self.add_to_bridge(bridge) - # remove no longer required 802.1ad (Q-in-Q VLANs) for vif_s_id in config.get('vif_s_remove', {}): self.del_vlan(vif_s_id) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index ea770af23..2d2017b7a 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -946,6 +946,10 @@ class Interface(Control): for addr in tmp: self.add_ipv6_eui64_address(addr) + # re-add ourselves to any bridge we might have fallen out of + if 'is_bridge_member' in config: + bridge = config.get('is_bridge_member') + self.add_to_bridge(bridge) # Interface administrative state state = 'down' if 'disable' in config else 'up' -- cgit v1.2.3 From 92dd96726dd638593ab0794ab9b66cfb33b2ac67 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Jul 2020 22:00:12 +0200 Subject: geneve: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- src/conf_mode/interfaces-geneve.py | 128 +++++++------------------------------ 1 file changed, 23 insertions(+), 105 deletions(-) diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 31f6eb6b5..868ab5ccf 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# 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 @@ -21,102 +21,44 @@ from copy import deepcopy from netifaces import interfaces 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.ifconfig import GeneveIf -from vyos.validate import is_member from vyos import ConfigError from vyos import airbag airbag.enable() -default_config_data = { - 'address': [], - 'deleted': False, - 'description': '', - 'disable': False, - 'intf': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp': 0, - 'is_bridge_member': False, - 'mtu': 1500, - 'remote': '', - 'vni': '' -} - def get_config(): - geneve = deepcopy(default_config_data) + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'geneve'] # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - geneve['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # check if interface is member if a bridge - geneve['is_bridge_member'] = is_member(conf, geneve['intf'], 'bridge') - - # Check if interface has been removed - if not conf.exists('interfaces geneve ' + geneve['intf']): - geneve['deleted'] = True - return geneve - - # set new configuration level - conf.set_level('interfaces geneve ' + geneve['intf']) - - # retrieve configured interface addresses - if conf.exists('address'): - geneve['address'] = conf.return_values('address') - - # retrieve interface description - if conf.exists('description'): - geneve['description'] = conf.return_value('description') - - # Disable this interface - if conf.exists('disable'): - geneve['disable'] = True - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - geneve['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # Enable proxy-arp on this interface - if conf.exists('ip enable-proxy-arp'): - geneve['ip_proxy_arp'] = 1 - - # Maximum Transmission Unit (MTU) - if conf.exists('mtu'): - geneve['mtu'] = int(conf.return_value('mtu')) - - # Remote address of GENEVE tunnel - if conf.exists('remote'): - geneve['remote'] = conf.return_value('remote') - - # Virtual Network Identifier - if conf.exists('vni'): - geneve['vni'] = conf.return_value('vni') + ifname = os.environ['VYOS_TAGNODE_VALUE'] + geneve = get_interface_dict(conf, base, ifname) return geneve - def verify(geneve): - if geneve['deleted']: - if geneve['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{geneve["intf"]}" as it is a ' - f'member of bridge "{geneve["is_bridge_member"]}"!')) - + if 'deleted' in geneve: + verify_bridge_delete(geneve) return None - if geneve['is_bridge_member'] and geneve['address']: - raise ConfigError(( - f'Cannot assign address to interface "{geneve["intf"]}" ' - f'as it is a member of bridge "{geneve["is_bridge_member"]}"!')) + verify_address(geneve) - if not geneve['remote']: - raise ConfigError('GENEVE remote must be configured') + if 'remote' not in geneve: + raise ConfigError('Remote side must be configured') - if not geneve['vni']: - raise ConfigError('GENEVE VNI must be configured') + if 'vni' not in geneve: + raise ConfigError('VNI must be configured') return None @@ -127,13 +69,13 @@ def generate(geneve): def apply(geneve): # Check if GENEVE interface already exists - if geneve['intf'] in interfaces(): - g = GeneveIf(geneve['intf']) + if geneve['ifname'] in interfaces(): + g = GeneveIf(geneve['ifname']) # GENEVE is super picky and the tunnel always needs to be recreated, # thus we can simply always delete it first. g.remove() - if not geneve['deleted']: + if 'deleted' not in geneve: # GENEVE interface needs to be created on-block # instead of passing a ton of arguments, I just use a dict # that is managed by vyos.ifconfig @@ -144,32 +86,8 @@ def apply(geneve): conf['remote'] = geneve['remote'] # Finally create the new interface - g = GeneveIf(geneve['intf'], **conf) - # update interface description used e.g. by SNMP - g.set_alias(geneve['description']) - # Maximum Transfer Unit (MTU) - g.set_mtu(geneve['mtu']) - - # configure ARP cache timeout in milliseconds - g.set_arp_cache_tmo(geneve['ip_arp_cache_tmo']) - # Enable proxy-arp on this interface - g.set_proxy_arp(geneve['ip_proxy_arp']) - - # Configure interface address(es) - no need to implicitly delete the - # old addresses as they have already been removed by deleting the - # interface above - for addr in geneve['address']: - g.add_addr(addr) - - # As the GENEVE interface is always disabled first when changing - # parameters we will only re-enable the interface if it is not - # administratively disabled - if not geneve['disable']: - g.set_admin_state('up') - - # re-add ourselves to any bridge we might have fallen out of - if geneve['is_bridge_member']: - g.add_to_bridge(geneve['is_bridge_member']) + g = GeneveIf(geneve['ifname'], **conf) + g.update(geneve) return None -- cgit v1.2.3 From 3ee6030b98f8afdbc3a606ce458e11323e59b23c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 21 Jul 2020 22:08:22 +0200 Subject: vyos.configdict: T2653: add new reusable helper node_changed() This can be used to see if a tagNode has been changed. It will return a list of changed nodes. --- python/vyos/configdict.py | 18 +++++++++++++++--- src/conf_mode/interfaces-bridge.py | 11 ++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 4fca426cd..7f05a15ed 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -133,17 +133,16 @@ def T2665_default_dict_cleanup(dict): return dict -def leaf_node_changed(conf, key): +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 the old value. If nothing has been changed, None is returned """ from vyos.configdiff import get_config_diff - D = get_config_diff(conf, key_mangling=('-', '_')) D.set_level(conf.get_level()) - (new, old) = D.get_value_diff(key) + (new, old) = D.get_value_diff(path) if new != old: if isinstance(old, str): return old @@ -156,6 +155,19 @@ def leaf_node_changed(conf, key): return None +def 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 the old value. If nothing + has been changed, None is returned + """ + from vyos.configdiff import get_config_diff, Diff + 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, expand_nodes=Diff.DELETE)['delete'].keys() + return list(keys) + def get_interface_dict(config, base, ifname): """ Common utility function to retrieve and mandgle the interfaces available diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 7998a251a..9c43d1983 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -21,7 +21,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import get_interface_dict -from vyos.configdiff import get_config_diff, Diff +from vyos.configdict import node_changed from vyos.configverify import verify_dhcpv6 from vyos.configverify import verify_vrf from vyos.ifconfig import BridgeIf @@ -34,13 +34,6 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_removed_members(conf): - 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(['member', 'interface'], expand_nodes=Diff.DELETE)['delete'].keys() - return list(keys) - def get_config(): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -57,7 +50,7 @@ def get_config(): bridge = get_interface_dict(conf, base, ifname) # determine which members have been removed - tmp = get_removed_members(conf) + tmp = node_changed(conf, ['member', 'interface']) if tmp: if 'member' in bridge: bridge['member'].update({'interface_remove': tmp }) -- cgit v1.2.3 From 3998e140d13d99fde0c814816f4cf7533a38a61a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 17:15:55 +0200 Subject: ifconfig: T2653: implement update() in derived classes for admin up/down Every derived class must implement update() to set the interfaces admin up/down state. This is required to prevend extensive link flaps when e.g. reconfiguring bond interfaces. --- python/vyos/ifconfig/bridge.py | 12 +++++++++++- python/vyos/ifconfig/dummy.py | 19 +++++++++++++++++++ python/vyos/ifconfig/ethernet.py | 12 +++++++++++- python/vyos/ifconfig/interface.py | 3 --- python/vyos/ifconfig/loopback.py | 12 +++++++++++- python/vyos/ifconfig/macsec.py | 19 +++++++++++++++++++ python/vyos/ifconfig/macvlan.py | 19 +++++++++++++++++++ 7 files changed, 90 insertions(+), 6 deletions(-) diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index af950b35d..da4e1a289 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -195,7 +195,7 @@ class BridgeIf(Interface): interface setup code and provide a single point of entry when workin on any interface. """ - # now call the regular function from within our base class + # call base class first super().update(config) # Set ageing time @@ -251,3 +251,13 @@ class BridgeIf(Interface): # set bridge port path priority value = interface_config.get('priority') tmp.set_path_priority(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) diff --git a/python/vyos/ifconfig/dummy.py b/python/vyos/ifconfig/dummy.py index 404c490c7..43614cd1c 100644 --- a/python/vyos/ifconfig/dummy.py +++ b/python/vyos/ifconfig/dummy.py @@ -35,3 +35,22 @@ class DummyIf(Interface): 'prefixes': ['dum', ], }, } + + 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 first + super().update(config) + + # 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) diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 7641e82fd..77128633d 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -261,7 +261,7 @@ class EthernetIf(Interface): interface setup code and provide a single point of entry when workin on any interface. """ - # now call the regular function from within our base class + # call base class first super().update(config) # disable ethernet flow control (pause frames) @@ -326,3 +326,13 @@ class EthernetIf(Interface): for vif_id, vif in config.get('vif', {}).items(): vlan = self.add_vlan(vif_id) vlan.update(vif) + + # 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) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 2d2017b7a..5942904b5 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -951,6 +951,3 @@ class Interface(Control): bridge = config.get('is_bridge_member') self.add_to_bridge(bridge) - # Interface administrative state - state = 'down' if 'disable' in config else 'up' - self.set_admin_state(state) diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py index 7ebd13b54..2b4ebfdcc 100644 --- a/python/vyos/ifconfig/loopback.py +++ b/python/vyos/ifconfig/loopback.py @@ -75,5 +75,15 @@ class LoopbackIf(Interface): # Update IP address entry in our dictionary config.update({'address' : addr}) - # now call the regular function from within our base class + # call base class super().update(config) + + # 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) diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py index ea8c9807e..6f570d162 100644 --- a/python/vyos/ifconfig/macsec.py +++ b/python/vyos/ifconfig/macsec.py @@ -71,3 +71,22 @@ class MACsecIf(Interface): 'source_interface': '', } return config + + 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 first + super().update(config) + + # 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) diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py index b5481f4a7..b068ce873 100644 --- a/python/vyos/ifconfig/macvlan.py +++ b/python/vyos/ifconfig/macvlan.py @@ -68,3 +68,22 @@ class MACVLANIf(Interface): >> dict = MACVLANIf().get_config() """ return deepcopy(cls.default) + + 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 first + super().update(config) + + # 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) -- cgit v1.2.3 From 0d21de93ce02fb0ae6e2e3ceb13dfd5b8dbe755f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 17:17:38 +0200 Subject: vyos.configdict: T2653: use dict_merge() over update() With dict.update() existing keys will get overwritten when blending in interface default values. --- python/vyos/configdict.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 7f05a15ed..f26b47e41 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -230,11 +230,11 @@ def get_interface_dict(config, base, ifname): # XML definitions which hold the defaults default_vif_values = defaults(base + ['vif']) for vif, vif_config in dict.get('vif', {}).items(): - vif_config.update(default_vif_values) + vif_config = dict_merge(default_vif_values, vif_config) for vif_s, vif_s_config in dict.get('vif_s', {}).items(): - vif_s_config.update(default_vif_values) + vif_s_config = dict_merge(default_vif_values, vif_s_config) for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): - vif_c_config.update(default_vif_values) + vif_c_config = dict_merge(default_vif_values, vif_c_config) # Check vif, vif-s/vif-c VLAN interfaces for removal dict = get_removed_vlans(config, dict) -- cgit v1.2.3 From add7eaebe7b8ebd4e143eb939d3ba7871ead0502 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 17:18:45 +0200 Subject: ifconfig: T2653: move vlan configuration code to base class This is required as other interfaces (e.g. pseudo-ethernet or bond) will have VLANs, too. --- python/vyos/ifconfig/ethernet.py | 29 ----------------------------- python/vyos/ifconfig/interface.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 77128633d..b2f701e00 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -17,7 +17,6 @@ import os import re import jmespath -from vyos.configdict import get_ethertype from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN from vyos.validate import assert_list @@ -299,34 +298,6 @@ class EthernetIf(Interface): duplex = config.get('duplex') self.set_speed_duplex(speed, duplex) - # remove no longer required 802.1ad (Q-in-Q VLANs) - for vif_s_id in config.get('vif_s_remove', {}): - self.del_vlan(vif_s_id) - - # create/update 802.1ad (Q-in-Q VLANs) - for vif_s_id, vif_s in config.get('vif_s', {}).items(): - tmp=get_ethertype(vif_s.get('ethertype', '0x88A8')) - s_vlan = self.add_vlan(vif_s_id, ethertype=tmp) - s_vlan.update(vif_s) - - # remove no longer required client VLAN (vif-c) - for vif_c_id in vif_s.get('vif_c_remove', {}): - s_vlan.del_vlan(vif_c_id) - - # create/update client VLAN (vif-c) interface - for vif_c_id, vif_c in vif_s.get('vif_c', {}).items(): - c_vlan = s_vlan.add_vlan(vif_c_id) - c_vlan.update(vif_c) - - # remove no longer required 802.1q VLAN interfaces - for vif_id in config.get('vif_remove', {}): - self.del_vlan(vif_id) - - # create/update 802.1q VLAN interfaces - for vif_id, vif in config.get('vif', {}).items(): - vlan = self.add_vlan(vif_id) - vlan.update(vif) - # 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 diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 5942904b5..1fe4f74f2 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -31,6 +31,7 @@ from netifaces import AF_INET6 from vyos import ConfigError from vyos.configdict import list_diff +from vyos.configdict import get_ethertype from vyos.util import mac2eui64 from vyos.validate import is_ipv4 from vyos.validate import is_ipv6 @@ -951,3 +952,30 @@ class Interface(Control): bridge = config.get('is_bridge_member') self.add_to_bridge(bridge) + # remove no longer required 802.1ad (Q-in-Q VLANs) + for vif_s_id in config.get('vif_s_remove', {}): + self.del_vlan(vif_s_id) + + # create/update 802.1ad (Q-in-Q VLANs) + for vif_s_id, vif_s in config.get('vif_s', {}).items(): + tmp=get_ethertype(vif_s.get('ethertype', '0x88A8')) + s_vlan = self.add_vlan(vif_s_id, ethertype=tmp) + s_vlan.update(vif_s) + + # remove no longer required client VLAN (vif-c) + for vif_c_id in vif_s.get('vif_c_remove', {}): + s_vlan.del_vlan(vif_c_id) + + # create/update client VLAN (vif-c) interface + for vif_c_id, vif_c in vif_s.get('vif_c', {}).items(): + c_vlan = s_vlan.add_vlan(vif_c_id) + c_vlan.update(vif_c) + + # remove no longer required 802.1q VLAN interfaces + for vif_id in config.get('vif_remove', {}): + self.del_vlan(vif_id) + + # create/update 802.1q VLAN interfaces + for vif_id, vif in config.get('vif', {}).items(): + vlan = self.add_vlan(vif_id) + vlan.update(vif) -- cgit v1.2.3 From f81b0443cf09c34cb1f2060094e3eb294b8fa192 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 17:20:50 +0200 Subject: bonding: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- interface-definitions/interfaces-bonding.xml.in | 2 + python/vyos/ifconfig/bond.py | 118 ++++++- python/vyos/ifconfig/interface.py | 16 +- python/vyos/validate.py | 5 +- src/conf_mode/interfaces-bonding.py | 437 ++++++------------------ 5 files changed, 241 insertions(+), 337 deletions(-) diff --git a/interface-definitions/interfaces-bonding.xml.in b/interface-definitions/interfaces-bonding.xml.in index ddd52979b..7d658f6a0 100644 --- a/interface-definitions/interfaces-bonding.xml.in +++ b/interface-definitions/interfaces-bonding.xml.in @@ -78,6 +78,7 @@ hash-policy must be layer2 layer2+3 or layer3+4 + layer2 @@ -137,6 +138,7 @@ mode must be 802.3ad, active-backup, broadcast, round-robin, transmit-load-balance, adaptive-load-balance, or xor + 802.3ad diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 47dd4ff34..5a48ac632 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -14,14 +14,15 @@ # License along with this library. If not, see . import os +import jmespath from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN +from vyos.util import cmd from vyos.validate import assert_list from vyos.validate import assert_positive - @Interface.register @VLAN.enable class BondIf(Interface): @@ -179,7 +180,13 @@ class BondIf(Interface): >>> BondIf('bond0').get_arp_ip_target() '192.0.2.1' """ - return self.get_interface('bond_arp_ip_target') + # 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): """ @@ -209,11 +216,31 @@ class BondIf(Interface): >>> BondIf('bond0').add_port('eth0') >>> BondIf('bond0').add_port('eth1') """ - # An interface can only be added to a bond if it is in 'down' state. If - # interface is in 'up' state, the following Kernel error will be thrown: - # bond0: eth1 is up - this may be due to an out of date ifenslave. - Interface(interface).set_admin_state('down') - return self.set_interface('bond_add_port', f'+{interface}') + + # 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): """ @@ -277,3 +304,80 @@ class BondIf(Interface): >>> BondIf('bond0').set_mode('802.3ad') """ return self.set_interface('bond_mode', mode) + + 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') + + # call base class first + super().update(config) + + # 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 = jmespath.search('arp_monitor.target', config) + if isinstance(value, str): + value = [value] + if value: + for addr in value: + self.set_arp_ip_target('+' + addr) + + # Bonding transmit hash policy + value = config.get('hash_policy') + if value: self.set_hash_policy(value) + + # Some interface options can only be changed if the interface is + # administratively down + if self.get_admin_state() == 'down': + # Delete bond member port(s) + for interface in self.get_slaves(): + self.del_port(interface) + + # Bonding policy/mode + value = config.get('mode') + if value: self.set_mode(value) + + # Add (enslave) interfaces to bond + value = jmespath.search('member.interface', config) + if value: + for interface in value: + # if we've come here we already verified the interface does + # not have an addresses configured so just flush any + # remaining ones + cmd(f'ip addr flush dev "{interface}"') + self.add_port(interface) + + # Primary device interface - must be set after 'mode' + value = config.get('primary') + if value: self.set_primary(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) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 1fe4f74f2..7e887db1b 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -205,6 +205,7 @@ class Interface(Control): # make sure the ifname is the first argument and not from the dict self.config['ifname'] = ifname + self._admin_state_down_cnt = 0 # we must have updated config before initialising the Interface super().__init__(**kargs) @@ -594,7 +595,13 @@ class Interface(Control): if not int(flags, 16) & 1: return None - return self.set_interface('admin_state', state) + 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_proxy_arp(self, enable): """ @@ -829,8 +836,11 @@ class Interface(Control): # There are some items in the configuration which can only be applied # if this instance is not bound to a bridge. This should be checked # by the caller but better save then sorry! - if not config.get('is_bridge_member', False): - # Bind interface instance into VRF + if not any(k in ['is_bond_member', 'is_bridge_member'] for k in config): + # Bind interface to given VRF or unbind it if vrf node is not set. + # unbinding will call 'ip link set dev eth0 nomaster' which will + # also drop the interface out of a bridge or bond - thus this is + # checked before self.set_vrf(config.get('vrf', '')) # DHCP options diff --git a/python/vyos/validate.py b/python/vyos/validate.py index a0620e4dd..ceeb6888a 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -279,7 +279,6 @@ def is_member(conf, interface, intftype=None): False -> interface type cannot have members """ ret_val = None - if intftype not in ['bonding', 'bridge', None]: raise ValueError(( f'unknown interface type "{intftype}" or it cannot ' @@ -292,9 +291,9 @@ def is_member(conf, interface, intftype=None): conf.set_level([]) for it in intftype: - base = 'interfaces ' + it + base = ['interfaces', it] for intf in conf.list_nodes(base): - memberintf = [base, intf, 'member', 'interface'] + memberintf = base + [intf, 'member', 'interface'] if xml.is_tag(memberintf): if interface in conf.list_nodes(memberintf): ret_val = intf diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index a16c4e105..8e87a0059 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -16,41 +16,25 @@ import os -from copy import deepcopy from sys import exit from netifaces import interfaces -from vyos.ifconfig import BondIf -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data from vyos.config import Config -from vyos.util import call, cmd -from vyos.validate import is_member, has_address_configured +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_dhcpv6 +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf +from vyos.ifconfig import BondIf +from vyos.validate import is_member +from vyos.validate import has_address_configured from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'arp_mon_intvl': 0, - 'arp_mon_tgt': [], - 'deleted': False, - 'hash_policy': 'layer2', - 'intf': '', - 'ip_arp_cache_tmo': 30, - 'ip_proxy_arp_pvlan': 0, - 'mode': '802.3ad', - 'member': [], - 'shutdown_required': False, - 'primary': '', - 'vif_s': {}, - 'vif_s_remove': [], - 'vif': {}, - 'vif_remove': [], -} - - def get_bond_mode(mode): if mode == 'round-robin': return 'balance-rr' @@ -67,339 +51,144 @@ def get_bond_mode(mode): elif mode == 'adaptive-load-balance': return 'balance-alb' else: - raise ConfigError('invalid bond mode "{}"'.format(mode)) + raise ConfigError(f'invalid bond mode "{mode}"') def get_config(): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + conf = Config() + base = ['interfaces', 'bonding'] + # 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'] - conf = Config() + bond = get_interface_dict(conf, base, ifname) + + # 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 wach member + if 'member' in bond and 'interface' in bond['member']: + # first convert it to a list if only one member is given + if isinstance(bond['member']['interface'], str): + bond['member']['interface'] = [bond['member']['interface']] + + tmp={} + for interface in bond['member']['interface']: + tmp.update({interface: {}}) + + bond['member']['interface'] = tmp + + if 'mode' in bond: + bond['mode'] = get_bond_mode(bond['mode']) + + tmp = leaf_node_changed(conf, ['mode']) + if tmp: + bond.update({'shutdown_required': ''}) + + # determine which members have been removed + tmp = leaf_node_changed(conf, ['member', 'interface']) + if tmp: + bond.update({'shutdown_required': ''}) + if 'member' in bond: + bond['member'].update({'interface_remove': tmp }) + else: + bond.update({'member': {'interface_remove': tmp }}) + + if 'member' in bond and 'interface' in bond['member']: + for interface, interface_config in bond['member']['interface'].items(): + # Check if we are a member of another bond device + tmp = is_member(conf, interface, 'bridge') + if tmp: + interface_config.update({'is_bridge_member' : tmp}) - # initialize kernel module if not loaded - if not os.path.isfile('/sys/class/net/bonding_masters'): - import syslog - syslog.syslog(syslog.LOG_NOTICE, "loading bonding kernel module") - if call('modprobe bonding max_bonds=0 miimon=250') != 0: - syslog.syslog(syslog.LOG_NOTICE, "failed loading bonding kernel module") - raise ConfigError("failed loading bonding kernel module") - - # check if bond has been removed - cfg_base = 'interfaces bonding ' + ifname - if not conf.exists(cfg_base): - bond = deepcopy(default_config_data) - bond['intf'] = ifname - bond['deleted'] = True - return bond - - # set new configuration level - conf.set_level(cfg_base) - - bond, disabled = intf_to_dict(conf, default_config_data) - - # ARP link monitoring frequency in milliseconds - if conf.exists('arp-monitor interval'): - bond['arp_mon_intvl'] = int(conf.return_value('arp-monitor interval')) - - # IP address to use for ARP monitoring - if conf.exists('arp-monitor target'): - bond['arp_mon_tgt'] = conf.return_values('arp-monitor target') - - # Bonding transmit hash policy - if conf.exists('hash-policy'): - bond['hash_policy'] = conf.return_value('hash-policy') - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - bond['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # Enable private VLAN proxy ARP on this interface - if conf.exists('ip proxy-arp-pvlan'): - bond['ip_proxy_arp_pvlan'] = 1 - - # Bonding mode - if conf.exists('mode'): - act_mode = conf.return_value('mode') - eff_mode = conf.return_effective_value('mode') - if not (act_mode == eff_mode): - bond['shutdown_required'] = True - - bond['mode'] = get_bond_mode(act_mode) - - # determine bond member interfaces (currently configured) - bond['member'] = conf.return_values('member interface') - - # We can not call conf.return_effective_values() as it would not work - # on reboots. Reboots/First boot will return that running config and - # saved config is the same, thus on a reboot the bond members will - # not be added all (https://phabricator.vyos.net/T2030) - live_members = BondIf(bond['intf']).get_slaves() - if not (bond['member'] == live_members): - bond['shutdown_required'] = True - - # Primary device interface - if conf.exists('primary'): - bond['primary'] = conf.return_value('primary') - - add_to_dict(conf, disabled, bond, 'vif', 'vif') - add_to_dict(conf, disabled, bond, 'vif-s', 'vif_s') + # Check if we are a member of a bond device + tmp = is_member(conf, interface, 'bonding') + if tmp and tmp != ifname: + interface_config.update({'is_bond_member' : tmp}) + + # bond members must not have an assigned address + tmp = has_address_configured(conf, interface) + if tmp: + interface_config.update({'has_address' : ''}) return bond def verify(bond): - if bond['deleted']: - if bond['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{bond["intf"]}" as it is a ' - f'member of bridge "{bond["is_bridge_member"]}"!')) - + if 'deleted' in bond: + verify_bridge_delete(bond) return None - if len(bond['arp_mon_tgt']) > 16: - raise ConfigError('The maximum number of arp-monitor targets is 16') + if 'arp_monitor' in bond: + if 'target' in bond['arp_monitor'] and len(int(bond['arp_monitor']['target'])) > 16: + raise ConfigError('The maximum number of arp-monitor targets is 16') + + if 'interval' in bond['arp_monitor'] and len(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 bond['primary']: + if 'primary' in bond: if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: - raise ConfigError(( - 'Mode dependency failed, primary not supported in mode ' - f'"{bond["mode"]}"!')) - - if ( bond['is_bridge_member'] - and ( bond['address'] - or bond['ipv6_eui64_prefix'] - or bond['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{bond["intf"]}" ' - f'as it is a member of bridge "{bond["is_bridge_member"]}"!')) - - if bond['vrf']: - if bond['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{bond["vrf"]}" does not exist') - - if bond['is_bridge_member']: - raise ConfigError(( - f'Interface "{bond["intf"]}" cannot be member of VRF ' - f'"{bond["vrf"]}" and bridge {bond["is_bridge_member"]} ' - f'at the same time!')) + raise ConfigError('Option primary - mode dependency failed, not' + 'supported in mode {mode}!'.format(**bond)) + + verify_address(bond) + verify_dhcpv6(bond) + verify_vrf(bond) # use common function to verify VLAN configuration verify_vlan_config(bond) - conf = Config() - for intf in bond['member']: - # check if member interface is "real" - if intf not in interfaces(): - raise ConfigError(f'Interface {intf} does not exist!') - - # a bonding member interface is only allowed to be assigned to one bond! - all_bonds = conf.list_nodes('interfaces bonding') - # We do not need to check our own bond - all_bonds.remove(bond['intf']) - for tmp in all_bonds: - if conf.exists('interfaces bonding {tmp} member interface {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already a member of bond "{tmp}"!')) - - # can not add interfaces with an assigned address to a bond - if has_address_configured(conf, intf): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it has an address assigned!')) - - # bond members are not allowed to be bridge members - tmp = is_member(conf, intf, 'bridge') - if tmp: - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already a member of bridge "{tmp}"!')) - - # bond members are not allowed to be vrrp members - for tmp in conf.list_nodes('high-availability vrrp group'): - if conf.exists('high-availability vrrp group {tmp} interface {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already a member of VRRP group "{tmp}"!')) - - # bond members are not allowed to be underlaying psuedo-ethernet devices - for tmp in conf.list_nodes('interfaces pseudo-ethernet'): - if conf.exists('interfaces pseudo-ethernet {tmp} link {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already the link of pseudo-ethernet "{tmp}"!')) - - # bond members are not allowed to be underlaying vxlan devices - for tmp in conf.list_nodes('interfaces vxlan'): - if conf.exists('interfaces vxlan {tmp} link {intf}'): - raise ConfigError(( - f'Cannot add interface "{intf}" to bond "{bond["intf"]}", ' - f'it is already the link of VXLAN "{tmp}"!')) - - if bond['primary']: - if bond['primary'] not in bond['member']: - raise ConfigError(f'Bond "{bond["intf"]}" primary interface must be a member') + bond_name = bond['ifname'] + if 'member' in bond: + member = bond.get('member') + for interface, interface_config in member.get('interface', {}).items(): + error_msg = f'Can not add interface "{interface}" to bond "{bond_name}", ' + + if interface == 'lo': + raise ConfigError('Loopback interface "lo" can not be added to a bond') + + if interface not in interfaces(): + raise ConfigError(error_msg + 'it does not exist!') + + if 'is_bridge_member' in interface_config: + tmp = 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 = interface_config['is_bond_member'] + raise ConfigError(error_msg + f'it is already a member of bond "{tmp}"!') + + if 'has_address' in interface_config: + raise ConfigError(error_msg + 'it has an address 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 bond['arp_mon_intvl'] > 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') - return None def generate(bond): return None def apply(bond): - b = BondIf(bond['intf']) + b = BondIf(bond['ifname']) - if bond['deleted']: + if 'deleted' in bond: # delete interface b.remove() else: - # ARP link monitoring frequency, reset miimon when arp-montior is inactive - # this is done inside BondIf automatically - b.set_arp_interval(bond['arp_mon_intvl']) - - # 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 adresses prior adding new ones, this will remove addresses - # added manually 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, b.get_arp_ip_target().split())) - for addr in arp_tgt_addr: - b.set_arp_ip_target('-' + addr) - - # Add configured ARP target addresses - for addr in bond['arp_mon_tgt']: - b.set_arp_ip_target('+' + addr) - - # update interface description used e.g. within SNMP - b.set_alias(bond['description']) - - if bond['dhcp_client_id']: - b.dhcp.v4.options['client_id'] = bond['dhcp_client_id'] - - if bond['dhcp_hostname']: - b.dhcp.v4.options['hostname'] = bond['dhcp_hostname'] - - if bond['dhcp_vendor_class_id']: - b.dhcp.v4.options['vendor_class_id'] = bond['dhcp_vendor_class_id'] - - if bond['dhcpv6_prm_only']: - b.dhcp.v6.options['dhcpv6_prm_only'] = True - - if bond['dhcpv6_temporary']: - b.dhcp.v6.options['dhcpv6_temporary'] = True - - if bond['dhcpv6_pd_length']: - b.dhcp.v6.options['dhcpv6_pd_length'] = bond['dhcpv6_pd_length'] - - if bond['dhcpv6_pd_interfaces']: - b.dhcp.v6.options['dhcpv6_pd_interfaces'] = bond['dhcpv6_pd_interfaces'] - - # ignore link state changes - b.set_link_detect(bond['disable_link_detect']) - # Bonding transmit hash policy - b.set_hash_policy(bond['hash_policy']) - # configure ARP cache timeout in milliseconds - b.set_arp_cache_tmo(bond['ip_arp_cache_tmo']) - # configure ARP filter configuration - b.set_arp_filter(bond['ip_disable_arp_filter']) - # configure ARP accept - b.set_arp_accept(bond['ip_enable_arp_accept']) - # configure ARP announce - b.set_arp_announce(bond['ip_enable_arp_announce']) - # configure ARP ignore - b.set_arp_ignore(bond['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - b.set_proxy_arp(bond['ip_proxy_arp']) - # Enable private VLAN proxy ARP on this interface - b.set_proxy_arp_pvlan(bond['ip_proxy_arp_pvlan']) - # IPv6 accept RA - b.set_ipv6_accept_ra(bond['ipv6_accept_ra']) - # IPv6 address autoconfiguration - b.set_ipv6_autoconf(bond['ipv6_autoconf']) - # IPv6 forwarding - b.set_ipv6_forwarding(bond['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - b.set_ipv6_dad_messages(bond['ipv6_dup_addr_detect']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in bond['ipv6_eui64_prefix_remove']: - b.del_ipv6_eui64_address(addr) - - # Change interface MAC address - if bond['mac']: - b.set_mac(bond['mac']) - - # Add IPv6 EUI-based addresses - for addr in bond['ipv6_eui64_prefix']: - b.add_ipv6_eui64_address(addr) - - # Maximum Transmission Unit (MTU) - b.set_mtu(bond['mtu']) - - # Primary device interface - if bond['primary']: - b.set_primary(bond['primary']) - - # Some parameters can not be changed when the bond is up. - if bond['shutdown_required']: - # Disable bond prior changing of certain properties - b.set_admin_state('down') - - # The bonding mode can not be changed when there are interfaces enslaved - # to this bond, thus we will free all interfaces from the bond first! - for intf in b.get_slaves(): - b.del_port(intf) - - # Bonding policy/mode - b.set_mode(bond['mode']) - - # Add (enslave) interfaces to bond - for intf in bond['member']: - # if we've come here we already verified the interface doesn't - # have addresses configured so just flush any remaining ones - cmd(f'ip addr flush dev "{intf}"') - b.add_port(intf) - - # As the bond interface is always disabled first when changing - # parameters we will only re-enable the interface if it is not - # administratively disabled - if not bond['disable']: - b.set_admin_state('up') - else: - b.set_admin_state('down') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in bond['address_remove']: - b.del_addr(addr) - for addr in bond['address']: - b.add_addr(addr) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not bond['is_bridge_member']: - b.set_vrf(bond['vrf']) - - # re-add ourselves to any bridge we might have fallen out of - if bond['is_bridge_member']: - b.add_to_bridge(bond['is_bridge_member']) - - # apply all vlans to interface - apply_all_vlans(b, bond) + b.update(bond) return None -- cgit v1.2.3 From d81ce482836ab4adf4f71e2b3dc21477db49a9f0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 17:24:50 +0200 Subject: vlan: ifconfig: T2653: move get_removed_vlans() to vyos.configdiff As we wrap up additional functions from this library it should be part of it. --- python/vyos/configdict.py | 28 +++++++++++++++++++++++++++- python/vyos/ifconfig_vlan.py | 27 --------------------------- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index f26b47e41..a1553ae61 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -168,6 +168,33 @@ def node_changed(conf, path): keys = D.get_child_nodes_diff(path, expand_nodes=Diff.DELETE)['delete'].keys() return list(keys) +def get_removed_vlans(conf, 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(['vif'], expand_nodes=Diff.DELETE)['delete'].keys() + if keys: + dict.update({'vif_remove': [*keys]}) + + # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 + keys = D.get_child_nodes_diff(['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys() + if keys: + dict.update({'vif_s_remove': [*keys]}) + + for vif in dict.get('vif_s', {}).keys(): + keys = D.get_child_nodes_diff(['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys() + if keys: + dict.update({'vif_s': { vif : {'vif_c_remove': [*keys]}}}) + + return dict + def get_interface_dict(config, base, ifname): """ Common utility function to retrieve and mandgle the interfaces available @@ -177,7 +204,6 @@ def get_interface_dict(config, base, ifname): Will return a dictionary with the necessary interface configuration """ from vyos.xml import defaults - from vyos.ifconfig_vlan import get_removed_vlans # retrieve interface default values default_values = defaults(base) diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index 0e4ecda53..442cb0db8 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -16,33 +16,6 @@ from netifaces import interfaces from vyos import ConfigError -def get_removed_vlans(conf, 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(['vif'], expand_nodes=Diff.DELETE)['delete'].keys() - if keys: - dict.update({'vif_remove': [*keys]}) - - # get_child_nodes() will return dict_keys(), mangle this into a list with PEP448 - keys = D.get_child_nodes_diff(['vif-s'], expand_nodes=Diff.DELETE)['delete'].keys() - if keys: - dict.update({'vif_s_remove': [*keys]}) - - for vif in dict.get('vif_s', {}).keys(): - keys = D.get_child_nodes_diff(['vif-s', vif, 'vif-c'], expand_nodes=Diff.DELETE)['delete'].keys() - if keys: - dict.update({'vif_s': { vif : {'vif_c_remove': [*keys]}}}) - - return dict - def apply_all_vlans(intf, intfconfig): """ Function applies all VLANs to the passed interface. -- cgit v1.2.3 From ee65528d720964cf77bc9b28e6f8fb19b9783066 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 21:06:02 +0200 Subject: ifconfig: T2653: move get_ethertype() from configdict to interface ... as it is only used inside the interface class. --- python/vyos/configdict.py | 9 +-------- python/vyos/ifconfig/interface.py | 8 +++++++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index a1553ae61..d79365722 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -267,14 +267,6 @@ def get_interface_dict(config, base, ifname): return dict -def get_ethertype(ethertype_val): - if ethertype_val == '0x88A8': - return '802.1ad' - elif ethertype_val == '0x8100': - return '802.1q' - else: - raise ConfigError('invalid ethertype "{}"'.format(ethertype_val)) - dhcpv6_pd_default_data = { 'dhcpv6_prm_only': False, 'dhcpv6_temporary': False, @@ -636,6 +628,7 @@ def add_to_dict(conf, disabled, ifdict, section, key): def vlan_to_dict(conf, default=vlan_default): + from vyos.ifconfig.interface import get_ethertype vlan, disabled = intf_to_dict(conf, default) # if this is a not within vif-s node, we are done diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 7e887db1b..5496499e5 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -31,7 +31,6 @@ from netifaces import AF_INET6 from vyos import ConfigError from vyos.configdict import list_diff -from vyos.configdict import get_ethertype from vyos.util import mac2eui64 from vyos.validate import is_ipv4 from vyos.validate import is_ipv6 @@ -49,6 +48,13 @@ from vyos.ifconfig.vrrp import VRRP from vyos.ifconfig.operational import Operational from vyos.ifconfig import Section +def get_ethertype(ethertype_val): + if ethertype_val == '0x88A8': + return '802.1ad' + elif ethertype_val == '0x8100': + return '802.1q' + else: + raise ConfigError('invalid ethertype "{}"'.format(ethertype_val)) class Interface(Control): # This is the class which will be used to create -- cgit v1.2.3 From 79af6c7b35164d3313c39dff2bc1bffbb4b326cd Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 22:00:36 +0200 Subject: wireless: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- data/templates/wifi/cfg80211.conf.tmpl | 4 +- data/templates/wifi/crda.tmpl | 4 +- data/templates/wifi/hostapd.conf.tmpl | 424 ++++++-------- data/templates/wifi/wpa_supplicant.conf.tmpl | 4 +- interface-definitions/interfaces-wireless.xml.in | 15 +- src/conf_mode/interfaces-wireless.py | 686 +++++------------------ 6 files changed, 337 insertions(+), 800 deletions(-) diff --git a/data/templates/wifi/cfg80211.conf.tmpl b/data/templates/wifi/cfg80211.conf.tmpl index b21bacc1e..91df57aab 100644 --- a/data/templates/wifi/cfg80211.conf.tmpl +++ b/data/templates/wifi/cfg80211.conf.tmpl @@ -1,3 +1 @@ -{%- if regdom -%} -options cfg80211 ieee80211_regdom={{ regdom }} -{% endif %} +{{ 'options cfg80211 ieee80211_regdom=' + regdom if regdom is defined }} diff --git a/data/templates/wifi/crda.tmpl b/data/templates/wifi/crda.tmpl index 750ad86ee..6cd125e37 100644 --- a/data/templates/wifi/crda.tmpl +++ b/data/templates/wifi/crda.tmpl @@ -1,3 +1 @@ -{%- if regdom -%} -REGDOMAIN={{ regdom }} -{% endif %} +{{ 'REGDOMAIN=' + regdom if regdom is defined }} diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl index d6068e4db..765668c57 100644 --- a/data/templates/wifi/hostapd.conf.tmpl +++ b/data/templates/wifi/hostapd.conf.tmpl @@ -9,7 +9,7 @@ device_name={{ description | truncate(32, True) }} # management frames with the Host AP driver); wlan0 with many nl80211 drivers # Note: This attribute can be overridden by the values supplied with the '-i' # command line parameter. -interface={{ intf }} +interface={{ ifname }} # Driver interface type (hostap/wired/none/nl80211/bsd); # default: hostap). nl80211 is used with all Linux mac80211 drivers. @@ -28,8 +28,7 @@ logger_syslog_level=0 logger_stdout=-1 logger_stdout_level=0 -{%- if country_code %} - +{% if country_code %} # Country code (ISO/IEC 3166-1). Used to set regulatory domain. # Set as needed to indicate country in which device is operating. # This can limit available channels and transmit power. @@ -42,14 +41,12 @@ country_code={{ country_code }} ieee80211d=1 {% endif %} -{%- if ssid %} - +{% if ssid %} # SSID to be used in IEEE 802.11 management frames ssid={{ ssid }} {% endif %} -{%- if channel %} - +{% if channel %} # Channel number (IEEE 802.11) # (default: 0, i.e., not set) # Please note that some drivers do not use this value from hostapd and the @@ -61,8 +58,7 @@ ssid={{ ssid }} channel={{ channel }} {% endif %} -{%- if mode %} - +{% if mode %} # Operation mode (a = IEEE 802.11a (5 GHz), b = IEEE 802.11b (2.4 GHz), # g = IEEE 802.11g (2.4 GHz), ad = IEEE 802.11ad (60 GHz); a/g options are used # with IEEE 802.11n (HT), too, to specify band). For IEEE 802.11ac (VHT), this @@ -71,29 +67,30 @@ channel={{ channel }} # special value "any" can be used to indicate that any support band can be used. # This special case is currently supported only with drivers with which # offloaded ACS is used. -{% if 'n' in mode -%} +{% if 'n' in mode %} hw_mode=g -{% elif 'ac' in mode -%} +{% elif 'ac' in mode %} hw_mode=a ieee80211h=1 ieee80211ac=1 -{% else -%} +{% else %} hw_mode={{ mode }} -{% endif %} +{% endif %} {% endif %} # ieee80211w: Whether management frame protection (MFP) is enabled # 0 = disabled (default) # 1 = optional # 2 = required -{% if 'disabled' in mgmt_frame_protection -%} +{% if 'disabled' in mgmt_frame_protection %} ieee80211w=0 -{% elif 'optional' in mgmt_frame_protection -%} +{% elif 'optional' in mgmt_frame_protection %} ieee80211w=1 -{% elif 'required' in mgmt_frame_protection -%} +{% elif 'required' in mgmt_frame_protection %} ieee80211w=2 {% endif %} +{% if capabilities is defined and capabilities.ht is defined %} # ht_capab: HT capabilities (list of flags) # LDPC coding capability: [LDPC] = supported # Supported channel width set: [HT40-] = both 20 MHz and 40 MHz with secondary @@ -127,79 +124,50 @@ ieee80211w=2 # DSSS/CCK Mode in 40 MHz: [DSSS_CCK-40] = allowed (not allowed if not set) # 40 MHz intolerant [40-INTOLERANT] (not advertised if not set) # L-SIG TXOP protection support: [LSIG-TXOP-PROT] (disabled if not set) -{% if cap_ht %} -ht_capab= -{%- endif -%} - -{%- if cap_ht_40mhz_incapable -%} -[40-INTOLERANT] -{%- endif -%} - -{%- if cap_ht_delayed_block_ack -%} -[DELAYED-BA] -{%- endif -%} - -{%- if cap_ht_dsss_cck_40 -%} -[DSSS_CCK-40] -{%- endif -%} - -{%- if cap_ht_greenfield -%} -[GF] -{%- endif -%} - -{%- if cap_ht_ldpc -%} -[LDPC] -{%- endif -%} - -{%- if cap_ht_lsig_protection -%} -[LSIG-TXOP-PROT] -{%- endif -%} - -{%- if cap_ht_max_amsdu -%} -[MAX-AMSDU-{{ cap_ht_max_amsdu }}] -{%- endif -%} - -{%- if cap_ht_smps -%} -[SMPS-{{ cap_ht_smps | upper }}] -{%- endif -%} - -{%- if cap_ht_chan_set_width -%} -{%- for csw in cap_ht_chan_set_width -%} -[{{ csw | upper }}] -{%- endfor -%} -{%- endif -%} - -{%- if cap_ht_short_gi -%} -{%- for gi in cap_ht_short_gi -%} -[SHORT-GI-{{ gi }}] -{%- endfor -%} -{%- endif -%} - -{%- if cap_ht_stbc_tx -%} -[TX-STBC] -{%- endif -%} -{%- if cap_ht_stbc_rx -%} -[RX-STBC{{ cap_ht_stbc_rx }}] -{%- endif %} +{% set output = '' %} +{% set output = output + '[40-INTOLERANT]' if capabilities.ht.fourtymhz_incapable is defined else '' %} +{% set output = output + '[DELAYED-BA]' if capabilities.ht.delayed_block_ack is defined else '' %} +{% set output = output + '[DSSS_CCK-40]' if capabilities.ht.dsss_cck_40 is defined else '' %} +{% set output = output + '[GF]' if capabilities.ht.greenfield is defined else '' %} +{% set output = output + '[LDPC]' if capabilities.ht.ldpc is defined else '' %} +{% set output = output + '[LSIG-TXOP-PROT]' if capabilities.ht.lsig_protection is defined else '' %} +{% set output = output + '[TX-STBC]' if capabilities.ht.stbc.tx is defined else '' %} +{% set output = output + '[RX-STBC-' + capabilities.ht.stbc.rx | upper + ']' if capabilities.ht.stbc.tx is defined else '' %} +{% set output = output + '[MAX-AMSDU-' + capabilities.ht.max_amsdu + ']' if capabilities.ht.max_amsdu is defined else '' %} +{% set output = output + '[SMPS-' + capabilities.ht.smps | upper + ']' if capabilities.ht.smps is defined else '' %} + +{% if capabilities.ht.channel_set_width is defined %} +{% for csw in capabilities.ht.channel_set_width %} +{% set output = output + '[' + csw | upper + ']' %} +{% endfor %} +{% endif %} -# Required for full HT and VHT functionality -wme_enabled=1 +{% if capabilities.ht.short_gi is defined %} +{% for short_gi in capabilities.ht.short_gi %} +{% set output = output + '[SHORT-GI-' + short_gi | upper + ']' %} +{% endfor %} +{% endif %} -{% if cap_ht_powersave -%} +ht_capab={{ output }} + +{% if capabilities.ht.auto_powersave is defined %} # WMM-PS Unscheduled Automatic Power Save Delivery [U-APSD] # Enable this flag if U-APSD supported outside hostapd (eg., Firmware/driver) uapsd_advertisement_enabled=1 -{%- endif %} +{% endif %} + +{% endif %} + +# Required for full HT and VHT functionality +wme_enabled=1 -{% if cap_req_ht -%} + +{% if capabilities is defined and capabilities.require_ht is defined %} # Require stations to support HT PHY (reject association if they do not) require_ht=1 {% endif %} -{%- if cap_vht_chan_set_width -%} -vht_oper_chwidth={{ cap_vht_chan_set_width }} -{%- endif %} - +{% if capabilities is defined and capabilities.vht is defined %} # vht_capab: VHT capabilities (list of flags) # # vht_max_mpdu_len: [MAX-MPDU-7991] [MAX-MPDU-11454] @@ -316,133 +284,95 @@ vht_oper_chwidth={{ cap_vht_chan_set_width }} # Tx Antenna Pattern Consistency: [TX-ANTENNA-PATTERN] # Indicates the possibility of Tx antenna pattern change # 0 = Tx antenna pattern might change during the lifetime of an association -# 1 = Tx antenna pattern does not change during the lifetime of an association -{% if cap_vht %} -vht_capab= -{%- endif -%} - -{%- if cap_vht_max_mpdu -%} -[MAX-MPDU-{{ cap_vht_max_mpdu }}] -{%- endif -%} - -{%- if cap_vht_max_mpdu_exp -%} -[MAX-A-MPDU-LEN-EXP{{ cap_vht_max_mpdu_exp }}] -{%- endif -%} - -{%- if cap_vht_chan_set_width -%} -{%- if '2' in cap_vht_chan_set_width -%} -[VHT160] -{%- elif '3' in cap_vht_chan_set_width -%} -[VHT160-80PLUS80] -{%- endif -%} -{%- endif -%} - -{%- if cap_vht_stbc_tx -%} -[TX-STBC-2BY1] -{%- endif -%} - -{%- if cap_vht_stbc_rx -%} -[RX-STBC-{{ cap_vht_stbc_rx }}] -{%- endif -%} - -{%- if cap_vht_link_adaptation -%} -{%- if 'unsolicited' in cap_vht_link_adaptation -%} -[VHT-LINK-ADAPT2] -{%- elif 'both' in cap_vht_link_adaptation -%} -[VHT-LINK-ADAPT3] -{%- endif -%} -{%- endif -%} - -{%- if cap_vht_short_gi -%} -{%- for gi in cap_vht_short_gi -%} -[SHORT-GI-{{ gi }}] -{%- endfor -%} -{%- endif -%} - -{%- if cap_vht_ldpc -%} -[RXLDPC] -{%- endif -%} - -{%- if cap_vht_tx_powersave -%} -[VHT-TXOP-PS] -{%- endif -%} - -{%- if cap_vht_vht_cf -%} -[HTC-VHT] -{%- endif -%} - -{%- if cap_vht_beamform -%} -{%- for beamform in cap_vht_beamform -%} -{%- if 'single-user-beamformer' in beamform -%} -[SU-BEAMFORMER] -{%- elif 'single-user-beamformee' in beamform -%} -[SU-BEAMFORMEE] -{%- elif 'multi-user-beamformer' in beamform -%} -[MU-BEAMFORMER] -{%- elif 'multi-user-beamformee' in beamform -%} -[MU-BEAMFORMEE] -{%- endif -%} -{%- endfor -%} -{%- endif -%} - -{%- if cap_vht_antenna_fixed -%} -[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN] -{%- endif -%} - -{%- if cap_vht_antenna_cnt -%} -{%- if cap_vht_antenna_cnt|int > 1 -%} -{%- if cap_vht_beamform -%} -{%- for beamform in cap_vht_beamform -%} -{%- if 'single-user-beamformer' in beamform -%} -{%- if cap_vht_antenna_cnt|int < 6 -%} -[BF-ANTENNA-{{ cap_vht_antenna_cnt|int -1 }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt|int -1}}] -{%- endif -%} -{%- else -%} -{%- if cap_vht_antenna_cnt|int < 5 -%} -[BF-ANTENNA-{{ cap_vht_antenna_cnt }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt }}] -{%- endif -%} -{%- endif -%} -{%- endfor -%} -{%- else -%} -{%- if cap_vht_antenna_cnt|int < 5 -%} -[BF-ANTENNA-{{ cap_vht_antenna_cnt }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt }}] -{%- endif -%} -{%- endif -%} -{%- endif -%} -{%- endif %} +# 1 = Tx antenna pattern does not change during the lifetime of an + +{% if capabilities.vht.center_channel_freq.freq_1 is defined %} +# center freq = 5 GHz + (5 * index) +# So index 42 gives center freq 5.210 GHz +# which is channel 42 in 5G band +vht_oper_centr_freq_seg0_idx={{ capabilities.vht.center_channel_freq.freq_1 }} +{% endif %} + +{% if capabilities.vht.center_channel_freq.freq_2 is defined %} +# center freq = 5 GHz + (5 * index) +# So index 159 gives center freq 5.795 GHz +# which is channel 159 in 5G band +vht_oper_centr_freq_seg1_idx={{ capabilities.vht.center_channel_freq.freq_2 }} +{% endif %} + +{% if capabilities.vht.channel_set_width is defined %} +vht_oper_chwidth={{ capabilities.vht.channel_set_width }} +{% endif %} + +{% set output = '' %} +{% set output = output + '[TX-STBC-2BY1]' if capabilities.vht.stbc.tx is defined else '' %} +{% set output = output + '[RXLDPC]' if capabilities.vht.ldpc is defined else '' %} +{% set output = output + '[VHT-TXOP-PS]' if capabilities.vht.tx_powersave is defined else '' %} +{% set output = output + '[HTC-VHT]' if capabilities.vht.vht_cf is defined else '' %} +{% set output = output + '[RX-ANTENNA-PATTERN]' if capabilities.vht.antenna_pattern_fixed is defined else '' %} +{% set output = output + '[TX-ANTENNA-PATTERN]' if capabilities.vht.antenna_pattern_fixed is defined else '' %} + +{% set output = output + '[RX-STBC-' + capabilities.vht.stbc.rx + ']' if capabilities.vht.stbc.rx is defined else '' %} +{% set output = output + '[MAX-MPDU-' + capabilities.vht.max_mpdu + ']' if capabilities.vht.max_mpdu is defined else '' %} +{% set output = output + '[MAX-A-MPDU-LEN-EXP-' + capabilities.vht.max_mpdu_exp + ']' if capabilities.vht.max_mpdu_exp is defined else '' %} +{% set output = output + '[MAX-A-MPDU-LEN-EXP-' + capabilities.vht.max_mpdu_exp + ']' if capabilities.vht.max_mpdu_exp is defined else '' %} + +{% set output = output + '[VHT160]' if capabilities.vht.max_mpdu_exp is defined and capabilities.vht.max_mpdu_exp == '2' else '' %} +{% set output = output + '[VHT160-80PLUS80]' if capabilities.vht.max_mpdu_exp is defined and capabilities.vht.max_mpdu_exp == '3' else '' %} +{% set output = output + '[VHT-LINK-ADAPT2]' if capabilities.vht.link_adaptation is defined and capabilities.vht.link_adaptation == 'unsolicited' else '' %} +{% set output = output + '[VHT-LINK-ADAPT3]' if capabilities.vht.link_adaptation is defined and capabilities.vht.link_adaptation == 'both' else '' %} + +{% if capabilities.vht.short_gi is defined %} +{% for short_gi in capabilities.vht.short_gi %} +{% set output = output + '[SHORT-GI-' + short_gi | upper + ']' %} +{% endfor %} +{% endif %} + +{% if capabilities.vht.beamform %} +{% for beamform in capabilities.vht.beamform %} +{% set output = output + '[SU-BEAMFORMER]' if beamform == 'single-user-beamformer' else '' %} +{% set output = output + '[SU-BEAMFORMEE]' if beamform == 'single-user-beamformee' else '' %} +{% set output = output + '[MU-BEAMFORMER]' if beamform == 'multi-user-beamformer' else '' %} +{% set output = output + '[MU-BEAMFORMEE]' if beamform == 'multi-user-beamformee' else '' %} +{% endfor %} +{% endif %} + +{% if capabilities.vht.antenna_count is defined and capabilities.vht.antenna_count|int > 1 %} +{% if capabilities.vht.beamform %} +{% if beamform == 'single-user-beamformer' %} +{% if capabilities.vht.antenna_count is defined and capabilities.vht.antenna_count|int > 1 and capabilities.vht.antenna_count|int < 6 %} +{% set output = output + '[BF-ANTENNA-' + capabilities.vht.antenna_count|int -1 + ']' %} +{% set output = output + '[SOUNDING-DIMENSION-' + capabilities.vht.antenna_count|int -1 + ']' %} +{% endif %} +{% endif %} +{% if capabilities.vht.antenna_count is defined and capabilities.vht.antenna_count|int > 1 and capabilities.vht.antenna_count|int < 5 %} +{% set output = output + '[BF-ANTENNA-' + capabilities.vht.antenna_count + ']' %} +{% set output = output + '[SOUNDING-DIMENSION-' + capabilities.vht.antenna_count+ ']' %} +{% endif %} +{% endif %} +{% endif %} + +vht_capab={{ output }} +{% endif %} # ieee80211n: Whether IEEE 802.11n (HT) is enabled # 0 = disabled (default) # 1 = enabled # Note: You will also need to enable WMM for full HT functionality. # Note: hw_mode=g (2.4 GHz) and hw_mode=a (5 GHz) is used to specify the band. -{% if cap_req_vht -%} +{% if capabilities is defined and capabilities.require_vht is defined %} ieee80211n=0 # Require stations to support VHT PHY (reject association if they do not) require_vht=1 -{% else -%} -{% if 'n' in mode or 'ac' in mode -%} +{% else %} +{% if 'n' in mode or 'ac' in mode %} ieee80211n=1 -{% else -%} +{% else %} ieee80211n=0 -{%- endif %} +{% endif %} {% endif %} -{% if cap_vht_center_freq_1 -%} -# center freq = 5 GHz + (5 * index) -# So index 42 gives center freq 5.210 GHz -# which is channel 42 in 5G band -vht_oper_centr_freq_seg0_idx={{ cap_vht_center_freq_1 }} -{% endif %} - -{% if cap_vht_center_freq_2 -%} -# center freq = 5 GHz + (5 * index) -# So index 159 gives center freq 5.795 GHz -# which is channel 159 in 5G band -vht_oper_centr_freq_seg1_idx={{ cap_vht_center_freq_2 }} -{% endif %} - -{% if disable_broadcast_ssid -%} +{% if disable_broadcast_ssid is defined %} # Send empty SSID in beacons and ignore probe request frames that do not # specify full SSID, i.e., require stations to know SSID. # default: disabled (0) @@ -463,7 +393,7 @@ ignore_broadcast_ssid=1 # 2 = use external RADIUS server (accept/deny lists are searched first) macaddr_acl=0 -{% if max_stations -%} +{% if max_stations is defined %} # Maximum number of stations allowed in station table. New stations will be # rejected after the station table is full. IEEE 802.11 has a limit of 2007 # different association IDs, so this number should not be larger than that. @@ -471,13 +401,13 @@ macaddr_acl=0 max_num_sta={{ max_stations }} {% endif %} -{% if isolate_stations -%} +{% if isolate_stations is defined %} # Client isolation can be used to prevent low-level bridging of frames between # associated stations in the BSS. By default, this bridging is allowed. ap_isolate=1 {% endif %} -{% if reduce_transmit_power -%} +{% if reduce_transmit_power is defined %} # Add Power Constraint element to Beacon and Probe Response frames # This config option adds Power Constraint element when applicable and Country # element is added. Power Constraint element is required by Transmit Power @@ -486,14 +416,15 @@ ap_isolate=1 local_pwr_constraint={{ reduce_transmit_power }} {% endif %} -{% if expunge_failing_stations -%} +{% if expunge_failing_stations is defined %} # Disassociate stations based on excessive transmission failures or other # indications of connection loss. This depends on the driver capabilities and # may not be available with all drivers. disassoc_low_ack=1 {% endif %} -{% if sec_wep -%} + +{% if security is defined and security.wep is defined %} # IEEE 802.11 specifies two authentication algorithms. hostapd can be # configured to allow both of these or only one. Open system authentication # should be used with IEEE 802.1X. @@ -522,13 +453,14 @@ wep_default_key=0 # digits, depending on whether 40-bit (64-bit), 104-bit (128-bit), or # 128-bit (152-bit) WEP is used. # Only the default key must be supplied; the others are optional. -{% if sec_wep_key -%} -{% for key in sec_wep_key -%} -wep_key{{ loop.index -1 }}={{ key}} -{% endfor %} -{%- endif %} +{% if security.wep.key is defined %} +{% for key in sec_wep_key %} +wep_key{{ loop.index -1 }}={{ security.wep.key }} +{% endfor %} +{% endif %} -{% elif sec_wpa -%} + +{% elif security is defined and security.wpa is defined %} ##### WPA/IEEE 802.11i configuration ########################################## # Enable WPA. Setting this variable configures the AP to require WPA (either @@ -542,15 +474,17 @@ wep_key{{ loop.index -1 }}={{ key}} # and/or WPA2 (full IEEE 802.11i/RSN): # bit0 = WPA # bit1 = IEEE 802.11i/RSN (WPA2) (dot11RSNAEnabled) -{% if 'both' in sec_wpa_mode -%} +{% if security.wpa.mode is defined %} +{% if security.wpa.mode == 'both' %} wpa=3 -{%- elif 'wpa2' in sec_wpa_mode -%} +{% elif security.wpa.mode == 'wpa2' %} wpa=2 -{%- elif 'wpa' in sec_wpa_mode -%} +{% elif security.wpa.mode == 'wpa' %} wpa=1 -{%- endif %} +{% endif %} +{% endif %} -{% if sec_wpa_cipher -%} +{% if security.wpa.cipher is defined %} # Set of accepted cipher suites (encryption algorithms) for pairwise keys # (unicast packets). This is a space separated list of algorithms: # CCMP = AES in Counter mode with CBC-MAC (CCMP-128) @@ -563,26 +497,39 @@ wpa=1 # allowed as the pairwise cipher, group cipher will also be CCMP. Otherwise, # TKIP will be used as the group cipher. The optional group_cipher parameter can # be used to override this automatic selection. -{% if 'wpa2' in sec_wpa_mode -%} + +{% if security.wpa.mode is defined and security.wpa.mode == 'wpa2' %} # Pairwise cipher for RSN/WPA2 (default: use wpa_pairwise value) -rsn_pairwise={{ sec_wpa_cipher | join(" ") }} -{% else -%} +{% if security.wpa.cipher is string %} +rsn_pairwise={{ security.wpa.cipher }} +{% else %} +rsn_pairwise={{ security.wpa.cipher | join(" ") }} +{% endif %} +{% else %} # Pairwise cipher for WPA (v1) (default: TKIP) -wpa_pairwise={{ sec_wpa_cipher | join(" ") }} -{%- endif -%} -{% endif %} - -{% if sec_wpa_group_cipher -%} +{% if security.wpa.cipher is string %} +wpa_pairwise={{ security.wpa.cipher }} +{% else %} +wpa_pairwise={{ security.wpa.cipher | join(" ") }} +{% endif %} +{% endif %} +{% endif %} + +{% if security.wpa.group_cipher is defined %} # Optional override for automatic group cipher selection # This can be used to select a specific group cipher regardless of which # pairwise ciphers were enabled for WPA and RSN. It should be noted that # overriding the group cipher with an unexpected value can result in # interoperability issues and in general, this parameter is mainly used for # testing purposes. -group_cipher={{ sec_wpa_group_cipher | join(" ") }} -{% endif %} - -{% if sec_wpa_passphrase -%} +{% if security.wpa.group_cipher is string %} +group_cipher={{ security.wpa.group_cipher }} +{% else %} +group_cipher={{ security.wpa.group_cipher | join(" ") }} +{% endif %} +{% endif %} + +{% if security.wpa.passphrase is defined %} # IEEE 802.11 specifies two authentication algorithms. hostapd can be # configured to allow both of these or only one. Open system authentication # should be used with IEEE 802.1X. @@ -595,7 +542,7 @@ auth_algs=1 # secret in hex format (64 hex digits), wpa_psk, or as an ASCII passphrase # (8..63 characters) that will be converted to PSK. This conversion uses SSID # so the PSK changes when ASCII passphrase is used and the SSID is changed. -wpa_passphrase={{ sec_wpa_passphrase }} +wpa_passphrase={{ security.wpa.passphrase }} # Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The # entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be @@ -604,7 +551,7 @@ wpa_passphrase={{ sec_wpa_passphrase }} # WPA-PSK-SHA256 = WPA2-Personal using SHA256 wpa_key_mgmt=WPA-PSK -{% elif sec_wpa_radius -%} +{% elif security.wpa.radius is defined %} ##### IEEE 802.1X-2004 related configuration ################################## # Require IEEE 802.1X authorization ieee8021x=1 @@ -616,40 +563,37 @@ ieee8021x=1 # WPA-EAP-SHA256 = WPA2-Enterprise using SHA256 wpa_key_mgmt=WPA-EAP -{% if sec_wpa_radius_source -%} +{% if security.wpa.radius.server is defined %} # RADIUS client forced local IP address for the access point # Normally the local IP address is determined automatically based on configured # IP addresses, but this field can be used to force a specific address to be # used, e.g., when the device has multiple IP addresses. -radius_client_addr={{ sec_wpa_radius_source }} - -# The own IP address of the access point (used as NAS-IP-Address) -own_ip_addr={{ sec_wpa_radius_source }} -{% else %} # The own IP address of the access point (used as NAS-IP-Address) +{% if security.wpa.radius.source_address is defined %} +radius_client_addr={{ security.wpa.radius.source_address }} +own_ip_addr={{ security.wpa.radius.source_address }} +{% else %} own_ip_addr=127.0.0.1 -{% endif %} +{% endif %} -{% for radius in sec_wpa_radius -%} -{%- if not radius.disabled -%} +{% for radius in security.wpa.radius.server if not radius.disabled %} # RADIUS authentication server auth_server_addr={{ radius.server }} auth_server_port={{ radius.port }} auth_server_shared_secret={{ radius.key }} -{% if radius.acc_port -%} + +{% if radius.acc_port %} # RADIUS accounting server acct_server_addr={{ radius.server }} acct_server_port={{ radius.acc_port }} acct_server_shared_secret={{ radius.key }} -{% endif %} -{% endif %} -{% endfor %} - -{% endif %} - -{% else %} +{% endif %} +{% endfor %} +{% else %} # Open system auth_algs=1 +{% endif %} +{% endif %} {% endif %} # TX queue parameters (EDCF / bursting) diff --git a/data/templates/wifi/wpa_supplicant.conf.tmpl b/data/templates/wifi/wpa_supplicant.conf.tmpl index 2784883f1..9ddad35fd 100644 --- a/data/templates/wifi/wpa_supplicant.conf.tmpl +++ b/data/templates/wifi/wpa_supplicant.conf.tmpl @@ -1,8 +1,8 @@ # WPA supplicant config network={ ssid="{{ ssid }}" -{%- if sec_wpa_passphrase %} - psk="{{ sec_wpa_passphrase }}" +{% if security is defined and security.wpa is defined and security.wpa.passphrase is defined %} + psk="{{ security.wpa.passphrase }}" {% else %} key_mgmt=NONE {% endif %} diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in index 06c7734f5..6f0ec9e71 100644 --- a/interface-definitions/interfaces-wireless.xml.in +++ b/interface-definitions/interfaces-wireless.xml.in @@ -320,7 +320,7 @@ VHT link adaptation capabilities - unsolicited both + unsolicited both unsolicited @@ -451,6 +451,7 @@ Disable broadcast of SSID from access-point + #include @@ -551,9 +552,10 @@ 802.11ac - 1300 Mbits/sec - (a|b|g|n|ac) + ^(a|b|g|n|ac)$ + g @@ -637,7 +639,7 @@ Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] - (GCMP-256|GCMP|CCMP-256|CCMP|TKIP) + ^(GCMP-256|GCMP|CCMP-256|CCMP|TKIP)$ Invalid cipher selection @@ -670,7 +672,7 @@ Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] - (GCMP-256|GCMP|CCMP-256|CCMP|TKIP) + ^(GCMP-256|GCMP|CCMP-256|CCMP|TKIP)$ Invalid group cipher selection @@ -695,7 +697,7 @@ Allow both WPA and WPA2 - (wpa|wpa2|both) + ^(wpa|wpa2|both)$ Unknown WPA mode @@ -762,10 +764,11 @@ Passively monitor all packets on the frequency/channel - (access-point|station|monitor) + ^(access-point|station|monitor)$ Type must be access-point, station or monitor + monitor #include #include diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 0162b642c..42b55ee6a 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -15,497 +15,169 @@ # along with this program. If not, see . import os + from sys import exit from re import findall - from copy import deepcopy - -from netifaces import interfaces from netaddr import EUI, mac_unix_expanded from vyos.config import Config -from vyos.configdict import list_diff, intf_to_dict, add_to_dict, interface_default_data -from vyos.ifconfig import WiFiIf, Section -from vyos.ifconfig_vlan import apply_all_vlans, verify_vlan_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_dhcpv6 +from vyos.configverify import verify_source_interface +from vyos.configverify import verify_vlan_config +from vyos.configverify import verify_vrf +from vyos.ifconfig import WiFiIf from vyos.template import render -from vyos.util import chown, call -from vyos.validate import is_member +from vyos.util import call from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - **interface_default_data, - 'cap_ht' : False, - 'cap_ht_40mhz_incapable' : False, - 'cap_ht_powersave' : False, - 'cap_ht_chan_set_width' : '', - 'cap_ht_delayed_block_ack' : False, - 'cap_ht_dsss_cck_40' : False, - 'cap_ht_greenfield' : False, - 'cap_ht_ldpc' : False, - 'cap_ht_lsig_protection' : False, - 'cap_ht_max_amsdu' : '', - 'cap_ht_short_gi' : [], - 'cap_ht_smps' : '', - 'cap_ht_stbc_rx' : '', - 'cap_ht_stbc_tx' : False, - 'cap_req_ht' : False, - 'cap_req_vht' : False, - 'cap_vht' : False, - 'cap_vht_antenna_cnt' : '', - 'cap_vht_antenna_fixed' : False, - 'cap_vht_beamform' : '', - 'cap_vht_center_freq_1' : '', - 'cap_vht_center_freq_2' : '', - 'cap_vht_chan_set_width' : '', - 'cap_vht_ldpc' : False, - 'cap_vht_link_adaptation' : '', - 'cap_vht_max_mpdu_exp' : '', - 'cap_vht_max_mpdu' : '', - 'cap_vht_short_gi' : [], - 'cap_vht_stbc_rx' : '', - 'cap_vht_stbc_tx' : False, - 'cap_vht_tx_powersave' : False, - 'cap_vht_vht_cf' : False, - 'channel': '', - 'country_code': '', - 'deleted': False, - 'disable_broadcast_ssid' : False, - 'disable_link_detect' : 1, - 'expunge_failing_stations' : False, - 'hw_id' : '', - 'intf': '', - 'isolate_stations' : False, - 'max_stations' : '', - 'mgmt_frame_protection' : 'disabled', - 'mode' : 'g', - 'phy' : '', - 'reduce_transmit_power' : '', - 'sec_wep' : False, - 'sec_wep_key' : [], - 'sec_wpa' : False, - 'sec_wpa_cipher' : [], - 'sec_wpa_mode' : 'both', - 'sec_wpa_passphrase' : '', - 'sec_wpa_radius' : [], - 'ssid' : '', - 'op_mode' : 'monitor', - 'vif': {}, - 'vif_remove': [], - 'vif_s': {}, - 'vif_s_remove': [] -} - # XXX: wpa_supplicant works on the source interface -wpa_suppl_conf = '/run/wpa_supplicant/{intf}.conf' -hostapd_conf = '/run/hostapd/{intf}.conf' +wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf' +hostapd_conf = '/run/hostapd/{ifname}.conf' + +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(): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ + conf = Config() + base = ['interfaces', 'wireless'] + # 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'] - conf = Config() - - # check if wireless interface has been removed - cfg_base = ['interfaces', 'wireless ', ifname] - if not conf.exists(cfg_base): - wifi = deepcopy(default_config_data) - wifi['intf'] = ifname - wifi['deleted'] = True - # we need to know if we're a bridge member so we can refuse deletion - wifi['is_bridge_member'] = is_member(conf, wifi['intf'], 'bridge') - # we can not bail out early as wireless interface can not be removed - # Kernel will complain with: RTNETLINK answers: Operation not supported. - # Thus we need to remove individual settings - return wifi - - # set new configuration level - conf.set_level(cfg_base) - - # get common interface settings - wifi, disabled = intf_to_dict(conf, default_config_data) - - # 40MHz intolerance, use 20MHz only - if conf.exists('capabilities ht 40mhz-incapable'): - wifi['cap_ht'] = True - wifi['cap_ht_40mhz_incapable'] = True - - # WMM-PS Unscheduled Automatic Power Save Delivery [U-APSD] - if conf.exists('capabilities ht auto-powersave'): - wifi['cap_ht'] = True - wifi['cap_ht_powersave'] = True - - # Supported channel set width - if conf.exists('capabilities ht channel-set-width'): - wifi['cap_ht'] = True - wifi['cap_ht_chan_set_width'] = conf.return_values('capabilities ht channel-set-width') - - # HT-delayed Block Ack - if conf.exists('capabilities ht delayed-block-ack'): - wifi['cap_ht'] = True - wifi['cap_ht_delayed_block_ack'] = True - - # DSSS/CCK Mode in 40 MHz - if conf.exists('capabilities ht dsss-cck-40'): - wifi['cap_ht'] = True - wifi['cap_ht_dsss_cck_40'] = True - - # HT-greenfield capability - if conf.exists('capabilities ht greenfield'): - wifi['cap_ht'] = True - wifi['cap_ht_greenfield'] = True - - # LDPC coding capability - if conf.exists('capabilities ht ldpc'): - wifi['cap_ht'] = True - wifi['cap_ht_ldpc'] = True - - # L-SIG TXOP protection capability - if conf.exists('capabilities ht lsig-protection'): - wifi['cap_ht'] = True - wifi['cap_ht_lsig_protection'] = True - - # Set Maximum A-MSDU length - if conf.exists('capabilities ht max-amsdu'): - wifi['cap_ht'] = True - wifi['cap_ht_max_amsdu'] = conf.return_value('capabilities ht max-amsdu') - - # Short GI capabilities - if conf.exists('capabilities ht short-gi'): - wifi['cap_ht'] = True - wifi['cap_ht_short_gi'] = conf.return_values('capabilities ht short-gi') - - # Spatial Multiplexing Power Save (SMPS) settings - if conf.exists('capabilities ht smps'): - wifi['cap_ht'] = True - wifi['cap_ht_smps'] = conf.return_value('capabilities ht smps') - - # Support for receiving PPDU using STBC (Space Time Block Coding) - if conf.exists('capabilities ht stbc rx'): - wifi['cap_ht'] = True - wifi['cap_ht_stbc_rx'] = conf.return_value('capabilities ht stbc rx') - - # Support for sending PPDU using STBC (Space Time Block Coding) - if conf.exists('capabilities ht stbc tx'): - wifi['cap_ht'] = True - wifi['cap_ht_stbc_tx'] = True - - # Require stations to support HT PHY (reject association if they do not) - if conf.exists('capabilities require-ht'): - wifi['cap_req_ht'] = True - - # Require stations to support VHT PHY (reject association if they do not) - if conf.exists('capabilities require-vht'): - wifi['cap_req_vht'] = True - - # Number of antennas on this card - if conf.exists('capabilities vht antenna-count'): - wifi['cap_vht'] = True - wifi['cap_vht_antenna_cnt'] = conf.return_value('capabilities vht antenna-count') - - # set if antenna pattern does not change during the lifetime of an association - if conf.exists('capabilities vht antenna-pattern-fixed'): - wifi['cap_vht'] = True - wifi['cap_vht_antenna_fixed'] = True - - # Beamforming capabilities - if conf.exists('capabilities vht beamform'): - wifi['cap_vht'] = True - wifi['cap_vht_beamform'] = conf.return_values('capabilities vht beamform') - - # VHT operating channel center frequency - center freq 1 (for use with 80, 80+80 and 160 modes) - if conf.exists('capabilities vht center-channel-freq freq-1'): - wifi['cap_vht'] = True - wifi['cap_vht_center_freq_1'] = conf.return_value('capabilities vht center-channel-freq freq-1') - - # VHT operating channel center frequency - center freq 2 (for use with the 80+80 mode) - if conf.exists('capabilities vht center-channel-freq freq-2'): - wifi['cap_vht'] = True - wifi['cap_vht_center_freq_2'] = conf.return_value('capabilities vht center-channel-freq freq-2') - - # VHT operating Channel width - if conf.exists('capabilities vht channel-set-width'): - wifi['cap_vht'] = True - wifi['cap_vht_chan_set_width'] = conf.return_value('capabilities vht channel-set-width') - - # LDPC coding capability - if conf.exists('capabilities vht ldpc'): - wifi['cap_vht'] = True - wifi['cap_vht_ldpc'] = True - - # VHT link adaptation capabilities - if conf.exists('capabilities vht link-adaptation'): - wifi['cap_vht'] = True - wifi['cap_vht_link_adaptation'] = conf.return_value('capabilities vht link-adaptation') - - # Set the maximum length of A-MPDU pre-EOF padding that the station can receive - if conf.exists('capabilities vht max-mpdu-exp'): - wifi['cap_vht'] = True - wifi['cap_vht_max_mpdu_exp'] = conf.return_value('capabilities vht max-mpdu-exp') - - # Increase Maximum MPDU length - if conf.exists('capabilities vht max-mpdu'): - wifi['cap_vht'] = True - wifi['cap_vht_max_mpdu'] = conf.return_value('capabilities vht max-mpdu') - - # Increase Maximum MPDU length - if conf.exists('capabilities vht short-gi'): - wifi['cap_vht'] = True - wifi['cap_vht_short_gi'] = conf.return_values('capabilities vht short-gi') - - # Support for receiving PPDU using STBC (Space Time Block Coding) - if conf.exists('capabilities vht stbc rx'): - wifi['cap_vht'] = True - wifi['cap_vht_stbc_rx'] = conf.return_value('capabilities vht stbc rx') - - # Support for the transmission of at least 2x1 STBC (Space Time Block Coding) - if conf.exists('capabilities vht stbc tx'): - wifi['cap_vht'] = True - wifi['cap_vht_stbc_tx'] = True - - # Support for VHT TXOP Power Save Mode - if conf.exists('capabilities vht tx-powersave'): - wifi['cap_vht'] = True - wifi['cap_vht_tx_powersave'] = True - - # STA supports receiving a VHT variant HT Control field - if conf.exists('capabilities vht vht-cf'): - wifi['cap_vht'] = True - wifi['cap_vht_vht_cf'] = True - - # Wireless radio channel - if conf.exists('channel'): - wifi['channel'] = conf.return_value('channel') - - # Disable broadcast of SSID from access-point - if conf.exists('disable-broadcast-ssid'): - wifi['disable_broadcast_ssid'] = True - - # Disassociate stations based on excessive transmission failures - if conf.exists('expunge-failing-stations'): - wifi['expunge_failing_stations'] = True - - # retrieve real hardware address - if conf.exists('hw-id'): - wifi['hw_id'] = conf.return_value('hw-id') - - # Isolate stations on the AP so they cannot see each other - if conf.exists('isolate-stations'): - wifi['isolate_stations'] = True - - # Wireless physical device - if conf.exists('physical-device'): - wifi['phy'] = conf.return_value('physical-device') - - # Maximum number of wireless radio stations - if conf.exists('max-stations'): - wifi['max_stations'] = conf.return_value('max-stations') - - # Management Frame Protection (MFP) according to IEEE 802.11w - if conf.exists('mgmt-frame-protection'): - wifi['mgmt_frame_protection'] = conf.return_value('mgmt-frame-protection') - - # Wireless radio mode - if conf.exists('mode'): - wifi['mode'] = conf.return_value('mode') - - # Transmission power reduction in dBm - if conf.exists('reduce-transmit-power'): - wifi['reduce_transmit_power'] = conf.return_value('reduce-transmit-power') - - # WEP enabled? - if conf.exists('security wep'): - wifi['sec_wep'] = True - - # WEP encryption key(s) - if conf.exists('security wep key'): - wifi['sec_wep_key'] = conf.return_values('security wep key') - - # WPA enabled? - if conf.exists('security wpa'): - wifi['sec_wpa'] = True - - # WPA Cipher suite - if conf.exists('security wpa cipher'): - wifi['sec_wpa_cipher'] = conf.return_values('security wpa cipher') - - # WPA mode - if conf.exists('security wpa mode'): - wifi['sec_wpa_mode'] = conf.return_value('security wpa mode') - - # WPA default ciphers depend on WPA mode - if not wifi['sec_wpa_cipher']: - if wifi['sec_wpa_mode'] == 'wpa': - wifi['sec_wpa_cipher'].append('TKIP') - wifi['sec_wpa_cipher'].append('CCMP') - - elif wifi['sec_wpa_mode'] == 'wpa2': - wifi['sec_wpa_cipher'].append('CCMP') - - elif wifi['sec_wpa_mode'] == 'both': - wifi['sec_wpa_cipher'].append('CCMP') - wifi['sec_wpa_cipher'].append('TKIP') - - # WPA Group Cipher suite - if conf.exists('security wpa group-cipher'): - wifi['sec_wpa_group_cipher'] = conf.return_values('security wpa group-cipher') - - # WPA personal shared pass phrase - if conf.exists('security wpa passphrase'): - wifi['sec_wpa_passphrase'] = conf.return_value('security wpa passphrase') - - # WPA RADIUS source address - if conf.exists('security wpa radius source-address'): - wifi['sec_wpa_radius_source'] = conf.return_value('security wpa radius source-address') - - # WPA RADIUS server - for server in conf.list_nodes('security wpa radius server'): - # set new configuration level - conf.set_level(cfg_base + ' security wpa radius server ' + server) - radius = { - 'server' : server, - 'acc_port' : '', - 'disabled': False, - 'port' : 1812, - 'key' : '' - } - - # RADIUS server port - if conf.exists('port'): - radius['port'] = int(conf.return_value('port')) - - # receive RADIUS accounting info - if conf.exists('accounting'): - radius['acc_port'] = radius['port'] + 1 - - # Check if RADIUS server was temporary disabled - if conf.exists(['disable']): - radius['disabled'] = True - - # RADIUS server shared-secret - if conf.exists('key'): - radius['key'] = conf.return_value('key') - - # append RADIUS server to list of servers - wifi['sec_wpa_radius'].append(radius) - - # re-set configuration level to parse new nodes - conf.set_level(cfg_base) - - # Wireless access-point service set identifier (SSID) - if conf.exists('ssid'): - wifi['ssid'] = conf.return_value('ssid') - - # Wireless device type for this interface - if conf.exists('type'): - tmp = conf.return_value('type') - if tmp == 'access-point': - tmp = 'ap' - - wifi['op_mode'] = tmp + wifi = get_interface_dict(conf, base, ifname) + + if 'security' in wifi and 'wpa' in wifi['security']: + 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']}}} + + if tmp: wifi = dict_merge(tmp, wifi) # retrieve configured regulatory domain - conf.set_level('system') - if conf.exists('wifi-regulatory-domain'): - wifi['country_code'] = conf.return_value('wifi-regulatory-domain') + conf.set_level(['system']) + if conf.exists(['wifi-regulatory-domain']): + wifi['country_code'] = conf.return_value(['wifi-regulatory-domain']) - return 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 + return wifi def verify(wifi): - if wifi['deleted']: - if wifi['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{wifi["intf"]}" as it is a ' - f'member of bridge "{wifi["is_bridge_member"]}"!')) - + if 'deleted' in wifi: + verify_bridge_delete(wifi) return None - if wifi['op_mode'] != 'monitor' and not wifi['ssid']: - raise ConfigError('SSID must be set for {}'.format(wifi['intf'])) - - if not wifi['phy']: - raise ConfigError('You must specify physical-device') + if 'physical_device' not in wifi: + raise ConfigError('You must specify a physical-device "phy"') - if not wifi['mode']: + if 'type' not in wifi: raise ConfigError('You must specify a WiFi mode') - if wifi['op_mode'] == 'ap': - c = Config() - if not c.exists('system wifi-regulatory-domain'): - raise ConfigError('Wireless regulatory domain is mandatory,\n' \ - 'use "set system wifi-regulatory-domain".') - - if not wifi['channel']: - raise ConfigError('Channel must be set for {}'.format(wifi['intf'])) + if 'ssid' not in wifi and wifi['type'] != 'monitor': + raise ConfigError('SSID must be configured') - if len(wifi['sec_wep_key']) > 4: - raise ConfigError('No more then 4 WEP keys configurable') - - if wifi['cap_vht'] and not wifi['cap_ht']: - raise ConfigError('Specify HT flags if you want to use VHT!') - - if wifi['cap_vht_beamform'] and wifi['cap_vht_antenna_cnt'] == 1: - raise ConfigError('Cannot use beam forming with just one antenna!') - - if wifi['cap_vht_beamform'] == 'single-user-beamformer' and wifi['cap_vht_antenna_cnt'] < 3: - # Nasty Gotcha: see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf lines 692-705 - raise ConfigError('Single-user beam former requires at least 3 antennas!') - - if wifi['sec_wep'] and (len(wifi['sec_wep_key']) == 0): - raise ConfigError('Missing WEP keys') - - if wifi['sec_wpa'] and not (wifi['sec_wpa_passphrase'] or wifi['sec_wpa_radius']): - raise ConfigError('Misssing WPA key or RADIUS server') - - for radius in wifi['sec_wpa_radius']: - if not radius['key']: - raise ConfigError('Misssing RADIUS shared secret key for server: {}'.format(radius['server'])) - - if ( wifi['is_bridge_member'] - and ( wifi['address'] - or wifi['ipv6_eui64_prefix'] - or wifi['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{wifi["intf"]}" ' - f'as it is a member of bridge "{wifi["is_bridge_member"]}"!')) - - if wifi['vrf']: - if wifi['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{wifi["vrf"]}" does not exist') - - if wifi['is_bridge_member']: - raise ConfigError(( - f'Interface "{wifi["intf"]}" cannot be member of VRF ' - f'"{wifi["vrf"]}" and bridge {wifi["is_bridge_member"]} ' - f'at the same time!')) + if wifi['type'] == 'access-point': + if 'country_code' not in wifi: + raise ConfigError('Wireless regulatory domain is mandatory,\n' \ + 'use "set system wifi-regulatory-domain" for configuration.') + + if 'channel' not in wifi: + raise ConfigError('Wireless channel must be configured!') + + 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 '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'Misssing 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 https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf lines 692-705 + 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) # use common function to verify VLAN configuration verify_vlan_config(wifi) - conf = Config() - # Only one wireless interface per phy can be in station mode - base = ['interfaces', 'wireless'] - for phy in os.listdir('/sys/class/ieee80211'): - stations = [] - for wlan in conf.list_nodes(base): - # the following node is mandatory - if conf.exists(base + [wlan, 'physical-device', phy]): - tmp = conf.return_value(base + [wlan, 'type']) - if tmp == 'station': - stations.append(wlan) - - if len(stations) > 1: - raise ConfigError('Only one station per wireless physical interface possible!') - return None def generate(wifi): - interface = wifi['intf'] + interface = wifi['ifname'] # always stop hostapd service first before reconfiguring it call(f'systemctl stop hostapd@{interface}.service') @@ -513,7 +185,7 @@ def generate(wifi): call(f'systemctl stop wpa_supplicant@{interface}.service') # Delete config files if interface is removed - if wifi['deleted']: + if 'deleted' in wifi: if os.path.isfile(hostapd_conf.format(**wifi)): os.unlink(hostapd_conf.format(**wifi)) @@ -522,10 +194,10 @@ def generate(wifi): return None - if not wifi['mac']: + 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/{}/addresses'.format(wifi['phy']), 'r') as f: + 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() @@ -545,20 +217,18 @@ def generate(wifi): wifi['mac'] = str(mac) # render appropriate new config files depending on access-point or station mode - if wifi['op_mode'] == 'ap': - render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl', wifi) + if wifi['type'] == 'access-point': + render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl', wifi, trim_blocks=True) - elif wifi['op_mode'] == 'station': - render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.tmpl', wifi) + elif wifi['type'] == 'station': + render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.tmpl', wifi, trim_blocks=True) return None def apply(wifi): - interface = wifi['intf'] - if wifi['deleted']: - w = WiFiIf(interface) - # delete interface - w.remove() + interface = wifi['ifname'] + if 'deleted' in wifi: + WiFiIf(interface).remove() else: # WiFi interface needs to be created on-block (e.g. mode or physical # interface) instead of passing a ton of arguments, I just use a dict @@ -566,97 +236,21 @@ def apply(wifi): conf = deepcopy(WiFiIf.get_config()) # Assign WiFi instance configuration parameters to config dict - conf['phy'] = wifi['phy'] + conf['phy'] = wifi['physical_device'] # Finally create the new interface w = WiFiIf(interface, **conf) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not wifi['is_bridge_member']: - w.set_vrf(wifi['vrf']) - - # update interface description used e.g. within SNMP - w.set_alias(wifi['description']) - - if wifi['dhcp_client_id']: - w.dhcp.v4.options['client_id'] = wifi['dhcp_client_id'] - - if wifi['dhcp_hostname']: - w.dhcp.v4.options['hostname'] = wifi['dhcp_hostname'] - - if wifi['dhcp_vendor_class_id']: - w.dhcp.v4.options['vendor_class_id'] = wifi['dhcp_vendor_class_id'] - - if wifi['dhcpv6_prm_only']: - w.dhcp.v6.options['dhcpv6_prm_only'] = True - - if wifi['dhcpv6_temporary']: - w.dhcp.v6.options['dhcpv6_temporary'] = True - - if wifi['dhcpv6_pd_length']: - w.dhcp.v6.options['dhcpv6_pd_length'] = wifi['dhcpv6_pd_length'] - - if wifi['dhcpv6_pd_interfaces']: - w.dhcp.v6.options['dhcpv6_pd_interfaces'] = wifi['dhcpv6_pd_interfaces'] - - # ignore link state changes - w.set_link_detect(wifi['disable_link_detect']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in wifi['ipv6_eui64_prefix_remove']: - w.del_ipv6_eui64_address(addr) - - # Change interface MAC address - re-set to real hardware address (hw-id) - # if custom mac is removed - if wifi['mac']: - w.set_mac(wifi['mac']) - elif wifi['hw_id']: - w.set_mac(wifi['hw_id']) - - # Add IPv6 EUI-based addresses - for addr in wifi['ipv6_eui64_prefix']: - w.add_ipv6_eui64_address(addr) - - # configure ARP filter configuration - w.set_arp_filter(wifi['ip_disable_arp_filter']) - # configure ARP accept - w.set_arp_accept(wifi['ip_enable_arp_accept']) - # configure ARP announce - w.set_arp_announce(wifi['ip_enable_arp_announce']) - # configure ARP ignore - w.set_arp_ignore(wifi['ip_enable_arp_ignore']) - # IPv6 accept RA - w.set_ipv6_accept_ra(wifi['ipv6_accept_ra']) - # IPv6 address autoconfiguration - w.set_ipv6_autoconf(wifi['ipv6_autoconf']) - # IPv6 forwarding - w.set_ipv6_forwarding(wifi['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - w.set_ipv6_dad_messages(wifi['ipv6_dup_addr_detect']) - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in wifi['address_remove']: - w.del_addr(addr) - for addr in wifi['address']: - w.add_addr(addr) - - # apply all vlans to interface - apply_all_vlans(w, wifi) + w.update(wifi) # Enable/Disable interface - interface is always placed in # administrative down state in WiFiIf class - if not wifi['disable']: - w.set_admin_state('up') - + if 'disable' not in wifi: # Physical interface is now configured. Proceed by starting hostapd or # wpa_supplicant daemon. When type is monitor we can just skip this. - if wifi['op_mode'] == 'ap': + if wifi['type'] == 'access-point': call(f'systemctl start hostapd@{interface}.service') - elif wifi['op_mode'] == 'station': + elif wifi['type'] == 'station': call(f'systemctl start wpa_supplicant@{interface}.service') return None -- cgit v1.2.3 From 72c0ac35b4acf049de29ce1ea67af28659793098 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 24 Jul 2020 22:00:57 +0200 Subject: vyos.configdict: T2653: remove obsolete code from configdict and ifconfig_vlan After all interfaces have been moved to the targetted implementation of T2653 the old implementations of migrating a CLI session to a configuration dict can be dropped. --- python/vyos/configdict.py | 380 ------------------------------------------- python/vyos/ifconfig_vlan.py | 245 ---------------------------- 2 files changed, 625 deletions(-) delete mode 100644 python/vyos/ifconfig_vlan.py diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index d79365722..53b5f9492 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -267,383 +267,3 @@ def get_interface_dict(config, base, ifname): return dict -dhcpv6_pd_default_data = { - 'dhcpv6_prm_only': False, - 'dhcpv6_temporary': False, - 'dhcpv6_pd_length': '', - 'dhcpv6_pd_interfaces': [] -} - -interface_default_data = { - **dhcpv6_pd_default_data, - 'address': [], - 'address_remove': [], - 'description': '', - 'dhcp_client_id': '', - 'dhcp_hostname': '', - 'dhcp_vendor_class_id': '', - 'disable': False, - 'disable_link_detect': 1, - 'ip_disable_arp_filter': 1, - 'ip_enable_arp_accept': 0, - 'ip_enable_arp_announce': 0, - 'ip_enable_arp_ignore': 0, - 'ip_proxy_arp': 0, - 'ipv6_accept_ra': 1, - 'ipv6_autoconf': 0, - 'ipv6_eui64_prefix': [], - 'ipv6_eui64_prefix_remove': [], - 'ipv6_forwarding': 1, - 'ipv6_dup_addr_detect': 1, - 'is_bridge_member': False, - 'mac': '', - 'mtu': 1500, - 'vrf': '' -} - -vlan_default = { - **interface_default_data, - 'egress_qos': '', - 'egress_qos_changed': False, - 'ingress_qos': '', - 'ingress_qos_changed': False, - 'vif_c': {}, - 'vif_c_remove': [] -} - -# see: https://docs.python.org/3/library/enum.html#functional-api -disable = Enum('disable','none was now both') - -def disable_state(conf, check=[3,5,7]): - """ - return if and how a particual section of the configuration is has disable'd - using "disable" including if it was disabled by one of its parent. - - check: a list of the level we should check, here 7,5 and 3 - interfaces ethernet eth1 vif-s 1 vif-c 2 disable - interfaces ethernet eth1 vif 1 disable - interfaces ethernet eth1 disable - - it returns an enum (none, was, now, both) - """ - - # save where we are in the config - current_level = conf.get_level() - - # logic to figure out if the interface (or one of it parent is disabled) - eff_disable = False - act_disable = False - - levels = check[:] - working_level = current_level[:] - - while levels: - position = len(working_level) - if not position: - break - if position not in levels: - working_level = working_level[:-1] - continue - - levels.remove(position) - conf.set_level(working_level) - working_level = working_level[:-1] - - eff_disable = eff_disable or conf.exists_effective('disable') - act_disable = act_disable or conf.exists('disable') - - conf.set_level(current_level) - - # how the disabling changed - if eff_disable and act_disable: - return disable.both - if eff_disable and not eff_disable: - return disable.was - if not eff_disable and act_disable: - return disable.now - return disable.none - - -def intf_to_dict(conf, default): - from vyos.ifconfig import Interface - - """ - Common used function which will extract VLAN related information from config - and represent the result as Python dictionary. - - Function call's itself recursively if a vif-s/vif-c pair is detected. - """ - - intf = deepcopy(default) - intf['intf'] = ifname_from_config(conf) - - current_vif_list = conf.list_nodes(['vif']) - previous_vif_list = conf.list_effective_nodes(['vif']) - - # set the vif to be deleted - for vif in previous_vif_list: - if vif not in current_vif_list: - intf['vif_remove'].append(vif) - - # retrieve interface description - if conf.exists(['description']): - intf['description'] = conf.return_value(['description']) - - # get DHCP client identifier - if conf.exists(['dhcp-options', 'client-id']): - intf['dhcp_client_id'] = conf.return_value(['dhcp-options', 'client-id']) - - # DHCP client host name (overrides the system host name) - if conf.exists(['dhcp-options', 'host-name']): - intf['dhcp_hostname'] = conf.return_value(['dhcp-options', 'host-name']) - - # DHCP client vendor identifier - if conf.exists(['dhcp-options', 'vendor-class-id']): - intf['dhcp_vendor_class_id'] = conf.return_value( - ['dhcp-options', 'vendor-class-id']) - - # DHCPv6 only acquire config parameters, no address - if conf.exists(['dhcpv6-options', 'parameters-only']): - intf['dhcpv6_prm_only'] = True - - # DHCPv6 prefix delegation (RFC3633) - current_level = conf.get_level() - if conf.exists(['dhcpv6-options', 'prefix-delegation']): - dhcpv6_pd_path = current_level + ['dhcpv6-options', 'prefix-delegation'] - conf.set_level(dhcpv6_pd_path) - - # retriebe DHCPv6-PD prefix helper length as some ISPs only hand out a - # /64 by default (https://phabricator.vyos.net/T2506) - if conf.exists(['length']): - intf['dhcpv6_pd_length'] = conf.return_value(['length']) - - for interface in conf.list_nodes(['interface']): - conf.set_level(dhcpv6_pd_path + ['interface', interface]) - pd = { - 'ifname': interface, - 'sla_id': '', - 'sla_len': '', - 'if_id': '' - } - - if conf.exists(['sla-id']): - pd['sla_id'] = conf.return_value(['sla-id']) - - if conf.exists(['sla-len']): - pd['sla_len'] = conf.return_value(['sla-len']) - - if conf.exists(['address']): - pd['if_id'] = conf.return_value(['address']) - - intf['dhcpv6_pd_interfaces'].append(pd) - - # re-set config level - conf.set_level(current_level) - - # DHCPv6 temporary IPv6 address - if conf.exists(['dhcpv6-options', 'temporary']): - intf['dhcpv6_temporary'] = True - - # ignore link state changes - if conf.exists(['disable-link-detect']): - intf['disable_link_detect'] = 2 - - # ARP filter configuration - if conf.exists(['ip', 'disable-arp-filter']): - intf['ip_disable_arp_filter'] = 0 - - # ARP enable accept - if conf.exists(['ip', 'enable-arp-accept']): - intf['ip_enable_arp_accept'] = 1 - - # ARP enable announce - if conf.exists(['ip', 'enable-arp-announce']): - intf['ip_enable_arp_announce'] = 1 - - # ARP enable ignore - if conf.exists(['ip', 'enable-arp-ignore']): - intf['ip_enable_arp_ignore'] = 1 - - # Enable Proxy ARP - if conf.exists(['ip', 'enable-proxy-arp']): - intf['ip_proxy_arp'] = 1 - - # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) - if conf.exists(['ipv6', 'address', 'autoconf']): - intf['ipv6_autoconf'] = 1 - - # Disable IPv6 forwarding on this interface - if conf.exists(['ipv6', 'disable-forwarding']): - intf['ipv6_forwarding'] = 0 - - # check if interface is member of a bridge - intf['is_bridge_member'] = is_member(conf, intf['intf'], 'bridge') - - # IPv6 Duplicate Address Detection (DAD) tries - if conf.exists(['ipv6', 'dup-addr-detect-transmits']): - intf['ipv6_dup_addr_detect'] = int( - conf.return_value(['ipv6', 'dup-addr-detect-transmits'])) - - # Media Access Control (MAC) address - if conf.exists(['mac']): - intf['mac'] = conf.return_value(['mac']) - - # Maximum Transmission Unit (MTU) - if conf.exists(['mtu']): - intf['mtu'] = int(conf.return_value(['mtu'])) - - # retrieve VRF instance - if conf.exists(['vrf']): - intf['vrf'] = conf.return_value(['vrf']) - - # egress QoS - if conf.exists(['egress-qos']): - intf['egress_qos'] = conf.return_value(['egress-qos']) - - # egress changes QoS require VLAN interface recreation - if conf.return_effective_value(['egress-qos']): - if intf['egress_qos'] != conf.return_effective_value(['egress-qos']): - intf['egress_qos_changed'] = True - - # ingress QoS - if conf.exists(['ingress-qos']): - intf['ingress_qos'] = conf.return_value(['ingress-qos']) - - # ingress changes QoS require VLAN interface recreation - if conf.return_effective_value(['ingress-qos']): - if intf['ingress_qos'] != conf.return_effective_value(['ingress-qos']): - intf['ingress_qos_changed'] = True - - # Get the interface addresses - intf['address'] = conf.return_values(['address']) - - # addresses to remove - difference between effective and working config - intf['address_remove'] = list_diff( - conf.return_effective_values(['address']), intf['address']) - - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - intf['ipv6_eui64_prefix'] = conf.return_values(['ipv6', 'address', 'eui64']) - - # EUI64 to remove - difference between effective and working config - intf['ipv6_eui64_prefix_remove'] = list_diff( - conf.return_effective_values(['ipv6', 'address', 'eui64']), - intf['ipv6_eui64_prefix']) - - # Determine if the interface should be disabled - disabled = disable_state(conf) - if disabled == disable.both: - # was and is still disabled - intf['disable'] = True - elif disabled == disable.now: - # it is now disable but was not before - intf['disable'] = True - elif disabled == disable.was: - # it was disable but not anymore - intf['disable'] = False - else: - # normal change - intf['disable'] = False - - # Remove the default link-local address if no-default-link-local is set, - # if member of a bridge or if disabled (it may not have a MAC if it's down) - if ( conf.exists(['ipv6', 'address', 'no-default-link-local']) - or intf.get('is_bridge_member') or intf['disable'] ): - intf['ipv6_eui64_prefix_remove'].append('fe80::/64') - else: - # add the link-local by default to make IPv6 work - intf['ipv6_eui64_prefix'].append('fe80::/64') - - # If MAC has changed, remove and re-add all IPv6 EUI64 addresses - try: - interface = Interface(intf['intf'], create=False) - if intf['mac'] and intf['mac'] != interface.get_mac(): - intf['ipv6_eui64_prefix_remove'] += intf['ipv6_eui64_prefix'] - except Exception: - # If the interface does not exist, it could not have changed - pass - - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, - # accept_ra must be 2 - if intf['ipv6_autoconf'] or 'dhcpv6' in intf['address']: - intf['ipv6_accept_ra'] = 2 - - return intf, disable - - - -def add_to_dict(conf, disabled, ifdict, section, key): - """ - parse a section of vif/vif-s/vif-c and add them to the dict - follow the convention to: - * use the "key" for what to add - * use the "key" what what to remove - - conf: is the Config() already at the level we need to parse - disabled: is a disable enum so we know how to handle to data - intf: if the interface dictionary - section: is the section name to parse (vif/vif-s/vif-c) - key: is the dict key to use (vif/vifs/vifc) - """ - - if not conf.exists(section): - return ifdict - - effect = conf.list_effective_nodes(section) - active = conf.list_nodes(section) - - # the section to parse for vlan - sections = [] - - # determine which interfaces to add or remove based on disable state - if disabled == disable.both: - # was and is still disabled - ifdict[f'{key}_remove'] = [] - elif disabled == disable.now: - # it is now disable but was not before - ifdict[f'{key}_remove'] = effect - elif disabled == disable.was: - # it was disable but not anymore - ifdict[f'{key}_remove'] = [] - sections = active - else: - # normal change - # get interfaces (currently effective) - to determine which - # interface is no longer present and needs to be removed - ifdict[f'{key}_remove'] = list_diff(effect, active) - sections = active - - current_level = conf.get_level() - - # add each section, the key must already exists - for s in sections: - # set config level to vif interface - conf.set_level(current_level + [section, s]) - # add the vlan config as a key (vlan id) - value (config) pair - ifdict[key][s] = vlan_to_dict(conf) - - # re-set configuration level to leave things as found - conf.set_level(current_level) - - return ifdict - - -def vlan_to_dict(conf, default=vlan_default): - from vyos.ifconfig.interface import get_ethertype - vlan, disabled = intf_to_dict(conf, default) - - # if this is a not within vif-s node, we are done - if conf.get_level()[-2] != 'vif-s': - return vlan - - # ethertype is mandatory on vif-s nodes and only exists here! - # ethertype uses a default of 0x88A8 - tmp = '0x88A8' - if conf.exists('ethertype'): - tmp = conf.return_value('ethertype') - vlan['ethertype'] = get_ethertype(tmp) - - # check if there is a Q-in-Q vlan customer interface - # and call this function recursively - add_to_dict(conf, disable, vlan, 'vif-c', 'vif_c') - - return vlan diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py deleted file mode 100644 index 442cb0db8..000000000 --- a/python/vyos/ifconfig_vlan.py +++ /dev/null @@ -1,245 +0,0 @@ -# Copyright 2019-2020 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 . - -from netifaces import interfaces -from vyos import ConfigError - -def apply_all_vlans(intf, intfconfig): - """ - Function applies all VLANs to the passed interface. - - intf: object of Interface class - intfconfig: dict with interface configuration - """ - # remove no longer required service VLAN interfaces (vif-s) - for vif_s in intfconfig['vif_s_remove']: - intf.del_vlan(vif_s) - - # create service VLAN interfaces (vif-s) - for vif_s_id, vif_s in intfconfig['vif_s'].items(): - s_vlan = intf.add_vlan(vif_s_id, ethertype=vif_s['ethertype']) - apply_vlan_config(s_vlan, vif_s) - - # remove no longer required client VLAN interfaces (vif-c) - # on lower service VLAN interface - for vif_c in vif_s['vif_c_remove']: - s_vlan.del_vlan(vif_c) - - # create client VLAN interfaces (vif-c) - # on lower service VLAN interface - for vif_c_id, vif_c in vif_s['vif_c'].items(): - c_vlan = s_vlan.add_vlan(vif_c_id) - apply_vlan_config(c_vlan, vif_c) - - # remove no longer required VLAN interfaces (vif) - for vif in intfconfig['vif_remove']: - intf.del_vlan(vif) - - # create VLAN interfaces (vif) - for vif_id, vif in intfconfig['vif'].items(): - # QoS priority mapping can only be set during interface creation - # so we delete the interface first if required. - if vif['egress_qos_changed'] or vif['ingress_qos_changed']: - try: - # on system bootup the above condition is true but the interface - # does not exists, which throws an exception, but that's legal - intf.del_vlan(vif_id) - except: - pass - - vlan = intf.add_vlan(vif_id, ingress_qos=vif['ingress_qos'], egress_qos=vif['egress_qos']) - apply_vlan_config(vlan, vif) - - -def apply_vlan_config(vlan, config): - """ - Generic function to apply a VLAN configuration from a dictionary - to a VLAN interface - """ - - if not vlan.definition['vlan']: - raise TypeError() - - if config['dhcp_client_id']: - vlan.dhcp.v4.options['client_id'] = config['dhcp_client_id'] - - if config['dhcp_hostname']: - vlan.dhcp.v4.options['hostname'] = config['dhcp_hostname'] - - if config['dhcp_vendor_class_id']: - vlan.dhcp.v4.options['vendor_class_id'] = config['dhcp_vendor_class_id'] - - if config['dhcpv6_prm_only']: - vlan.dhcp.v6.options['dhcpv6_prm_only'] = True - - if config['dhcpv6_temporary']: - vlan.dhcp.v6.options['dhcpv6_temporary'] = True - - if config['dhcpv6_pd_length']: - vlan.dhcp.v6.options['dhcpv6_pd_length'] = config['dhcpv6_pd_length'] - - if config['dhcpv6_pd_interfaces']: - vlan.dhcp.v6.options['dhcpv6_pd_interfaces'] = config['dhcpv6_pd_interfaces'] - - # update interface description used e.g. within SNMP - vlan.set_alias(config['description']) - # ignore link state changes - vlan.set_link_detect(config['disable_link_detect']) - # configure ARP filter configuration - vlan.set_arp_filter(config['ip_disable_arp_filter']) - # configure ARP accept - vlan.set_arp_accept(config['ip_enable_arp_accept']) - # configure ARP announce - vlan.set_arp_announce(config['ip_enable_arp_announce']) - # configure ARP ignore - vlan.set_arp_ignore(config['ip_enable_arp_ignore']) - # configure Proxy ARP - vlan.set_proxy_arp(config['ip_proxy_arp']) - # IPv6 accept RA - vlan.set_ipv6_accept_ra(config['ipv6_accept_ra']) - # IPv6 address autoconfiguration - vlan.set_ipv6_autoconf(config['ipv6_autoconf']) - # IPv6 forwarding - vlan.set_ipv6_forwarding(config['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - vlan.set_ipv6_dad_messages(config['ipv6_dup_addr_detect']) - # Maximum Transmission Unit (MTU) - vlan.set_mtu(config['mtu']) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not config['is_bridge_member']: - vlan.set_vrf(config['vrf']) - - # Delete old IPv6 EUI64 addresses before changing MAC - for addr in config['ipv6_eui64_prefix_remove']: - vlan.del_ipv6_eui64_address(addr) - - # Change VLAN interface MAC address - if config['mac']: - vlan.set_mac(config['mac']) - - # Add IPv6 EUI-based addresses - for addr in config['ipv6_eui64_prefix']: - vlan.add_ipv6_eui64_address(addr) - - # enable/disable VLAN interface - if config['disable']: - vlan.set_admin_state('down') - else: - vlan.set_admin_state('up') - - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in config['address_remove']: - vlan.del_addr(addr) - for addr in config['address']: - vlan.add_addr(addr) - - # re-add ourselves to any bridge we might have fallen out of - if config['is_bridge_member']: - vlan.add_to_bridge(config['is_bridge_member']) - -def verify_vlan_config(config): - """ - Generic function to verify VLAN config consistency. Instead of re- - implementing this function in multiple places use single source \o/ - """ - - # config['vif'] is a dict with ids as keys and config dicts as values - for vif in config['vif'].values(): - # DHCPv6 parameters-only and temporary address are mutually exclusive - if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - - if ( vif['is_bridge_member'] - and ( vif['address'] - or vif['ipv6_eui64_prefix'] - or vif['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to vif interface {vif["intf"]} ' - f'which is a member of bridge {vif["is_bridge_member"]}')) - - if vif['vrf']: - if vif['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{vif["vrf"]}" does not exist') - - if vif['is_bridge_member']: - raise ConfigError(( - f'vif {vif["intf"]} cannot be member of VRF {vif["vrf"]} ' - f'and bridge {vif["is_bridge_member"]} at the same time!')) - - # e.g. wireless interface has no vif_s support - # thus we bail out eraly. - if 'vif_s' not in config.keys(): - return - - # config['vif_s'] is a dict with ids as keys and config dicts as values - for vif_s_id, vif_s in config['vif_s'].items(): - for vif_id, vif in config['vif'].items(): - if vif_id == vif_s_id: - raise ConfigError(( - f'Cannot use identical ID on vif "{vif["intf"]}" ' - f'and vif-s "{vif_s["intf"]}"')) - - # DHCPv6 parameters-only and temporary address are mutually exclusive - if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']: - raise ConfigError(( - 'DHCPv6 temporary and parameters-only options are mutually ' - 'exclusive!')) - - if ( vif_s['is_bridge_member'] - and ( vif_s['address'] - or vif_s['ipv6_eui64_prefix'] - or vif_s['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to vif-s interface {vif_s["intf"]} ' - f'which is a member of bridge {vif_s["is_bridge_member"]}')) - - if vif_s['vrf']: - if vif_s['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{vif_s["vrf"]}" does not exist') - - if vif_s['is_bridge_member']: - raise ConfigError(( - f'vif-s {vif_s["intf"]} cannot be member of VRF {vif_s["vrf"]} ' - f'and bridge {vif_s["is_bridge_member"]} at the same time!')) - - # vif_c is a dict with ids as keys and config dicts as values - for vif_c in vif_s['vif_c'].values(): - # DHCPv6 parameters-only and temporary address are mutually exclusive - if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']: - raise ConfigError(( - 'DHCPv6 temporary and parameters-only options are ' - 'mutually exclusive!')) - - if ( vif_c['is_bridge_member'] - and ( vif_c['address'] - or vif_c['ipv6_eui64_prefix'] - or vif_c['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to vif-c interface {vif_c["intf"]} ' - f'which is a member of bridge {vif_c["is_bridge_member"]}')) - - if vif_c['vrf']: - if vif_c['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{vif_c["vrf"]}" does not exist') - - if vif_c['is_bridge_member']: - raise ConfigError(( - f'vif-c {vif_c["intf"]} cannot be member of VRF {vif_c["vrf"]} ' - f'and bridge {vif_c["is_bridge_member"]} at the same time!')) - -- cgit v1.2.3 From e70a304e36fc6456e16fea81ace4a0a5fd8bd1df Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jul 2020 00:39:14 +0200 Subject: ifconfig: T2653: make ifname an optional argument to get_interface_dict() Further reduce the boiler-plate code to determine interface tag node or not. It can be passed into get_interface_dict() if explicitly required - else it is taken from the environment. --- python/vyos/configdict.py | 11 ++++++++--- src/conf_mode/interfaces-bonding.py | 10 ++-------- src/conf_mode/interfaces-bridge.py | 11 ++--------- src/conf_mode/interfaces-dummy.py | 8 +------- src/conf_mode/interfaces-ethernet.py | 8 +------- src/conf_mode/interfaces-geneve.py | 9 +-------- src/conf_mode/interfaces-loopback.py | 8 +------- src/conf_mode/interfaces-macsec.py | 8 +------- src/conf_mode/interfaces-pppoe.py | 8 +------- src/conf_mode/interfaces-pseudo-ethernet.py | 8 +------- src/conf_mode/interfaces-wireless.py | 8 +------- src/conf_mode/interfaces-wirelessmodem.py | 9 +-------- 12 files changed, 21 insertions(+), 85 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 53b5f9492..126d6195a 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -15,8 +15,8 @@ """ A library for retrieving value dicts from VyOS configs in a declarative fashion. - """ +import os import jmespath from enum import Enum @@ -24,7 +24,6 @@ from copy import deepcopy from vyos import ConfigError from vyos.validate import is_member -from vyos.util import ifname_from_config def retrieve_config(path_hash, base_path, config): """ @@ -195,7 +194,7 @@ def get_removed_vlans(conf, dict): return dict -def get_interface_dict(config, base, ifname): +def get_interface_dict(config, base, ifname=''): """ Common utility function to retrieve and mandgle the interfaces available in CLI configuration. All interfaces have a common base ground where the @@ -205,6 +204,12 @@ def get_interface_dict(config, base, ifname): """ from vyos.xml import defaults + if not ifname: + # 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'] + # retrieve interface default values default_values = defaults(base) diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 8e87a0059..3b238f1ea 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -60,13 +60,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'bonding'] - - # 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'] - bond = get_interface_dict(conf, base, ifname) + bond = get_interface_dict(conf, base) # To make our own life easier transfor the list of member interfaces # into a dictionary - we will use this to add additional information @@ -107,7 +101,7 @@ def get_config(): # Check if we are a member of a bond device tmp = is_member(conf, interface, 'bonding') - if tmp and tmp != ifname: + if tmp and tmp != bond['ifname']: interface_config.update({'is_bond_member' : tmp}) # bond members must not have an assigned address diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index 9c43d1983..ee8e85e73 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -41,13 +41,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'bridge'] - - # 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'] - bridge = get_interface_dict(conf, base, ifname) + bridge = get_interface_dict(conf, base) # determine which members have been removed tmp = node_changed(conf, ['member', 'interface']) @@ -69,13 +63,12 @@ def get_config(): # the default dictionary is not properly paged into the dict (see T2665) # thus we will ammend it ourself default_member_values = defaults(base + ['member', 'interface']) - for interface, interface_config in bridge['member']['interface'].items(): interface_config.update(default_member_values) # Check if we are a member of another bridge device tmp = is_member(conf, interface, 'bridge') - if tmp and tmp != ifname: + if tmp and tmp != bridge['ifname']: interface_config.update({'is_bridge_member' : tmp}) # Check if we are a member of a bond device diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 6d2a78e30..8df86c8ea 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -35,13 +35,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'dummy'] - - # 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'] - dummy = get_interface_dict(conf, base, ifname) + dummy = get_interface_dict(conf, base) return dummy def verify(dummy): diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 24ea0af7c..10758e35a 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -37,13 +37,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'ethernet'] - - # 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'] - ethernet = get_interface_dict(conf, base, ifname) + ethernet = get_interface_dict(conf, base) return ethernet def verify(ethernet): diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 868ab5ccf..1104bd3c0 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -37,14 +37,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'geneve'] - - # 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'] - geneve = get_interface_dict(conf, base, ifname) - + geneve = get_interface_dict(conf, base) return geneve def verify(geneve): diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index 68a1392ff..0398cd591 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -32,13 +32,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'loopback'] - - # 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'] - loopback = get_interface_dict(conf, base, ifname) + loopback = get_interface_dict(conf, base) return loopback def verify(loopback): diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 06aee9ea0..ca15212d4 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -42,13 +42,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'macsec'] - - # 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'] - macsec = get_interface_dict(conf, base, ifname) + macsec = get_interface_dict(conf, base) # Check if interface has been removed if 'deleted' in macsec: diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 6947cc1e2..b9a88a949 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -38,13 +38,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'pppoe'] - - # 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'] - pppoe = get_interface_dict(conf, base, ifname) + pppoe = get_interface_dict(conf, base) # PPPoE is "special" the default MTU is 1492 - update accordingly # as the config_level is already st in get_interface_dict() - we can use [] diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index 55f11e65e..4afea2b3a 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -40,13 +40,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'pseudo-ethernet'] - - # 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'] - peth = get_interface_dict(conf, base, ifname) + peth = get_interface_dict(conf, base) mode = leaf_node_changed(conf, ['mode']) if mode: diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 42b55ee6a..b6f247952 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -71,13 +71,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'wireless'] - - # 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'] - wifi = get_interface_dict(conf, base, ifname) + wifi = get_interface_dict(conf, base) if 'security' in wifi and 'wpa' in wifi['security']: wpa_cipher = wifi['security']['wpa'].get('cipher') diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 9a5dae9e0..4081be3c9 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -48,14 +48,7 @@ def get_config(): """ conf = Config() base = ['interfaces', 'wirelessmodem'] - - # 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'] - wwan = get_interface_dict(conf, base, ifname) - + wwan = get_interface_dict(conf, base) return wwan def verify(wwan): -- cgit v1.2.3 From e57d76e86f7e5280eb065e98552c7d6395805c01 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jul 2020 17:53:32 +0200 Subject: vyos.configverify: T2653: fix some formatting issues --- python/vyos/configverify.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 36b10c956..8e06d16f2 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -79,12 +79,12 @@ def verify_source_interface(config): required by e.g. peth/MACvlan, MACsec ... """ from netifaces import interfaces - if not 'source_interface' in config.keys(): + if 'source_interface' not in config: raise ConfigError('Physical source-interface required for ' 'interface "{ifname}"'.format(**config)) - if not config['source_interface'] in interfaces(): - raise ConfigError(f'Source interface {source_interface} does not ' - f'exist'.format(**config)) + if config['source_interface'] not in interfaces(): + raise ConfigError('Source interface {source_interface} does not ' + 'exist'.format(**config)) def verify_dhcpv6(config): """ -- cgit v1.2.3 From 56255d62fe1afe9da83e50478a89a720a7dd08ab Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jul 2020 21:32:48 +0200 Subject: Revert "Merge pull request #423 from thomas-mangin/T2494" This reverts commit bfbf51acb2d4b6b5fe2d22d39f7259686f98d2a0, reversing changes made to 1a85e758b105d493bb9d95916816bd206345bc5d. --- debian/control | 1 - src/services/vyos-hostsd | 1 - src/services/vyos-http-api-server | 4 +--- src/systemd/vyos-hostsd.service | 6 +++--- src/systemd/vyos-http-api.service | 8 ++++++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/debian/control b/debian/control index 089b2d6e2..3a441b47b 100644 --- a/debian/control +++ b/debian/control @@ -36,7 +36,6 @@ Depends: python3, python3-xmltodict, python3-pyudev, python3-voluptuous, - python3-systemd, bsdmainutils, cron, etherwake, diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index 53ac5a770..0079f7e5c 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -592,7 +592,6 @@ if __name__ == '__main__': socket.bind(SOCKET_PATH) os.umask(o_mask) - systemd.daemon.notify('READY=1') while True: # Wait for next request from client msg_json = socket.recv().decode() diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 3eecaba5a..d5730d86c 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -28,7 +28,6 @@ import vyos.config from flask import Flask, request from waitress import serve -import systemd.daemon from functools import wraps @@ -394,9 +393,8 @@ if __name__ == '__main__': signal.signal(signal.SIGTERM, sig_handler) - systemd.daemon.notify('READY=1') try: serve(app, host=server_config["listen_address"], port=server_config["port"]) except OSError as e: - sys.exit(f"OSError {e}") + print(f"OSError {e}") diff --git a/src/systemd/vyos-hostsd.service b/src/systemd/vyos-hostsd.service index 26e9c745e..b77335778 100644 --- a/src/systemd/vyos-hostsd.service +++ b/src/systemd/vyos-hostsd.service @@ -1,6 +1,5 @@ [Unit] Description=VyOS DNS configuration keeper -Before=vyos-router.service # Without this option, lots of default dependencies are added, # among them network.target, which creates a dependency cycle @@ -15,7 +14,7 @@ WorkingDirectory=/run/vyos-hostsd RuntimeDirectory=vyos-hostsd RuntimeDirectoryPreserve=yes ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-hostsd -Type=notify +Type=idle KillMode=process SyslogIdentifier=vyos-hostsd @@ -23,6 +22,7 @@ SyslogFacility=daemon Restart=on-failure +# Does't work in Jessie but leave it here User=root Group=hostsd @@ -31,4 +31,4 @@ Group=hostsd # 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. -WantedBy=vyos.target +RequiredBy=cloud-init-local.service vyos-router.service diff --git a/src/systemd/vyos-http-api.service b/src/systemd/vyos-http-api.service index 74a97d193..4fa68b4ff 100644 --- a/src/systemd/vyos-http-api.service +++ b/src/systemd/vyos-http-api.service @@ -4,8 +4,9 @@ After=auditd.service systemd-user-sessions.service time-sync.target vyos-router. Requires=vyos-router.service [Service] +ExecStartPre=/usr/libexec/vyos/init/vyos-config ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-http-api-server -Type=notify +Type=idle KillMode=process SyslogIdentifier=vyos-http-api @@ -13,8 +14,11 @@ SyslogFacility=daemon Restart=on-failure +# Does't work but leave it here User=root Group=vyattacfg [Install] -WantedBy=vyos.target +# Installing in a earlier target leaves ExecStartPre waiting +WantedBy=getty.target + -- cgit v1.2.3 From fbfab33180dfac893df6447ab3fb5ef9fa8d7e28 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jul 2020 23:51:30 +0200 Subject: l2tpv3: add initial interface test --- scripts/cli/test_interfaces_l2tpv3.py | 59 +++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 scripts/cli/test_interfaces_l2tpv3.py diff --git a/scripts/cli/test_interfaces_l2tpv3.py b/scripts/cli/test_interfaces_l2tpv3.py new file mode 100755 index 000000000..d8655d157 --- /dev/null +++ b/scripts/cli/test_interfaces_l2tpv3.py @@ -0,0 +1,59 @@ +#!/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 . + +import json +import jmespath +import unittest + +from base_interfaces_test import BasicInterfaceTest +from vyos.util import cmd + +class GeneveInterfaceTest(BasicInterfaceTest.BaseTest): + def setUp(self): + super().setUp() + + self._base_path = ['interfaces', 'l2tpv3'] + self._options = { + 'l2tpeth10': ['local-ip 127.0.0.1', 'remote-ip 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': ['local-ip 127.0.0.1', 'peer-session-id 20', + 'peer-tunnel-id 200', 'remote-ip 127.20.20.20', + 'session-id 20', 'tunnel-id 200', + 'source-port 2020', 'destination-port 20202'], + } + self._interfaces = list(self._options) + + def test_add_address_single(self): + super().test_add_address_single() + + 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__': + unittest.main() -- cgit v1.2.3 From 675942ce3e2329a0122da189cd5944df08d7fcab Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jul 2020 23:52:12 +0200 Subject: l2tpv3: ifconfig: T2653: move implementation to get_interface_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- interface-definitions/interfaces-l2tpv3.xml.in | 3 + src/conf_mode/interfaces-l2tpv3.py | 252 +++++-------------------- 2 files changed, 48 insertions(+), 207 deletions(-) diff --git a/interface-definitions/interfaces-l2tpv3.xml.in b/interface-definitions/interfaces-l2tpv3.xml.in index 30dd9b604..3a878ad76 100644 --- a/interface-definitions/interfaces-l2tpv3.xml.in +++ b/interface-definitions/interfaces-l2tpv3.xml.in @@ -29,6 +29,7 @@ + 5000 #include @@ -50,6 +51,7 @@ Encapsulation must be UDP or IP + udp @@ -138,6 +140,7 @@ + 5000 diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 866419f2c..0978df5b6 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -21,196 +21,65 @@ from copy import deepcopy from netifaces import interfaces from vyos.config import Config -from vyos.ifconfig import L2TPv3If, Interface -from vyos import ConfigError -from vyos.util import call +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.ifconfig import L2TPv3If from vyos.util import check_kmod -from vyos.validate import is_member, is_addr_assigned - +from vyos.validate import is_addr_assigned +from vyos import ConfigError from vyos import airbag airbag.enable() k_mod = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] -default_config_data = { - 'address': [], - 'deleted': False, - 'description': '', - 'disable': False, - 'encapsulation': 'udp', - 'local_address': '', - 'local_port': 5000, - 'intf': '', - 'ipv6_accept_ra': 1, - 'ipv6_autoconf': 0, - 'ipv6_eui64_prefix': [], - 'ipv6_forwarding': 1, - 'ipv6_dup_addr_detect': 1, - 'is_bridge_member': False, - 'mtu': 1488, - 'peer_session_id': '', - 'peer_tunnel_id': '', - 'remote_address': '', - 'remote_port': 5000, - 'session_id': '', - 'tunnel_id': '' -} def get_config(): - l2tpv3 = deepcopy(default_config_data) + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'l2tpv3'] + l2tpv3 = get_interface_dict(conf, base) - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - l2tpv3['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # check if interface is member of a bridge - l2tpv3['is_bridge_member'] = is_member(conf, l2tpv3['intf'], 'bridge') - - # Check if interface has been removed - if not conf.exists('interfaces l2tpv3 ' + l2tpv3['intf']): - l2tpv3['deleted'] = True - interface = l2tpv3['intf'] - - # to delete the l2tpv3 interface we need the current tunnel_id and session_id - if conf.exists_effective(f'interfaces l2tpv3 {interface} tunnel-id'): - l2tpv3['tunnel_id'] = conf.return_effective_value(f'interfaces l2tpv3 {interface} tunnel-id') - - if conf.exists_effective(f'interfaces l2tpv3 {interface} session-id'): - l2tpv3['session_id'] = conf.return_effective_value(f'interfaces l2tpv3 {interface} session-id') - - return l2tpv3 - - # set new configuration level - conf.set_level('interfaces l2tpv3 ' + l2tpv3['intf']) - - # retrieve configured interface addresses - if conf.exists('address'): - l2tpv3['address'] = conf.return_values('address') - - # retrieve interface description - if conf.exists('description'): - l2tpv3['description'] = conf.return_value('description') - - # get tunnel destination port - if conf.exists('destination-port'): - l2tpv3['remote_port'] = int(conf.return_value('destination-port')) - - # Disable this interface - if conf.exists('disable'): - l2tpv3['disable'] = True - - # get tunnel encapsulation type - if conf.exists('encapsulation'): - l2tpv3['encapsulation'] = conf.return_value('encapsulation') - - # get tunnel local ip address - if conf.exists('local-ip'): - l2tpv3['local_address'] = conf.return_value('local-ip') - - # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) - if conf.exists('ipv6 address autoconf'): - l2tpv3['ipv6_autoconf'] = 1 - - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - if conf.exists('ipv6 address eui64'): - l2tpv3['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - - # Remove the default link-local address if set. - if not ( conf.exists('ipv6 address no-default-link-local') or - l2tpv3['is_bridge_member'] ): - # add the link-local by default to make IPv6 work - l2tpv3['ipv6_eui64_prefix'].append('fe80::/64') - - # Disable IPv6 forwarding on this interface - if conf.exists('ipv6 disable-forwarding'): - l2tpv3['ipv6_forwarding'] = 0 + # L2TPv3 is "special" the default MTU is 1488 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + l2tpv3['mtu'] = '1488' - # IPv6 Duplicate Address Detection (DAD) tries - if conf.exists('ipv6 dup-addr-detect-transmits'): - l2tpv3['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) + # To delete an l2tpv3 interface we need the current tunnel and session-id + if 'deleted' in l2tpv3: + tmp = leaf_node_changed(conf, ['tunnel-id']) + l2tpv3.update({'tunnel_id': tmp}) - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, - # accept_ra must be 2 - if l2tpv3['ipv6_autoconf'] or 'dhcpv6' in l2tpv3['address']: - l2tpv3['ipv6_accept_ra'] = 2 - - # Maximum Transmission Unit (MTU) - if conf.exists('mtu'): - l2tpv3['mtu'] = int(conf.return_value('mtu')) - - # Remote session id - if conf.exists('peer-session-id'): - l2tpv3['peer_session_id'] = conf.return_value('peer-session-id') - - # Remote tunnel id - if conf.exists('peer-tunnel-id'): - l2tpv3['peer_tunnel_id'] = conf.return_value('peer-tunnel-id') - - # Remote address of L2TPv3 tunnel - if conf.exists('remote-ip'): - l2tpv3['remote_address'] = conf.return_value('remote-ip') - - # Local session id - if conf.exists('session-id'): - l2tpv3['session_id'] = conf.return_value('session-id') - - # get local tunnel port - if conf.exists('source-port'): - l2tpv3['local_port'] = conf.return_value('source-port') - - # get local tunnel id - if conf.exists('tunnel-id'): - l2tpv3['tunnel_id'] = conf.return_value('tunnel-id') + tmp = leaf_node_changed(conf, ['session-id']) + l2tpv3.update({'session_id': tmp}) return l2tpv3 - def verify(l2tpv3): - interface = l2tpv3['intf'] - - if l2tpv3['deleted']: - if l2tpv3['is_bridge_member']: - raise ConfigError(( - f'Interface "{l2tpv3["intf"]}" cannot be deleted as it is a ' - f'member of bridge "{l2tpv3["is_bridge_member"]}"!')) - + if 'deleted' in l2tpv3: + verify_bridge_delete(l2tpv3) return None - if not l2tpv3['local_address']: - raise ConfigError(f'Must configure the l2tpv3 local-ip for {interface}') + interface = l2tpv3['ifname'] - if not is_addr_assigned(l2tpv3['local_address']): - raise ConfigError(f'Must use a configured IP on l2tpv3 local-ip for {interface}') + for key in ['local_ip', 'remote_ip', 'tunnel_id', 'peer_tunnel_id', + 'session_id', 'peer_session_id']: + if key not in l2tpv3: + tmp = key.replace('_', '-') + raise ConfigError(f'L2TPv3 {tmp} must be configured!') - if not l2tpv3['remote_address']: - raise ConfigError(f'Must configure the l2tpv3 remote-ip for {interface}') - - if not l2tpv3['tunnel_id']: - raise ConfigError(f'Must configure the l2tpv3 tunnel-id for {interface}') - - if not l2tpv3['peer_tunnel_id']: - raise ConfigError(f'Must configure the l2tpv3 peer-tunnel-id for {interface}') - - if not l2tpv3['session_id']: - raise ConfigError(f'Must configure the l2tpv3 session-id for {interface}') - - if not l2tpv3['peer_session_id']: - raise ConfigError(f'Must configure the l2tpv3 peer-session-id for {interface}') - - if ( l2tpv3['is_bridge_member'] - and ( l2tpv3['address'] - or l2tpv3['ipv6_eui64_prefix'] - or l2tpv3['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{l2tpv3["intf"]}" ' - f'as it is a member of bridge "{l2tpv3["is_bridge_member"]}"!')) + if not is_addr_assigned(l2tpv3['local_ip']): + raise ConfigError('L2TPv3 local-ip address ' + '"{local_ip}" is not configured!'.format(**l2tpv3)) + verify_address(l2tpv3) return None - def generate(l2tpv3): return None @@ -221,59 +90,28 @@ def apply(l2tpv3): conf = deepcopy(L2TPv3If.get_config()) # Check if L2TPv3 interface already exists - if l2tpv3['intf'] in interfaces(): + if l2tpv3['ifname'] in interfaces(): # L2TPv3 is picky when changing tunnels/sessions, thus we can simply # always delete it first. conf['session_id'] = l2tpv3['session_id'] conf['tunnel_id'] = l2tpv3['tunnel_id'] - l = L2TPv3If(l2tpv3['intf'], **conf) + l = L2TPv3If(l2tpv3['ifname'], **conf) l.remove() - if not l2tpv3['deleted']: + if 'deleted' not in l2tpv3: conf['peer_tunnel_id'] = l2tpv3['peer_tunnel_id'] - conf['local_port'] = l2tpv3['local_port'] - conf['remote_port'] = l2tpv3['remote_port'] + conf['local_port'] = l2tpv3['source_port'] + conf['remote_port'] = l2tpv3['destination_port'] conf['encapsulation'] = l2tpv3['encapsulation'] - conf['local_address'] = l2tpv3['local_address'] - conf['remote_address'] = l2tpv3['remote_address'] + conf['local_address'] = l2tpv3['local_ip'] + conf['remote_address'] = l2tpv3['remote_ip'] conf['session_id'] = l2tpv3['session_id'] conf['tunnel_id'] = l2tpv3['tunnel_id'] conf['peer_session_id'] = l2tpv3['peer_session_id'] # Finally create the new interface - l = L2TPv3If(l2tpv3['intf'], **conf) - # update interface description used e.g. by SNMP - l.set_alias(l2tpv3['description']) - # Maximum Transfer Unit (MTU) - l.set_mtu(l2tpv3['mtu']) - # IPv6 accept RA - l.set_ipv6_accept_ra(l2tpv3['ipv6_accept_ra']) - # IPv6 address autoconfiguration - l.set_ipv6_autoconf(l2tpv3['ipv6_autoconf']) - # IPv6 forwarding - l.set_ipv6_forwarding(l2tpv3['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - l.set_ipv6_dad_messages(l2tpv3['ipv6_dup_addr_detect']) - - # Configure interface address(es) - no need to implicitly delete the - # old addresses as they have already been removed by deleting the - # interface above - for addr in l2tpv3['address']: - l.add_addr(addr) - - # IPv6 EUI-based addresses - for addr in l2tpv3['ipv6_eui64_prefix']: - l.add_ipv6_eui64_address(addr) - - # As the interface is always disabled first when changing parameters - # we will only re-enable the interface if it is not administratively - # disabled - if not l2tpv3['disable']: - l.set_admin_state('up') - - # re-add ourselves to any bridge we might have fallen out of - if l2tpv3['is_bridge_member']: - l.add_to_bridge(l2tpv3['is_bridge_member']) + l = L2TPv3If(l2tpv3['ifname'], **conf) + l.update(l2tpv3) return None -- cgit v1.2.3 From e388ed14fa31f403cd9aadf99965b253fad9f816 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 26 Jul 2020 10:23:56 +0200 Subject: pseudo-ethernet: extend smoketests with VIFs --- scripts/cli/base_interfaces_test.py | 12 ++++++++++-- scripts/cli/test_interfaces_pseudo_ethernet.py | 23 ++++++++++++----------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index fa3e8a0c1..8fae5970e 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -142,9 +142,13 @@ class BasicInterfaceTest: if not self._test_vlan: return None - for intf in self._interfaces: + for interface in self._interfaces: + base = self._base_path + [interface] + for option in self._options.get(interface, []): + self.session.set(base + option.split()) + for vlan in self._vlan_range: - base = self._base_path + [intf, 'vif', vlan] + base = self._base_path + [interface, 'vif', vlan] self.session.set(base + ['mtu', self._mtu]) for address in self._test_addr: self.session.set(base + ['address', address]) @@ -164,6 +168,10 @@ class BasicInterfaceTest: return None for interface in self._interfaces: + base = self._base_path + [interface] + for option in self._options.get(interface, []): + self.session.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] diff --git a/scripts/cli/test_interfaces_pseudo_ethernet.py b/scripts/cli/test_interfaces_pseudo_ethernet.py index 1f5de4f61..bc2e6e7eb 100755 --- a/scripts/cli/test_interfaces_pseudo_ethernet.py +++ b/scripts/cli/test_interfaces_pseudo_ethernet.py @@ -21,18 +21,19 @@ from base_interfaces_test import BasicInterfaceTest class PEthInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): - super().setUp() - self._base_path = ['interfaces', 'pseudo-ethernet'] - options = ['source-interface eth0', 'ip arp-cache-timeout 10', - 'ip disable-arp-filter', 'ip enable-arp-accept', - 'ip enable-arp-announce', 'ip enable-arp-ignore', - 'ip enable-proxy-arp', 'ip proxy-arp-pvlan'] + super().setUp() + self._base_path = ['interfaces', 'pseudo-ethernet'] - self._options = { - 'peth0': options, - 'peth1': options, - } - self._interfaces = list(self._options) + self._test_ip = True + self._test_mtu = True + self._test_vlan = True + self._test_qinq = True + + self._options = { + 'peth0': ['source-interface eth1'], + 'peth1': ['source-interface eth1'], + } + self._interfaces = list(self._options) if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 9578890e1e4dc31da7387a6ad810d2efb22391e7 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Sun, 26 Jul 2020 14:06:01 +0300 Subject: Fix a typo in BFD command help. --- interface-definitions/protocols-bfd.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/protocols-bfd.xml.in b/interface-definitions/protocols-bfd.xml.in index 62e2c87b9..8900e7955 100644 --- a/interface-definitions/protocols-bfd.xml.in +++ b/interface-definitions/protocols-bfd.xml.in @@ -28,7 +28,7 @@ - Bind listener to specifid interface/address, mandatory for IPv6 + Bind listener to specified interface/address, mandatory for IPv6 -- cgit v1.2.3 From 6b531e04474d7d976d7cee11e556c3fe3dc2b69f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 26 Jul 2020 09:55:18 +0200 Subject: pseudo-ethernet: ifconfig: T2733: support MTU configuration --- interface-definitions/interfaces-pseudo-ethernet.xml.in | 1 + 1 file changed, 1 insertion(+) diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in index 0ef45e2c2..4382db598 100644 --- a/interface-definitions/interfaces-pseudo-ethernet.xml.in +++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in @@ -72,6 +72,7 @@ private + #include #include #include -- cgit v1.2.3 From a156d2f1479affe4e7cfa56785e4d2d61a776cea Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 26 Jul 2020 11:16:13 +0200 Subject: vxlan: ifconfig: T2653: move to get_interface_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- interface-definitions/interfaces-vxlan.xml.in | 1 + python/vyos/ifconfig/vxlan.py | 20 +- src/conf_mode/interfaces-vxlan.py | 253 ++++---------------------- 3 files changed, 46 insertions(+), 228 deletions(-) diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index bd3ab4022..8529f6885 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -93,6 +93,7 @@ + 8472 diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 973b4ef05..0dddab7b7 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -47,8 +47,8 @@ class VXLANIf(Interface): 'port': 8472, # The Linux implementation of VXLAN pre-dates # the IANA's selection of a standard destination port 'remote': '', - 'src_address': '', - 'src_interface': '', + 'source_address': '', + 'source_interface': '', 'vni': 0 } definition = { @@ -60,29 +60,29 @@ class VXLANIf(Interface): } } options = Interface.options + \ - ['group', 'remote', 'src_interface', 'port', 'vni', 'src_address'] + ['group', 'remote', 'source_interface', 'port', 'vni', 'source_address'] mapping = { 'ifname': 'add', 'vni': 'id', 'port': 'dstport', - 'src_address': 'local', - 'src_interface': 'dev', + 'source_address': 'local', + 'source_interface': 'dev', } def _create(self): cmdline = ['ifname', 'type', 'vni', 'port'] - if self.config['src_address']: - cmdline.append('src_address') + if self.config['source_address']: + cmdline.append('source_address') if self.config['remote']: cmdline.append('remote') - if self.config['group'] or self.config['src_interface']: - if self.config['group'] and self.config['src_interface']: + if self.config['group'] or self.config['source_interface']: + if self.config['group'] and self.config['source_interface']: cmdline.append('group') - cmdline.append('src_interface') + cmdline.append('source_interface') else: ifname = self.config['ifname'] raise ConfigError( diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 39db814b4..47c0bdcb8 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -21,197 +21,61 @@ from copy import deepcopy from netifaces import interfaces 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_source_interface from vyos.ifconfig import VXLANIf, Interface -from vyos.validate import is_member from vyos import ConfigError - from vyos import airbag airbag.enable() -default_config_data = { - 'address': [], - 'deleted': False, - 'description': '', - 'disable': False, - 'group': '', - 'intf': '', - 'ip_arp_cache_tmo': 30, - 'ip_disable_arp_filter': 1, - 'ip_enable_arp_accept': 0, - 'ip_enable_arp_announce': 0, - 'ip_enable_arp_ignore': 0, - 'ip_proxy_arp': 0, - 'ipv6_accept_ra': 1, - 'ipv6_autoconf': 0, - 'ipv6_eui64_prefix': [], - 'ipv6_forwarding': 1, - 'ipv6_dup_addr_detect': 1, - 'is_bridge_member': False, - 'source_address': '', - 'source_interface': '', - 'mtu': 1450, - 'remote': '', - 'remote_port': 8472, # The Linux implementation of VXLAN pre-dates - # the IANA's selection of a standard destination port - 'vni': '' -} - def get_config(): - vxlan = deepcopy(default_config_data) + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() + base = ['interfaces', 'vxlan'] + vxlan = get_interface_dict(conf, base) - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - vxlan['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # check if interface is member if a bridge - vxlan['is_bridge_member'] = is_member(conf, vxlan['intf'], 'bridge') - - # Check if interface has been removed - if not conf.exists('interfaces vxlan ' + vxlan['intf']): - vxlan['deleted'] = True - return vxlan - - # set new configuration level - conf.set_level('interfaces vxlan ' + vxlan['intf']) - - # retrieve configured interface addresses - if conf.exists('address'): - vxlan['address'] = conf.return_values('address') - - # retrieve interface description - if conf.exists('description'): - vxlan['description'] = conf.return_value('description') - - # Disable this interface - if conf.exists('disable'): - vxlan['disable'] = True - - # VXLAN multicast grou - if conf.exists('group'): - vxlan['group'] = conf.return_value('group') - - # ARP cache entry timeout in seconds - if conf.exists('ip arp-cache-timeout'): - vxlan['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) - - # ARP filter configuration - if conf.exists('ip disable-arp-filter'): - vxlan['ip_disable_arp_filter'] = 0 - - # ARP enable accept - if conf.exists('ip enable-arp-accept'): - vxlan['ip_enable_arp_accept'] = 1 - - # ARP enable announce - if conf.exists('ip enable-arp-announce'): - vxlan['ip_enable_arp_announce'] = 1 - - # ARP enable ignore - if conf.exists('ip enable-arp-ignore'): - vxlan['ip_enable_arp_ignore'] = 1 - - # Enable proxy-arp on this interface - if conf.exists('ip enable-proxy-arp'): - vxlan['ip_proxy_arp'] = 1 - - # Enable acquisition of IPv6 address using stateless autoconfig (SLAAC) - if conf.exists('ipv6 address autoconf'): - vxlan['ipv6_autoconf'] = 1 - - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - if conf.exists('ipv6 address eui64'): - vxlan['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - - # Remove the default link-local address if set. - if not ( conf.exists('ipv6 address no-default-link-local') - or vxlan['is_bridge_member'] ): - # add the link-local by default to make IPv6 work - vxlan['ipv6_eui64_prefix'].append('fe80::/64') - - # Disable IPv6 forwarding on this interface - if conf.exists('ipv6 disable-forwarding'): - vxlan['ipv6_forwarding'] = 0 - - # IPv6 Duplicate Address Detection (DAD) tries - if conf.exists('ipv6 dup-addr-detect-transmits'): - vxlan['ipv6_dup_addr_detect'] = int(conf.return_value('ipv6 dup-addr-detect-transmits')) - - # to make IPv6 SLAAC and DHCPv6 work with forwarding=1, - # accept_ra must be 2 - if vxlan['ipv6_autoconf'] or 'dhcpv6' in vxlan['address']: - vxlan['ipv6_accept_ra'] = 2 - - # VXLAN source address - if conf.exists('source-address'): - vxlan['source_address'] = conf.return_value('source-address') - - # VXLAN underlay interface - if conf.exists('source-interface'): - vxlan['source_interface'] = conf.return_value('source-interface') - - # Maximum Transmission Unit (MTU) - if conf.exists('mtu'): - vxlan['mtu'] = int(conf.return_value('mtu')) - - # Remote address of VXLAN tunnel - if conf.exists('remote'): - vxlan['remote'] = conf.return_value('remote') - - # Remote port of VXLAN tunnel - if conf.exists('port'): - vxlan['remote_port'] = int(conf.return_value('port')) - - # Virtual Network Identifier - if conf.exists('vni'): - vxlan['vni'] = conf.return_value('vni') + # VXLAN is "special" the default MTU is 1492 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + vxlan['mtu'] = '1450' return vxlan - def verify(vxlan): - if vxlan['deleted']: - if vxlan['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{vxlan["intf"]}" as it is a ' - f'member of bridge "{vxlan["is_bridge_member"]}"!')) - + if 'deleted' in vxlan: + verify_bridge_delete(vxlan) return None - if vxlan['mtu'] < 1500: + if int(vxlan['mtu']) < 1500: print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU') - if vxlan['group']: - if not vxlan['source_interface']: + if 'group' in vxlan: + if 'source_interface' not in vxlan: raise ConfigError('Multicast VXLAN requires an underlaying interface ') - if not vxlan['source_interface'] in interfaces(): - raise ConfigError('VXLAN source interface does not exist') + verify_source_interface(vxlan) - if not (vxlan['group'] or vxlan['remote'] or vxlan['source_address']): + if not any(tmp in ['group', 'remote', 'source_address'] for tmp in vxlan): raise ConfigError('Group, remote or source-address must be configured') - if not vxlan['vni']: + if 'vni' not in vxlan: raise ConfigError('Must configure VNI for VXLAN') - if vxlan['source_interface']: + if 'source_interface' in vxlan: # VXLAN adds a 50 byte overhead - we need to check the underlaying MTU # if our configured MTU is at least 50 bytes less underlay_mtu = int(Interface(vxlan['source_interface']).get_mtu()) - if underlay_mtu < (vxlan['mtu'] + 50): + if underlay_mtu < (int(vxlan['mtu']) + 50): raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \ - 'MTU is to small ({})'.format(underlay_mtu)) - - if ( vxlan['is_bridge_member'] - and ( vxlan['address'] - or vxlan['ipv6_eui64_prefix'] - or vxlan['ipv6_autoconf'] ) ): - raise ConfigError(( - f'Cannot assign address to interface "{vxlan["intf"]}" ' - f'as it is a member of bridge "{vxlan["is_bridge_member"]}"!')) + f'MTU is to small ({underlay_mtu} bytes)') + verify_address(vxlan) return None @@ -221,73 +85,26 @@ def generate(vxlan): def apply(vxlan): # Check if the VXLAN interface already exists - if vxlan['intf'] in interfaces(): - v = VXLANIf(vxlan['intf']) + if vxlan['ifname'] in interfaces(): + v = VXLANIf(vxlan['ifname']) # VXLAN is super picky and the tunnel always needs to be recreated, # thus we can simply always delete it first. v.remove() - if not vxlan['deleted']: + if 'deleted' not in vxlan: # VXLAN interface needs to be created on-block # instead of passing a ton of arguments, I just use a dict # that is managed by vyos.ifconfig conf = deepcopy(VXLANIf.get_config()) # Assign VXLAN instance configuration parameters to config dict - conf['vni'] = vxlan['vni'] - conf['group'] = vxlan['group'] - conf['src_address'] = vxlan['source_address'] - conf['src_interface'] = vxlan['source_interface'] - conf['remote'] = vxlan['remote'] - conf['port'] = vxlan['remote_port'] + for tmp in ['vni', 'group', 'source_address', 'source_interface', 'remote', 'port']: + if tmp in vxlan: + conf[tmp] = vxlan[tmp] # Finally create the new interface - v = VXLANIf(vxlan['intf'], **conf) - # update interface description used e.g. by SNMP - v.set_alias(vxlan['description']) - # Maximum Transfer Unit (MTU) - v.set_mtu(vxlan['mtu']) - - # configure ARP cache timeout in milliseconds - v.set_arp_cache_tmo(vxlan['ip_arp_cache_tmo']) - # configure ARP filter configuration - v.set_arp_filter(vxlan['ip_disable_arp_filter']) - # configure ARP accept - v.set_arp_accept(vxlan['ip_enable_arp_accept']) - # configure ARP announce - v.set_arp_announce(vxlan['ip_enable_arp_announce']) - # configure ARP ignore - v.set_arp_ignore(vxlan['ip_enable_arp_ignore']) - # Enable proxy-arp on this interface - v.set_proxy_arp(vxlan['ip_proxy_arp']) - # IPv6 accept RA - v.set_ipv6_accept_ra(vxlan['ipv6_accept_ra']) - # IPv6 address autoconfiguration - v.set_ipv6_autoconf(vxlan['ipv6_autoconf']) - # IPv6 forwarding - v.set_ipv6_forwarding(vxlan['ipv6_forwarding']) - # IPv6 Duplicate Address Detection (DAD) tries - v.set_ipv6_dad_messages(vxlan['ipv6_dup_addr_detect']) - - # Configure interface address(es) - no need to implicitly delete the - # old addresses as they have already been removed by deleting the - # interface above - for addr in vxlan['address']: - v.add_addr(addr) - - # IPv6 EUI-based addresses - for addr in vxlan['ipv6_eui64_prefix']: - v.add_ipv6_eui64_address(addr) - - # As the VXLAN interface is always disabled first when changing - # parameters we will only re-enable the interface if it is not - # administratively disabled - if not vxlan['disable']: - v.set_admin_state('up') - - # re-add ourselves to any bridge we might have fallen out of - if vxlan['is_bridge_member']: - v.add_to_bridge(vxlan['is_bridge_member']) + v = VXLANIf(vxlan['ifname'], **conf) + v.update(vxlan) return None -- cgit v1.2.3 From c87616aaef53e69ddf135725bb33d105afaa6c5b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 26 Jul 2020 13:16:19 +0200 Subject: wireguard: T2734: fwmark is a 32bit value - fix validator --- interface-definitions/interfaces-wireguard.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index 5894f159d..edf9bf696 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -30,7 +30,7 @@ value which marks the packet for QoS/shaper - + -- cgit v1.2.3 From 789775af9f5e3f9239ef4583eb4ef7538e40b37c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 26 Jul 2020 16:36:48 +0200 Subject: wireguard: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. --- interface-definitions/interfaces-wireguard.xml.in | 8 +- python/vyos/ifconfig/wireguard.py | 136 +++++----- src/conf_mode/interfaces-wireguard.py | 300 ++++------------------ 3 files changed, 139 insertions(+), 305 deletions(-) diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in index edf9bf696..981bce826 100644 --- a/interface-definitions/interfaces-wireguard.xml.in +++ b/interface-definitions/interfaces-wireguard.xml.in @@ -33,6 +33,7 @@ + 0 @@ -41,6 +42,7 @@ + default @@ -103,7 +105,11 @@ #include - how often send keep alives in seconds + Interval to send keepalive messages + + 1-65535 + Interval in seconds + diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index 62ca57ca2..fad4ef282 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -24,7 +24,7 @@ from hurry.filesize import alternative from vyos.config import Config from vyos.ifconfig import Interface from vyos.ifconfig import Operational - +from vyos.validate import is_ipv6 class WireGuardOperational(Operational): def _dump(self): @@ -169,65 +169,79 @@ class WireGuardIf(Interface): ['port', 'private_key', 'pubkey', 'psk', 'allowed_ips', 'fwmark', 'endpoint', 'keepalive'] - """ - Wireguard interface class, contains a comnfig dictionary since - wireguard VPN is being comnfigured via the wg command rather than - writing the config into a file. Otherwise if a pre-shared key is used - (symetric enryption key), it would we exposed within multiple files. - Currently it's only within the config.boot if the config was saved. - - Example: - >>> from vyos.ifconfig import WireGuardIf as wg_if - >>> wg_intfc = wg_if("wg01") - >>> print (wg_intfc.wg_config) - {'private_key': None, 'keepalive': 0, 'endpoint': None, 'port': 0, - 'allowed_ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'} - >>> wg_intfc.wg_config['keepalive'] = 100 - >>> print (wg_intfc.wg_config) - {'private_key': None, 'keepalive': 100, 'endpoint': None, 'port': 0, - 'allowed_ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'} - """ - - def update(self): - if not self.config['private_key']: - raise ValueError("private key required") - else: - # fmask permission check? - pass - - cmd = 'wg set {ifname}'.format(**self.config) - cmd += ' listen-port {port}'.format(**self.config) - cmd += ' fwmark "{fwmark}" '.format(**self.config) - cmd += ' private-key {private_key}'.format(**self.config) - cmd += ' peer {pubkey}'.format(**self.config) - cmd += ' persistent-keepalive {keepalive}'.format(**self.config) - # allowed-ips must be properly quoted else the interface can't be properly - # created as the wg utility will tread multiple IP addresses as command - # parameters - cmd += ' allowed-ips "{}"'.format(','.join(self.config['allowed-ips'])) - - if self.config['endpoint']: - cmd += ' endpoint "{endpoint}"'.format(**self.config) - - psk_file = '' - if self.config['psk']: - psk_file = '/tmp/{ifname}.psk'.format(**self.config) - with open(psk_file, 'w') as f: - f.write(self.config['psk']) + 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. """ + + # remove no longer associated peers first + if 'peer_remove' in config: + for tmp in config['peer_remove']: + peer = config['peer_remove'][tmp] + peer['ifname'] = config['ifname'] + + cmd = 'wg set {ifname} peer {pubkey} remove' + self._cmd(cmd.format(**peer)) + + # Wireguard base command is identical for every peer + base_cmd = 'wg set {ifname} private-key {private_key}' + if 'port' in config: + base_cmd += ' listen-port {port}' + if 'fwmark' in config: + base_cmd += ' fwmark {fwmark}' + + base_cmd = base_cmd.format(**config) + + for tmp in config['peer']: + peer = config['peer'][tmp] + + # start of with a fresh 'wg' command + cmd = base_cmd + ' peer {pubkey}' + + # 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: + psk_file = '/tmp/tmp.wireguard.psk' + with open(psk_file, 'w') as f: + f.write(peer['preshared_key']) cmd += f' preshared-key {psk_file}' - self._cmd(cmd) - - # PSK key file is not required to be stored persistently as its backed by CLI - if os.path.exists(psk_file): - os.remove(psk_file) - - def remove_peer(self, peerkey): - """ - Remove a peer of an interface, peers are identified by their public key. - Giving it a readable name is a vyos feature, to remove a peer the pubkey - and the interface is needed, to remove the entry. - """ - cmd = "wg set {0} peer {1} remove".format( - self.config['ifname'], str(peerkey)) - return self._cmd(cmd) + # Persistent keepalive is optional + if 'persistent_keepalive'in peer: + cmd += ' persistent-keepalive {persistent_keepalive}' + + # Multiple allowed-ip ranges can be defined - ensure we are always + # dealing with a list + if isinstance(peer['allowed_ips'], str): + peer['allowed_ips'] = [peer['allowed_ips']] + cmd += ' allowed-ips ' + ','.join(peer['allowed_ips']) + + # Endpoint configuration is optional + if {'address', 'port'} <= set(peer): + if is_ipv6(config['address']): + cmd += ' endpoint [{address}]:{port}' + else: + cmd += ' endpoint {address}:{port}' + + self._cmd(cmd.format(**peer)) + + # 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) + + # 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) + diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 982aefa5f..6325a8b05 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -15,44 +15,29 @@ # along with this program. If not, see . import os -import re from sys import exit from copy import deepcopy -from netifaces import interfaces from vyos.config import Config -from vyos.configdict import list_diff +from vyos.configdict import dict_merge +from vyos.configdict import get_interface_dict +from vyos.configdict import node_changed +from vyos.configdict import leaf_node_changed +from vyos.configverify import verify_vrf +from vyos.configverify import verify_address +from vyos.configverify import verify_bridge_delete from vyos.ifconfig import WireGuardIf -from vyos.util import chown, chmod_750, call +from vyos.util import chown, chmod_750 from vyos.util import check_kmod -from vyos.validate import is_member, is_ipv6 from vyos import ConfigError - from vyos import airbag airbag.enable() -kdir = r'/config/auth/wireguard' k_mod = 'wireguard' -default_config_data = { - 'intfc': '', - 'address': [], - 'address_remove': [], - 'description': '', - 'listen_port': '', - 'deleted': False, - 'disable': False, - 'fwmark': 0, - 'is_bridge_member': False, - 'mtu': 1420, - 'peer': [], - 'peer_remove': [], # stores public keys of peers to remove - 'pk': f'{kdir}/default/private.key', - 'vrf': '' -} - 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): @@ -65,246 +50,75 @@ def _migrate_default_keys(): def get_config(): + """ + Retrive CLI config as dictionary. Dictionary can never be empty, as at least the + interface name will be added or a deleted flag + """ conf = Config() base = ['interfaces', 'wireguard'] + wireguard = get_interface_dict(conf, base) - # determine tagNode instance - if 'VYOS_TAGNODE_VALUE' not in os.environ: - raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified') - - wg = deepcopy(default_config_data) - wg['intf'] = os.environ['VYOS_TAGNODE_VALUE'] - - # check if interface is member if a bridge - wg['is_bridge_member'] = is_member(conf, wg['intf'], 'bridge') - - # Check if interface has been removed - if not conf.exists(base + [wg['intf']]): - wg['deleted'] = True - return wg - - conf.set_level(base + [wg['intf']]) - - # retrieve configured interface addresses - if conf.exists(['address']): - wg['address'] = conf.return_values(['address']) - - # get interface addresses (currently effective) - to determine which - # address is no longer valid and needs to be removed - eff_addr = conf.return_effective_values(['address']) - wg['address_remove'] = list_diff(eff_addr, wg['address']) - - # retrieve interface description - if conf.exists(['description']): - wg['description'] = conf.return_value(['description']) - - # disable interface - if conf.exists(['disable']): - wg['disable'] = True - - # local port to listen on - if conf.exists(['port']): - wg['listen_port'] = conf.return_value(['port']) - - # fwmark value - if conf.exists(['fwmark']): - wg['fwmark'] = int(conf.return_value(['fwmark'])) - - # Maximum Transmission Unit (MTU) - if conf.exists('mtu'): - wg['mtu'] = int(conf.return_value(['mtu'])) - - # retrieve VRF instance - if conf.exists('vrf'): - wg['vrf'] = conf.return_value('vrf') - - # private key - if conf.exists(['private-key']): - wg['pk'] = "{0}/{1}/private.key".format( - kdir, conf.return_value(['private-key'])) - - # peer removal, wg identifies peers by its pubkey - peer_eff = conf.list_effective_nodes(['peer']) - peer_rem = list_diff(peer_eff, conf.list_nodes(['peer'])) - for peer in peer_rem: - wg['peer_remove'].append( - conf.return_effective_value(['peer', peer, 'pubkey'])) - - # peer settings - if conf.exists(['peer']): - for p in conf.list_nodes(['peer']): - # set new config level for this peer - conf.set_level(base + [wg['intf'], 'peer', p]) - peer = { - 'allowed-ips': [], - 'address': '', - 'name': p, - 'persistent_keepalive': '', - 'port': '', - 'psk': '', - 'pubkey': '' - } - - # peer allowed-ips - if conf.exists(['allowed-ips']): - peer['allowed-ips'] = conf.return_values(['allowed-ips']) - - # peer address - if conf.exists(['address']): - peer['address'] = conf.return_value(['address']) - - # peer port - if conf.exists(['port']): - peer['port'] = conf.return_value(['port']) + # Wireguard is "special" the default MTU is 1420 - update accordingly + # as the config_level is already st in get_interface_dict() - we can use [] + tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) + if 'mtu' not in tmp: + wireguard['mtu'] = '1420' - # persistent-keepalive - if conf.exists(['persistent-keepalive']): - peer['persistent_keepalive'] = conf.return_value(['persistent-keepalive']) + # Mangle private key - it has a default so its always valid + wireguard['private_key'] = '/config/auth/wireguard/{private_key}/private.key'.format(**wireguard) - # preshared-key - if conf.exists(['preshared-key']): - peer['psk'] = conf.return_value(['preshared-key']) + # Determine which Wireguard peer has been removed. + # Peers can only be removed with their public key! + tmp = node_changed(conf, ['peer']) + if tmp: + dict = {} + for peer in tmp: + peer_config = leaf_node_changed(conf, ['peer', peer, 'pubkey']) + dict = dict_merge({'peer_remove' : {peer : {'pubkey' : peer_config}}}, dict) + wireguard.update(dict) - # peer pubkeys - if conf.exists(['pubkey']): - key_eff = conf.return_effective_value(['pubkey']) - key_cfg = conf.return_value(['pubkey']) - peer['pubkey'] = key_cfg + return wireguard - # on a pubkey change we need to remove the pubkey first - # peers are identified by pubkey, so key update means - # peer removal and re-add - if key_eff != key_cfg and key_eff != None: - wg['peer_remove'].append(key_cfg) - - # if a peer is disabled, we have to exec a remove for it's pubkey - if conf.exists(['disable']): - wg['peer_remove'].append(peer['pubkey']) - else: - wg['peer'].append(peer) - - return wg - - -def verify(wg): - if wg['deleted']: - if wg['is_bridge_member']: - raise ConfigError(( - f'Cannot delete interface "{wg["intf"]}" as it is a member ' - f'of bridge "{wg["is_bridge_member"]}"!')) +def verify(wireguard): + if 'deleted' in wireguard: + verify_bridge_delete(wireguard) return None - if wg['is_bridge_member'] and wg['address']: - raise ConfigError(( - f'Cannot assign address to interface "{wg["intf"]}" ' - f'as it is a member of bridge "{wg["is_bridge_member"]}"!')) - - if wg['vrf']: - if wg['vrf'] not in interfaces(): - raise ConfigError(f'VRF "{wg["vrf"]}" does not exist') + verify_address(wireguard) + verify_vrf(wireguard) - if wg['is_bridge_member']: - raise ConfigError(( - f'Interface "{wg["intf"]}" cannot be member of VRF ' - f'"{wg["vrf"]}" and bridge {wg["is_bridge_member"]} ' - f'at the same time!')) + if not os.path.exists(wireguard['private_key']): + raise ConfigError('Wireguard private-key not found! Execute: ' \ + '"run generate wireguard [default-keypair|named-keypairs]"') - if not os.path.exists(wg['pk']): - raise ConfigError('No keys found, generate them by executing:\n' \ - '"run generate wireguard [keypair|named-keypairs]"') + if 'address' not in wireguard: + raise ConfigError('IP address required!') - if not wg['address']: - raise ConfigError(f'IP address required for interface "{wg["intf"]}"!') - - if not wg['peer']: - raise ConfigError(f'Peer required for interface "{wg["intf"]}"!') + if 'peer' not in wireguard: + raise ConfigError('At least one Wireguard peer is required!') # run checks on individual configured WireGuard peer - for peer in wg['peer']: - if not peer['allowed-ips']: - raise ConfigError(f'Peer allowed-ips required for peer "{peer["name"]}"!') - - if not peer['pubkey']: - raise ConfigError(f'Peer public-key required for peer "{peer["name"]}"!') - - if peer['address'] and not peer['port']: - raise ConfigError(f'Peer "{peer["name"]}" port must be defined if address is defined!') + for tmp in wireguard['peer']: + peer = wireguard['peer'][tmp] - if not peer['address'] and peer['port']: - raise ConfigError(f'Peer "{peer["name"]}" address must be defined if port is defined!') + if 'allowed_ips' not in peer: + raise ConfigError(f'Wireguard allowed-ips required for peer "{tmp}"!') + if 'pubkey' not in peer: + raise ConfigError(f'Wireguard public-key required for peer "{tmp}"!') -def apply(wg): - # init wg class - w = WireGuardIf(wg['intf']) + 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!') - # single interface removal - if wg['deleted']: - w.remove() +def apply(wireguard): + if 'deleted' in wireguard: + WireGuardIf(wireguard['ifname']).remove() return None - # Configure interface address(es) - # - not longer required addresses get removed first - # - newly addresses will be added second - for addr in wg['address_remove']: - w.del_addr(addr) - for addr in wg['address']: - w.add_addr(addr) - - # Maximum Transmission Unit (MTU) - w.set_mtu(wg['mtu']) - - # update interface description used e.g. within SNMP - w.set_alias(wg['description']) - - # assign/remove VRF (ONLY when not a member of a bridge, - # otherwise 'nomaster' removes it from it) - if not wg['is_bridge_member']: - w.set_vrf(wg['vrf']) - - # remove peers - for pub_key in wg['peer_remove']: - w.remove_peer(pub_key) - - # peer pubkey - # setting up the wg interface - w.config['private_key'] = c['pk'] - - for peer in wg['peer']: - # peer pubkey - w.config['pubkey'] = peer['pubkey'] - # peer allowed-ips - w.config['allowed-ips'] = peer['allowed-ips'] - # local listen port - if wg['listen_port']: - w.config['port'] = wg['listen_port'] - # fwmark - if c['fwmark']: - w.config['fwmark'] = wg['fwmark'] - - # endpoint - if peer['address'] and peer['port']: - if is_ipv6(peer['address']): - w.config['endpoint'] = '[{}]:{}'.format(peer['address'], peer['port']) - else: - w.config['endpoint'] = '{}:{}'.format(peer['address'], peer['port']) - - # persistent-keepalive - if peer['persistent_keepalive']: - w.config['keepalive'] = peer['persistent_keepalive'] - - if peer['psk']: - w.config['psk'] = peer['psk'] - - w.update() - - # Enable/Disable interface - if wg['disable']: - w.set_admin_state('down') - else: - w.set_admin_state('up') - + w = WireGuardIf(wireguard['ifname']) + w.update(wireguard) return None if __name__ == '__main__': -- cgit v1.2.3 From ed11b28b2f3e20a07cdfed869fcb2d1992c5f7f6 Mon Sep 17 00:00:00 2001 From: sever-sever Date: Mon, 27 Jul 2020 13:38:26 +0000 Subject: op-mode: T1117: Add show ipv6 bgp route-map --- Makefile | 1 + op-mode-definitions/show-ipv6-bgp.xml | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 op-mode-definitions/show-ipv6-bgp.xml diff --git a/Makefile b/Makefile index fecc093de..6c715d138 100644 --- a/Makefile +++ b/Makefile @@ -94,6 +94,7 @@ op_mode_definitions: rm -f $(OP_TMPL_DIR)/show/node.def rm -f $(OP_TMPL_DIR)/show/interfaces/node.def rm -f $(OP_TMPL_DIR)/show/ipv6/node.def + rm -f $(OP_TMPL_DIR)/show/ipv6/bgp/node.def rm -f $(OP_TMPL_DIR)/show/ipv6/route/node.def rm -f $(OP_TMPL_DIR)/restart/node.def rm -f $(OP_TMPL_DIR)/monitor/node.def diff --git a/op-mode-definitions/show-ipv6-bgp.xml b/op-mode-definitions/show-ipv6-bgp.xml new file mode 100644 index 000000000..67a8c8658 --- /dev/null +++ b/op-mode-definitions/show-ipv6-bgp.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + Show BGP routes matching the specified route map + + policy route-map + + + /usr/bin/vtysh -c "show bgp ipv6 route-map $5" + + + + + + + + -- cgit v1.2.3 From 9f7ef46636c8b995a3b758484324db4bc9a4df37 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 29 Jul 2020 17:31:35 +0200 Subject: mdns-repeater: T2742: migrate to get_config_dict() --- data/templates/mdns-repeater/mdns-repeater.tmpl | 2 +- interface-definitions/mdns-repeater.xml.in | 38 -------- interface-definitions/service_mdns-repeater.xml.in | 37 +++++++ src/conf_mode/mdns_repeater.py | 107 --------------------- src/conf_mode/service_mdns-repeater.py | 89 +++++++++++++++++ 5 files changed, 127 insertions(+), 146 deletions(-) delete mode 100644 interface-definitions/mdns-repeater.xml.in create mode 100644 interface-definitions/service_mdns-repeater.xml.in delete mode 100755 src/conf_mode/mdns_repeater.py create mode 100755 src/conf_mode/service_mdns-repeater.py diff --git a/data/templates/mdns-repeater/mdns-repeater.tmpl b/data/templates/mdns-repeater/mdns-repeater.tmpl index 3fc4db67e..80f4ab047 100644 --- a/data/templates/mdns-repeater/mdns-repeater.tmpl +++ b/data/templates/mdns-repeater/mdns-repeater.tmpl @@ -1,2 +1,2 @@ ### Autogenerated by mdns_repeater.py ### -DAEMON_ARGS="{{ interfaces | join(' ') }}" +DAEMON_ARGS="{{ interface | join(' ') }}" diff --git a/interface-definitions/mdns-repeater.xml.in b/interface-definitions/mdns-repeater.xml.in deleted file mode 100644 index a59321294..000000000 --- a/interface-definitions/mdns-repeater.xml.in +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - Multicast DNS (mDNS) parameters - - - - - mDNS repeater configuration - 990 - - - - - Disable mDNS repeater service - - - - - - Interface to repeat mDNS advertisements [REQUIRED] - - - - - - - - - - - - - diff --git a/interface-definitions/service_mdns-repeater.xml.in b/interface-definitions/service_mdns-repeater.xml.in new file mode 100644 index 000000000..e21b1b27c --- /dev/null +++ b/interface-definitions/service_mdns-repeater.xml.in @@ -0,0 +1,37 @@ + + + + + + + Multicast DNS (mDNS) parameters + + + + + mDNS repeater configuration + 990 + + + + + Disable mDNS repeater service + + + + + + Interface to repeat mDNS advertisements [REQUIRED] + + + + + + + + + + + + + diff --git a/src/conf_mode/mdns_repeater.py b/src/conf_mode/mdns_repeater.py deleted file mode 100755 index b43f9bdd8..000000000 --- a/src/conf_mode/mdns_repeater.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2017-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 . - -import os - -from sys import exit -from copy import deepcopy -from netifaces import ifaddresses, AF_INET - -from vyos.config import Config -from vyos import ConfigError -from vyos.util import call -from vyos.template import render - -from vyos import airbag -airbag.enable() - -config_file = r'/etc/default/mdns-repeater' - -default_config_data = { - 'disabled': False, - 'interfaces': [] -} - -def get_config(): - mdns = deepcopy(default_config_data) - conf = Config() - base = ['service', 'mdns', 'repeater'] - if not conf.exists(base): - return None - else: - conf.set_level(base) - - # Service can be disabled by user - if conf.exists(['disable']): - mdns['disabled'] = True - return mdns - - # Interface to repeat mDNS advertisements - if conf.exists(['interface']): - mdns['interfaces'] = conf.return_values(['interface']) - - return mdns - -def verify(mdns): - if mdns is None: - return None - - if mdns['disabled']: - return None - - # We need at least two interfaces to repeat mDNS advertisments - if len(mdns['interfaces']) < 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['interfaces']: - if AF_INET in ifaddresses(interface).keys(): - if len(ifaddresses(interface)[AF_INET]) < 1: - raise ConfigError('mDNS repeater requires an IPv6 address configured on interface %s!'.format(interface)) - - return None - -def generate(mdns): - if mdns is None: - return None - - if mdns['disabled']: - print('Warning: mDNS repeater will be deactivated because it is disabled') - return None - - render(config_file, 'mdns-repeater/mdns-repeater.tmpl', mdns) - return None - -def apply(mdns): - if (mdns is None) or mdns['disabled']: - call('systemctl stop mdns-repeater.service') - if os.path.exists(config_file): - os.unlink(config_file) - else: - call('systemctl restart mdns-repeater.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_mdns-repeater.py b/src/conf_mode/service_mdns-repeater.py new file mode 100755 index 000000000..1a6b2c328 --- /dev/null +++ b/src/conf_mode/service_mdns-repeater.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017-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 . + +import os + +from sys import exit +from netifaces import ifaddresses, interfaces, AF_INET + +from vyos.config import Config +from vyos.template import render +from vyos.util import call +from vyos import ConfigError +from vyos import airbag +airbag.enable() + +config_file = r'/etc/default/mdns-repeater' + +def get_config(): + conf = Config() + base = ['service', 'mdns', 'repeater'] + mdns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + return mdns + +def verify(mdns): + if not mdns: + return None + + if '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']: + if interface not in interfaces(): + raise ConfigError(f'Interface "{interface}" does not exist!') + + if AF_INET not in ifaddresses(interface): + raise ConfigError('mDNS repeater requires an IPv4 address to be ' + f'configured on interface "{interface}"') + + return None + +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 + + render(config_file, 'mdns-repeater/mdns-repeater.tmpl', mdns) + return None + +def apply(mdns): + if not mdns or 'disable' in mdns: + call('systemctl stop mdns-repeater.service') + if os.path.exists(config_file): + os.unlink(config_file) + else: + call('systemctl restart mdns-repeater.service') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) -- cgit v1.2.3 From 4e1062ea02ccd99940da18a887e2092b0a9e5650 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 29 Jul 2020 17:41:10 +0200 Subject: mdns-repeater: add basic test --- scripts/cli/test_service_mdns-repeater.py | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100755 scripts/cli/test_service_mdns-repeater.py diff --git a/scripts/cli/test_service_mdns-repeater.py b/scripts/cli/test_service_mdns-repeater.py new file mode 100755 index 000000000..18900b6d2 --- /dev/null +++ b/scripts/cli/test_service_mdns-repeater.py @@ -0,0 +1,51 @@ +#!/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 . + +import os +import unittest + +from psutil import process_iter +from vyos.configsession import ConfigSession + +base_path = ['service', 'mdns', 'repeater'] +intf_base = ['interfaces', 'dummy'] + +class TestServiceMDNSrepeater(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + + def tearDown(self): + self.session.delete(base_path) + self.session.delete(intf_base + ['dum10']) + self.session.delete(intf_base + ['dum20']) + self.session.commit() + del self.session + + def test_service(self): + # Service required a configured IP address on the interface + + self.session.set(intf_base + ['dum10', 'address', '192.0.2.1/30']) + self.session.set(intf_base + ['dum20', 'address', '192.0.2.5/30']) + + self.session.set(base_path + ['interface', 'dum10']) + self.session.set(base_path + ['interface', 'dum20']) + self.session.commit() + + # Check for running process + self.assertTrue("mdns-repeater" in (p.name() for p in process_iter())) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From fe1d2377fe1169d7e13012295036935447ccfed1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 29 Jul 2020 21:28:21 +0200 Subject: wireguard: T2743: move key migration from config script to migration script Migration files on the storage should be done one time by a migration script instead of every time the configuration changes. Moving this to an older migration script is fine as this is around for a long time and all rolling releases are already up2date. It only affects updates from VyOS 1.2 series. --- src/conf_mode/interfaces-wireguard.py | 20 +------------------- src/migration-scripts/interfaces/7-to-8 | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 6325a8b05..8b64cde4d 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -28,27 +28,11 @@ from vyos.configverify import verify_vrf from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.ifconfig import WireGuardIf -from vyos.util import chown, chmod_750 from vyos.util import check_kmod from vyos import ConfigError from vyos import airbag airbag.enable() -k_mod = 'wireguard' - -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 get_config(): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -79,7 +63,6 @@ def get_config(): return wireguard - def verify(wireguard): if 'deleted' in wireguard: verify_bridge_delete(wireguard) @@ -123,8 +106,7 @@ def apply(wireguard): if __name__ == '__main__': try: - check_kmod(k_mod) - _migrate_default_keys() + check_kmod('wireguard') c = get_config() verify(c) apply(c) diff --git a/src/migration-scripts/interfaces/7-to-8 b/src/migration-scripts/interfaces/7-to-8 index 8830ffdc7..a4051301f 100755 --- a/src/migration-scripts/interfaces/7-to-8 +++ b/src/migration-scripts/interfaces/7-to-8 @@ -17,8 +17,23 @@ # Split WireGuard endpoint into address / port nodes to make use of common # validators +import os + from sys import exit, argv from vyos.configtree import ConfigTree +from vyos.util import chown, 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') if __name__ == '__main__': if (len(argv) < 1): @@ -32,6 +47,8 @@ if __name__ == '__main__': config = ConfigTree(config_file) base = ['interfaces', 'wireguard'] + migrate_default_keys() + if not config.exists(base): # Nothing to do exit(0) -- cgit v1.2.3 From e06948a5445f5ef8d16d2acdaac032c6a7c257be Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 29 Jul 2020 21:48:49 +0200 Subject: op-mode: T2560: rename "show system usb serial" to "show hardware usb serial" This is more inline with the overall op-mode. --- op-mode-definitions/show-hardware.xml | 10 ++++++++-- op-mode-definitions/show-system.xml | 14 -------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/op-mode-definitions/show-hardware.xml b/op-mode-definitions/show-hardware.xml index a49036397..c3ff3a60f 100644 --- a/op-mode-definitions/show-hardware.xml +++ b/op-mode-definitions/show-hardware.xml @@ -71,14 +71,20 @@ Show peripherals connected to the USB bus - lsusb + /usr/bin/lsusb -t Show detailed USB bus information - lsusb -v + /usr/bin/lsusb -v + + + Show information about connected USB serial ports + + ${vyos_op_scripts_dir}/show_usb_serial.py + diff --git a/op-mode-definitions/show-system.xml b/op-mode-definitions/show-system.xml index 74b34ae92..1b98b559b 100644 --- a/op-mode-definitions/show-system.xml +++ b/op-mode-definitions/show-system.xml @@ -176,20 +176,6 @@ uptime - - - Show information about Universal Serial Bus (USB) - - /usr/bin/lsusb -t - - - - Show information about connected USB serial ports - - ${vyos_op_scripts_dir}/show_usb_serial.py - - - -- cgit v1.2.3 From f75f9cf6aae10c061e7b757b4e8a658b4457def7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 30 Jul 2020 22:54:48 +0200 Subject: router-advert: T2745: migrate to get_config_dict() --- data/templates/router-advert/radvd.conf.tmpl | 72 +++++---- interface-definitions/service_router-advert.xml.in | 11 +- src/conf_mode/service_router-advert.py | 163 ++++++--------------- 3 files changed, 98 insertions(+), 148 deletions(-) diff --git a/data/templates/router-advert/radvd.conf.tmpl b/data/templates/router-advert/radvd.conf.tmpl index 073623eac..cebfc54b5 100644 --- a/data/templates/router-advert/radvd.conf.tmpl +++ b/data/templates/router-advert/radvd.conf.tmpl @@ -1,37 +1,47 @@ ### Autogenerated by service_router-advert.py ### -{% for i in interfaces -%} -interface {{ i.name }} { +{% if interface is defined and interface is not none %} +{% for iface in interface %} +interface {{ iface }} { IgnoreIfMissing on; - AdvDefaultPreference {{ i.default_preference }}; - AdvManagedFlag {{ i.managed_flag }}; - MaxRtrAdvInterval {{ i.interval_max }}; -{% if i.interval_min %} - MinRtrAdvInterval {{ i.interval_min }}; -{% endif %} - AdvReachableTime {{ i.reachable_time }}; - AdvIntervalOpt {{ i.send_advert }}; - AdvSendAdvert {{ i.send_advert }}; -{% if i.default_lifetime %} - AdvDefaultLifetime {{ i.default_lifetime }}; -{% endif %} -{% if i.link_mtu %} - AdvLinkMTU {{ i.link_mtu }}; -{% endif %} - AdvOtherConfigFlag {{ i.other_config_flag }}; - AdvRetransTimer {{ i.retrans_timer }}; - AdvCurHopLimit {{ i.hop_limit }}; -{% for p in i.prefixes %} - prefix {{ p.prefix }} { - AdvAutonomous {{ p.autonomous_flag }}; - AdvValidLifetime {{ p.valid_lifetime }}; - AdvOnLink {{ p.on_link }}; - AdvPreferredLifetime {{ p.preferred_lifetime }}; +{% if interface[iface].default_preference is defined and interface[iface].default_preference is not none %} + AdvDefaultPreference {{ interface[iface].default_preference }}; +{% endif %} +{% if interface[iface].managed_flag is defined and interface[iface].managed_flag is not none %} + AdvManagedFlag {{ 'on' if interface[iface].managed_flag is defined else 'off' }}; +{% endif %} +{% if interface[iface].interval.max is defined and interface[iface].interval.max is not none %} + MaxRtrAdvInterval {{ interface[iface].interval.max }}; +{% endif %} +{% if interface[iface].interval.min is defined and interface[iface].interval.min is not none %} + MinRtrAdvInterval {{ interface[iface].interval.min }}; +{% endif %} +{% if interface[iface].reachable_time is defined and interface[iface].reachable_time is not none %} + AdvReachableTime {{ interface[iface].reachable_time }}; +{% endif %} + AdvIntervalOpt {{ 'off' if interface[iface].no_send_advert is defined else 'on' }}; + AdvSendAdvert {{ 'off' if interface[iface].no_send_advert is defined else 'on' }}; +{% if interface[iface].default_lifetime is defined %} + AdvDefaultLifetime {{ interface[iface].default_lifetime }}; +{% endif %} +{% if interface[iface].link_mtu is defined %} + AdvLinkMTU {{ interface[iface].link_mtu }}; +{% endif %} + AdvOtherConfigFlag {{ 'on' if interface[iface].other_config_flag is defined else 'off' }}; + AdvRetransTimer {{ interface[iface].retrans_timer }}; + AdvCurHopLimit {{ interface[iface].hop_limit }}; +{% for prefix in interface[iface].prefix %} + prefix {{ prefix }} { + AdvAutonomous {{ 'off' if interface[iface].prefix[prefix].no_autonomous_flag is defined else 'on' }}; + AdvValidLifetime {{ interface[iface].prefix[prefix].valid_lifetime }}; + AdvOnLink {{ 'off' if interface[iface].prefix[prefix].no_on_link_flag is defined else 'on' }}; + AdvPreferredLifetime {{ interface[iface].prefix[prefix].preferred_lifetime }}; }; -{% endfor %} -{% if i.name_server %} - RDNSS {{ i.name_server | join(" ") }} { +{% endfor %} +{% if interface[iface].name_server is defined %} + RDNSS {{ interface[iface].name_server | join(" ") }} { }; -{% endif %} +{% endif %} }; -{% endfor -%} +{% endfor -%} +{% endif %} diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service_router-advert.xml.in index 6a4706ab7..5a472fc9a 100644 --- a/interface-definitions/service_router-advert.xml.in +++ b/interface-definitions/service_router-advert.xml.in @@ -32,6 +32,7 @@ Hop count must be between 0 and 255 + 64 @@ -69,10 +70,11 @@ Default router has high preference - (low|medium|high) + ^(low|medium|high)$ Default preference must be low, medium or high + medium @@ -116,6 +118,7 @@ Maximum interval must be between 4 and 1800 seconds + 600 @@ -191,9 +194,10 @@ - (infinity) + ^(infinity)$ + 14400 @@ -214,6 +218,7 @@ (infinity) + 2592000 @@ -233,6 +238,7 @@ Reachable time must be 0 or between 1 and 3600000 milliseconds + 0 @@ -250,6 +256,7 @@ Retransmit interval must be 0 or between 1 and 4294967295 milliseconds + 0 diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index ef6148ebd..1b333e1a5 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -16,145 +16,77 @@ import os -from stat import S_IRUSR, S_IWUSR, S_IRGRP from sys import exit from vyos.config import Config -from vyos import ConfigError -from vyos.util import call +from vyos.configdict import dict_merge from vyos.template import render - +from vyos.util import call +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() config_file = r'/run/radvd/radvd.conf' -default_config_data = { - 'interfaces': [] -} - def get_config(): - rtradv = default_config_data conf = Config() - base_level = ['service', 'router-advert'] - - if not conf.exists(base_level): - return rtradv - - for interface in conf.list_nodes(base_level + ['interface']): - intf = { - 'name': interface, - 'hop_limit' : '64', - 'default_lifetime': '', - 'default_preference': 'medium', - 'dnssl': [], - 'link_mtu': '', - 'managed_flag': 'off', - 'interval_max': '600', - 'interval_min': '', - 'name_server': [], - 'other_config_flag': 'off', - 'prefixes' : [], - 'reachable_time': '0', - 'retrans_timer': '0', - 'send_advert': 'on' - } - - # set config level first to reduce boilerplate code - conf.set_level(base_level + ['interface', interface]) - - if conf.exists(['hop-limit']): - intf['hop_limit'] = conf.return_value(['hop-limit']) - - if conf.exists(['default-lifetime']): - intf['default_lifetime'] = conf.return_value(['default-lifetime']) - - if conf.exists(['default-preference']): - intf['default_preference'] = conf.return_value(['default-preference']) - - if conf.exists(['dnssl']): - intf['dnssl'] = conf.return_values(['dnssl']) - - if conf.exists(['link-mtu']): - intf['link_mtu'] = conf.return_value(['link-mtu']) - - if conf.exists(['managed-flag']): - intf['managed_flag'] = 'on' - - if conf.exists(['interval', 'max']): - intf['interval_max'] = conf.return_value(['interval', 'max']) - - if conf.exists(['interval', 'min']): - intf['interval_min'] = conf.return_value(['interval', 'min']) - - if conf.exists(['name-server']): - intf['name_server'] = conf.return_values(['name-server']) - - if conf.exists(['other-config-flag']): - intf['other_config_flag'] = 'on' - - if conf.exists(['reachable-time']): - intf['reachable_time'] = conf.return_value(['reachable-time']) - - if conf.exists(['retrans-timer']): - intf['retrans_timer'] = conf.return_value(['retrans-timer']) - - if conf.exists(['no-send-advert']): - intf['send_advert'] = 'off' - - for prefix in conf.list_nodes(['prefix']): - tmp = { - 'prefix' : prefix, - 'autonomous_flag' : 'on', - 'on_link' : 'on', - 'preferred_lifetime': 14400, - 'valid_lifetime' : 2592000 - - } - - # set config level first to reduce boilerplate code - conf.set_level(base_level + ['interface', interface, 'prefix', prefix]) - - if conf.exists(['no-autonomous-flag']): - tmp['autonomous_flag'] = 'off' - - if conf.exists(['no-on-link-flag']): - tmp['on_link'] = 'off' - - if conf.exists(['preferred-lifetime']): - tmp['preferred_lifetime'] = int(conf.return_value(['preferred-lifetime'])) - - if conf.exists(['valid-lifetime']): - tmp['valid_lifetime'] = int(conf.return_value(['valid-lifetime'])) - - intf['prefixes'].append(tmp) - - rtradv['interfaces'].append(intf) + base = ['service', 'router-advert'] + rtradv = 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_interface_values = defaults(base + ['interface']) + # we deal with prefix defaults later on + if 'prefix' in default_interface_values: + del default_interface_values['prefix'] + + default_prefix_values = defaults(base + ['interface', 'prefix']) + + if 'interface' in rtradv: + for interface in rtradv['interface']: + rtradv['interface'][interface] = dict_merge( + default_interface_values, rtradv['interface'][interface]) + + if 'prefix' in rtradv['interface'][interface]: + for prefix in rtradv['interface'][interface]['prefix']: + rtradv['interface'][interface]['prefix'][prefix] = dict_merge( + default_prefix_values, rtradv['interface'][interface]['prefix'][prefix]) + + if 'name_server' in rtradv['interface'][interface]: + # always use a list when dealing with nameservers - eases the template generation + if isinstance(rtradv['interface'][interface]['name_server'], str): + rtradv['interface'][interface]['name_server'] = [ + rtradv['interface'][interface]['name_server']] return rtradv def verify(rtradv): - for interface in rtradv['interfaces']: - for prefix in interface['prefixes']: - if not (prefix['valid_lifetime'] > prefix['preferred_lifetime']): - raise ConfigError('Prefix valid-lifetime must be greater then preferred-lifetime') + if not rtradv: + return None + + if 'interface' not in rtradv: + return None + + for interface in rtradv['interface']: + interface = rtradv['interface'][interface] + if 'prefix' in interface: + for prefix in interface['prefix']: + prefix = interface['prefix'][prefix] + if not (int(prefix['valid_lifetime']) > int(prefix['preferred_lifetime'])): + raise ConfigError('Prefix valid-lifetime must be greater then preferred-lifetime') return None def generate(rtradv): - if not rtradv['interfaces']: + if not rtradv: return None - render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, trim_blocks=True) - - # adjust file permissions of new configuration file - if os.path.exists(config_file): - os.chmod(config_file, S_IRUSR | S_IWUSR | S_IRGRP) - + render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, trim_blocks=True, permission=0o644) return None def apply(rtradv): - if not rtradv['interfaces']: + if not rtradv: # bail out early - looks like removal from running config call('systemctl stop radvd.service') if os.path.exists(config_file): @@ -163,6 +95,7 @@ def apply(rtradv): return None call('systemctl restart radvd.service') + return None if __name__ == '__main__': -- cgit v1.2.3 From fa3e3915254df7d4bcb3c8964113954669413d12 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 30 Jul 2020 22:56:00 +0200 Subject: router-advert: add initial test --- scripts/cli/test_service_router-advert.py | 98 +++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100755 scripts/cli/test_service_router-advert.py diff --git a/scripts/cli/test_service_router-advert.py b/scripts/cli/test_service_router-advert.py new file mode 100755 index 000000000..cc251e355 --- /dev/null +++ b/scripts/cli/test_service_router-advert.py @@ -0,0 +1,98 @@ +#!/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 . + +import re +import os +import unittest + +from psutil import process_iter +from vyos.configsession import ConfigSession +from vyos.util import read_file + +RADVD_CONF = '/run/radvd/radvd.conf' + +interface = 'eth1' +base_path = ['service', 'router-advert', 'interface', interface] +address_base = ['interfaces', 'ethernet', interface, 'address'] + +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(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + self.session.set(address_base + ['2001:db8::1/64']) + + def tearDown(self): + self.session.delete(address_base) + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_single(self): + self.session.set(base_path + ['prefix', '::/64', 'no-on-link-flag']) + self.session.set(base_path + ['prefix', '::/64', 'no-autonomous-flag']) + self.session.set(base_path + ['dnssl', '2001:db8::1234']) + self.session.set(base_path + ['other-config-flag']) + + # commit changes + self.session.commit() + + # verify values + tmp = get_config_value('interface') + self.assertEqual(tmp, interface) + + tmp = get_config_value('prefix') + self.assertEqual(tmp, '::/64') + + 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, '2592000') + + # this is a default value + tmp = get_config_value('AdvPreferredLifetime') + self.assertEqual(tmp, '14400') + + tmp = get_config_value('AdvOnLink') + self.assertEqual(tmp, 'off') + + + + # Check for running process + self.assertTrue('radvd' in (p.name() for p in process_iter())) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From 29dd5079ad38e82363032b585304d509db0fea8e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 30 Jul 2020 23:31:55 +0200 Subject: ifconfig: T2653: remove duplicated code for address flush --- python/vyos/ifconfig/bond.py | 8 ++++---- python/vyos/ifconfig/bridge.py | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 5a48ac632..5c9a43c9b 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -362,10 +362,10 @@ class BondIf(Interface): value = jmespath.search('member.interface', config) if value: for interface in value: - # if we've come here we already verified the interface does - # not have an addresses configured so just flush any - # remaining ones - cmd(f'ip addr flush dev "{interface}"') + # if we've come here we already verified the interface + # does not have an addresses configured so just flush + # any remaining ones + self.flush_addrs(interface) self.add_port(interface) # Primary device interface - must be set after 'mode' diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index da4e1a289..e4ae0a80a 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -237,9 +237,10 @@ class BridgeIf(Interface): tmp = jmespath.search('member.interface', config) if tmp: for interface, interface_config in tmp.items(): - # if we've come here we already verified the interface doesn't - # have addresses configured so just flush any remaining ones - cmd(f'ip addr flush dev "{interface}"') + # if we've come here we already verified the interface + # does not have an addresses configured so just flush + # any remaining ones + self.flush_addrs(interface) # enslave interface port to bridge self.add_port(interface) -- cgit v1.2.3 From 55c93007111883308bb49a203d3e8f98f7117092 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 30 Jul 2020 23:32:18 +0200 Subject: ifconfig: T2746: bugfix for non programmed link-local addresses After the fresh rewrite of the interfaces to a unified solution (T2653) IPv6 link-local addresses are no longer added. This will result in e.g. broken RAs. --- python/vyos/ifconfig/interface.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 5496499e5..d477153e8 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -954,6 +954,15 @@ class Interface(Control): if mac: self.set_mac(mac) + # Manage IPv6 link-local addresses + tmp = jmespath.search('ipv6.address.no_default_link_local', config) + # we must check explicitly for None type as if the key is set we will + # get an empty dict () + if tmp is not None: + self.del_ipv6_eui64_address('fe80::/64') + else: + self.add_ipv6_eui64_address('fe80::/64') + # Add IPv6 EUI-based addresses tmp = jmespath.search('ipv6.address.eui64', config) if tmp: -- cgit v1.2.3 From 7beaf8fc7ff86213737202b9cd74e6a0f538297b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 30 Jul 2020 23:33:34 +0200 Subject: Add CONTRIBUTING.md --- CONTRIBUTING.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..d8177a5f5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,12 @@ +# Contributing to VyOS + +You wan't to help us improve VyOS? This is awesome. We accept any kind of Pull +Requests on GitHub. To make the life of the maintainers and you as future +contributor (or maybe maintainer) much easier we have come up with some basic +rules. Instead of copy/pasting or maintaining two instances of how to contribute +to VyOS you can find the entire process documented in our online documentation: +https://docs.vyos.io/en/latest/contributing/development.html + +Also this guide might not be complete so any PR is much appreciated. + +It might also worth browsing our blog: https://blog.vyos.io -- cgit v1.2.3 From 7e9266025d97bd51a6a413af8bd7f3f65ea9a65d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 31 Jul 2020 07:05:47 +0200 Subject: ifconfig: T2653: bugfix on wrong flush_addr API Commit 29dd5079 ("ifconfig: T2653: remove duplicated code for address flush") used the class method for address flushing, but it was cvalled in the wrong way. --- python/vyos/ifconfig/bond.py | 2 +- python/vyos/ifconfig/bridge.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 5c9a43c9b..193cea321 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -365,7 +365,7 @@ class BondIf(Interface): # if we've come here we already verified the interface # does not have an addresses configured so just flush # any remaining ones - self.flush_addrs(interface) + Interface(interface).flush_addrs() self.add_port(interface) # Primary device interface - must be set after 'mode' diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index e4ae0a80a..466e6b682 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -240,7 +240,7 @@ class BridgeIf(Interface): # if we've come here we already verified the interface # does not have an addresses configured so just flush # any remaining ones - self.flush_addrs(interface) + Interface(interface).flush_addrs() # enslave interface port to bridge self.add_port(interface) -- cgit v1.2.3 From eb0c2efe324da5ff2287000a28b7270b8782f217 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 31 Jul 2020 15:27:06 +0200 Subject: ipv6: link-local: test address assignment on interfaces --- scripts/cli/base_interfaces_test.py | 42 +++++++++++++++++++++++++++++---- scripts/cli/test_interfaces_bonding.py | 37 ++++++++++++++--------------- scripts/cli/test_interfaces_bridge.py | 26 +++++++++++++------- scripts/cli/test_interfaces_ethernet.py | 1 + 4 files changed, 73 insertions(+), 33 deletions(-) diff --git a/scripts/cli/base_interfaces_test.py b/scripts/cli/base_interfaces_test.py index 8fae5970e..14ec7e137 100644 --- a/scripts/cli/base_interfaces_test.py +++ b/scripts/cli/base_interfaces_test.py @@ -15,11 +15,12 @@ import os import unittest -from vyos.configsession import ConfigSession from netifaces import ifaddresses, AF_INET, AF_INET6 -from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local + +from vyos.configsession import ConfigSession from vyos.ifconfig import Interface from vyos.util import read_file +from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local class BasicInterfaceTest: class BaseTest(unittest.TestCase): @@ -27,6 +28,7 @@ class BasicInterfaceTest: _test_mtu = False _test_vlan = False _test_qinq = False + _test_ipv6 = False _base_path = [] _options = {} @@ -47,12 +49,14 @@ class BasicInterfaceTest: def tearDown(self): # we should not remove ethernet from the overall CLI if 'ethernet' in self._base_path: - for intf in self._interfaces: + 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.session.delete(self._base_path + [intf]) - self.session.set(self._base_path + [intf]) + self.session.delete(self._base_path + [interface]) + self.session.set(self._base_path + [interface, 'duplex', 'auto']) + self.session.set(self._base_path + [interface, 'speed', 'auto']) + self.session.set(self._base_path + [interface, 'smp-affinity', 'auto']) else: self.session.delete(self._base_path) @@ -117,6 +121,34 @@ class BasicInterfaceTest: self.assertTrue(is_intf_addr_assigned(intf, addr['addr'])) + def test_ipv6_link_local(self): + """ Common function for IPv6 link-local address assignemnts """ + if not self._test_ipv6: + return None + + for interface in self._interfaces: + base = self._base_path + [interface] + for option in self._options.get(interface, []): + self.session.set(base + option.split()) + + # after commit we must have an IPv6 link-local address + self.session.commit() + + for interface in self._interfaces: + 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.session.set(base + ['ipv6', 'address', 'no-default-link-local']) + + # after commit we must have no IPv6 link-local address + self.session.commit() + + for interface in self._interfaces: + self.assertTrue(AF_INET6 not in ifaddresses(interface)) + def _mtu_test(self, intf): """ helper function to verify MTU size """ with open('/sys/class/net/{}/mtu'.format(intf), 'r') as f: diff --git a/scripts/cli/test_interfaces_bonding.py b/scripts/cli/test_interfaces_bonding.py index b4251fa15..e3d3b25ee 100755 --- a/scripts/cli/test_interfaces_bonding.py +++ b/scripts/cli/test_interfaces_bonding.py @@ -25,38 +25,37 @@ from vyos.util import read_file class BondingInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): - super().setUp() + super().setUp() - self._base_path = ['interfaces', 'bonding'] - self._interfaces = ['bond0'] - self._test_mtu = True - self._test_vlan = True - self._test_qinq = True + self._base_path = ['interfaces', 'bonding'] + self._interfaces = ['bond0'] + self._test_mtu = True + self._test_vlan = True + self._test_qinq = True + self._test_ipv6 = True - def test_add_member(self): - members = [] + self._members = [] # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! if 'TEST_ETH' in os.environ: - members = os.environ['TEST_ETH'].split() + self._members = os.environ['TEST_ETH'].split() else: for tmp in Section.interfaces("ethernet"): if not '.' in tmp: - members.append(tmp) + self._members.append(tmp) + + self._options['bond0'] = [] + for member in self._members: + self._options['bond0'].append(f'member interface {member}') - for interface in self._interfaces: - base = self._base_path + [interface] - for member in members: - # We can not enslave an interface when there is an address - # assigned - take care here - or find them dynamically if a user - # runs vyos-smoketest on his production device? - self.session.set(base + ['member', 'interface', member]) - self.session.commit() + def test_add_address_single(self): + """ derived method to check if member interfaces are enslaved properly """ + super().test_add_address_single() for interface in self._interfaces: slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split() - self.assertListEqual(slaves, members) + self.assertListEqual(slaves, self._members) if __name__ == '__main__': unittest.main() diff --git a/scripts/cli/test_interfaces_bridge.py b/scripts/cli/test_interfaces_bridge.py index 4402cad68..bc0bb69c6 100755 --- a/scripts/cli/test_interfaces_bridge.py +++ b/scripts/cli/test_interfaces_bridge.py @@ -24,27 +24,35 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): super().setUp() + self._test_ipv6 = True + self._base_path = ['interfaces', 'bridge'] self._interfaces = ['br0'] - def test_add_remove_member(self): - members = [] + self._members = [] # we need to filter out VLAN interfaces identified by a dot (.) # in their name - just in case! if 'TEST_ETH' in os.environ: - members = os.environ['TEST_ETH'].split() + self._members = os.environ['TEST_ETH'].split() else: for tmp in Section.interfaces("ethernet"): - if not '.' in tmp: members.append(tmp) + if not '.' in tmp: + self._members.append(tmp) - for intf in self._interfaces: - base = self._base_path + [intf] + self._options['br0'] = [] + for member in self._members: + self._options['br0'].append(f'member interface {member}') + + def test_add_remove_member(self): + for interface in self._interfaces: + base = self._base_path + [interface] self.session.set(base + ['stp']) + self.session.set(base + ['address', '192.0.2.1/24']) cost = 1000 priority = 10 # assign members to bridge interface - for member in members: + for member in self._members: base_member = base + ['member', 'interface', member] self.session.set(base_member + ['cost', str(cost)]) self.session.set(base_member + ['priority', str(priority)]) @@ -53,8 +61,8 @@ class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): self.session.commit() - for intf in self._interfaces: - self.session.delete(self._base_path + [intf, 'member']) + for interface in self._interfaces: + self.session.delete(self._base_path + [interface, 'member']) self.session.commit() diff --git a/scripts/cli/test_interfaces_ethernet.py b/scripts/cli/test_interfaces_ethernet.py index ccc4238e2..c31a606da 100755 --- a/scripts/cli/test_interfaces_ethernet.py +++ b/scripts/cli/test_interfaces_ethernet.py @@ -29,6 +29,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): self._test_mtu = True self._test_vlan = True self._test_qinq = True + self._test_ipv6 = True self._interfaces = [] # we need to filter out VLAN interfaces identified by a dot (.) -- cgit v1.2.3 From dfbbdd2950c3db3f56649ff602a86b8bf17de748 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 1 Aug 2020 10:49:59 +0200 Subject: ifconfig: T2752: fix string format in verify_interface_exists() We do not have a formatted string here thus the "f" keyword is wrong and triggered an exception. --- python/vyos/configverify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 8e06d16f2..bb590a514 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -69,7 +69,7 @@ def verify_interface_exists(config): """ from netifaces import interfaces if not config['ifname'] in interfaces(): - raise ConfigError(f'Interface "{ifname}" does not exist!' + raise ConfigError('Interface "{ifname}" does not exist!' .format(**config)) def verify_source_interface(config): -- cgit v1.2.3 From 37e905de5ec903cff3e83ce4863b22b9e6d53d73 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 1 Aug 2020 12:28:21 +0200 Subject: T2753: add new op-mode command "add system image" --- op-mode-definitions/add-system-image.xml | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 op-mode-definitions/add-system-image.xml diff --git a/op-mode-definitions/add-system-image.xml b/op-mode-definitions/add-system-image.xml new file mode 100644 index 000000000..2809f6050 --- /dev/null +++ b/op-mode-definitions/add-system-image.xml @@ -0,0 +1,38 @@ + + + + + + + Add item to a system facility + + + + + Add a new image to the system + + /path/to/vyos-image.iso http://example.com/vyos-image.iso + + + sudo ${vyatta_sbindir}/install-image "$4" + + + + Username for authentication + + + + + Password to use with authentication + + sudo ${vyatta_sbindir}/install-image "$4" "$6" "$8" + + + + + + + + + + -- cgit v1.2.3 From 134a05e95c14c5a7d2da23ca7fb1b05c533d53c6 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 1 Aug 2020 13:07:09 +0200 Subject: Makefile: remove add/node.def file to not break builds --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 6c715d138..2773642d0 100644 --- a/Makefile +++ b/Makefile @@ -88,6 +88,7 @@ op_mode_definitions: find $(CURDIR)/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 # XXX: delete top level op mode node.def's that now live in other packages + rm -f $(OP_TMPL_DIR)/add/node.def rm -f $(OP_TMPL_DIR)/clear/node.def rm -f $(OP_TMPL_DIR)/clear/interfaces/node.def rm -f $(OP_TMPL_DIR)/set/node.def -- cgit v1.2.3 From 50a0781b3f3e0c86d6418acb72b0036729a72283 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 1 Aug 2020 13:23:09 +0200 Subject: T2753: adjust "add system image" to new named command line arguments This is needed work for adding VRF support. --- op-mode-definitions/add-system-image.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/op-mode-definitions/add-system-image.xml b/op-mode-definitions/add-system-image.xml index 2809f6050..a39f4cc22 100644 --- a/op-mode-definitions/add-system-image.xml +++ b/op-mode-definitions/add-system-image.xml @@ -14,7 +14,7 @@ /path/to/vyos-image.iso http://example.com/vyos-image.iso - sudo ${vyatta_sbindir}/install-image "$4" + sudo ${vyatta_sbindir}/install-image --url "$4" @@ -25,7 +25,7 @@ Password to use with authentication - sudo ${vyatta_sbindir}/install-image "$4" "$6" "$8" + sudo ${vyatta_sbindir}/install-image --url "$4" --username "$6" --password "$8" -- cgit v1.2.3 From f69fccfead3f2b0608d14d099f5583bb397d6131 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 1 Aug 2020 14:30:27 +0200 Subject: T2690: add VRF support to "add system image" --- op-mode-definitions/add-system-image.xml | 28 ++++++++++++++++++++++++++-- op-mode-definitions/show-vrf.xml | 6 +++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/op-mode-definitions/add-system-image.xml b/op-mode-definitions/add-system-image.xml index a39f4cc22..3dc1c67ab 100644 --- a/op-mode-definitions/add-system-image.xml +++ b/op-mode-definitions/add-system-image.xml @@ -14,8 +14,32 @@ /path/to/vyos-image.iso http://example.com/vyos-image.iso - sudo ${vyatta_sbindir}/install-image --url "$4" + sudo ${vyatta_sbindir}/install-image --url "${4}" + + + Download image via specified VRF + + vrf name + + + sudo ${vyatta_sbindir}/install-image --url "${4}" --vrf "${6}" + + + + Username for authentication + + + + + Password to use with authentication + + sudo ${vyatta_sbindir}/install-image --url "${4}" --vrf "${6}" --username "${8}" --password "${10}" + + + + + Username for authentication @@ -25,7 +49,7 @@ Password to use with authentication - sudo ${vyatta_sbindir}/install-image --url "$4" --username "$6" --password "$8" + sudo ${vyatta_sbindir}/install-image --url "${4}" --username "${6}" --password "${8}" diff --git a/op-mode-definitions/show-vrf.xml b/op-mode-definitions/show-vrf.xml index 1c806908b..438e7c334 100644 --- a/op-mode-definitions/show-vrf.xml +++ b/op-mode-definitions/show-vrf.xml @@ -11,9 +11,9 @@ Show information on specific VRF instance - - vrf name - + + vrf name + ${vyos_op_scripts_dir}/show_vrf.py -e "$3" -- cgit v1.2.3 From 4803a3a169f83a7c6c9ba888d7a111a922e84467 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 2 Aug 2020 14:54:19 +0200 Subject: ipoe-server: T2314: fix improper use of fail-time dictionary key --- src/conf_mode/service_ipoe-server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index b539da98e..fbc1000dd 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -153,7 +153,7 @@ def get_config(): conf.set_level(base_path + ['authentication', 'radius', 'server', server]) if conf.exists(['fail-time']): - radius['fail-time'] = conf.return_value(['fail-time']) + radius['fail_time'] = conf.return_value(['fail-time']) if conf.exists(['port']): radius['port'] = conf.return_value(['port']) -- cgit v1.2.3 From ea10917897bb4478e7f6f576f2e1c42b134cc5c0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 2 Aug 2020 14:58:42 +0200 Subject: accel-ppp: T2756: make RADIUS accounting port configurable Make the port used for RADIUS accounting user configurable. This is now valid for the following services which are based on Accel-PPP: * ipoe-server * pppoe-server * l2tp * pptp * sstp --- data/templates/accel-ppp/ipoe.config.tmpl | 2 +- data/templates/accel-ppp/l2tp.config.tmpl | 2 +- data/templates/accel-ppp/pppoe.config.tmpl | 2 +- data/templates/accel-ppp/pptp.config.tmpl | 2 +- data/templates/accel-ppp/sstp.config.tmpl | 2 +- interface-definitions/include/accel-radius-additions.xml.in | 12 ++++++++++++ src/conf_mode/service_ipoe-server.py | 6 +++++- src/conf_mode/service_pppoe-server.py | 6 +++++- src/conf_mode/vpn_l2tp.py | 6 +++++- src/conf_mode/vpn_pptp.py | 6 +++++- src/conf_mode/vpn_sstp.py | 6 +++++- 11 files changed, 42 insertions(+), 10 deletions(-) diff --git a/data/templates/accel-ppp/ipoe.config.tmpl b/data/templates/accel-ppp/ipoe.config.tmpl index 84de5bf51..fca520efa 100644 --- a/data/templates/accel-ppp/ipoe.config.tmpl +++ b/data/templates/accel-ppp/ipoe.config.tmpl @@ -77,7 +77,7 @@ chap-secrets={{ chap_secrets_file }} [radius] verbose=1 {% for r in radius_server %} -server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }} +server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} {% endfor -%} acct-timeout={{ radius_acct_tmo }} diff --git a/data/templates/accel-ppp/l2tp.config.tmpl b/data/templates/accel-ppp/l2tp.config.tmpl index b0ef17525..b9131684d 100644 --- a/data/templates/accel-ppp/l2tp.config.tmpl +++ b/data/templates/accel-ppp/l2tp.config.tmpl @@ -83,7 +83,7 @@ chap-secrets={{ chap_secrets_file }} [radius] verbose=1 {% for r in radius_server %} -server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }} +server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} {% endfor -%} acct-timeout={{ radius_acct_tmo }} diff --git a/data/templates/accel-ppp/pppoe.config.tmpl b/data/templates/accel-ppp/pppoe.config.tmpl index 370ca7946..5ad628fde 100644 --- a/data/templates/accel-ppp/pppoe.config.tmpl +++ b/data/templates/accel-ppp/pppoe.config.tmpl @@ -93,7 +93,7 @@ chap-secrets={{ chap_secrets_file }} [radius] verbose=1 {% for r in radius_server %} -server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }} +server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} {% endfor -%} acct-timeout={{ radius_acct_tmo }} diff --git a/data/templates/accel-ppp/pptp.config.tmpl b/data/templates/accel-ppp/pptp.config.tmpl index 0bbfc13c5..e0f2c6da9 100644 --- a/data/templates/accel-ppp/pptp.config.tmpl +++ b/data/templates/accel-ppp/pptp.config.tmpl @@ -66,7 +66,7 @@ chap-secrets={{ chap_secrets_file }} [radius] verbose=1 {% for r in radius_server %} -server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }} +server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} {% endfor -%} acct-timeout={{ radius_acct_tmo }} diff --git a/data/templates/accel-ppp/sstp.config.tmpl b/data/templates/accel-ppp/sstp.config.tmpl index 2c8c00023..c9e4a1d7d 100644 --- a/data/templates/accel-ppp/sstp.config.tmpl +++ b/data/templates/accel-ppp/sstp.config.tmpl @@ -69,7 +69,7 @@ chap-secrets={{ chap_secrets_file }} [radius] verbose=1 {% for r in radius_server %} -server={{ r.server }},{{ r.key }},auth-port={{ r.port }},req-limit=0,fail-time={{ r.fail_time }} +server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }} {% endfor -%} acct-timeout={{ radius_acct_tmo }} diff --git a/interface-definitions/include/accel-radius-additions.xml.in b/interface-definitions/include/accel-radius-additions.xml.in index 227a043cd..e37b68514 100644 --- a/interface-definitions/include/accel-radius-additions.xml.in +++ b/interface-definitions/include/accel-radius-additions.xml.in @@ -2,6 +2,18 @@ + + + Accounting port + + 1-65535 + Numeric IP port (default: 1813) + + + + + + Mark server unavailable for <n> seconds on failure diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index fbc1000dd..553cc2e97 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -147,7 +147,8 @@ def get_config(): 'server' : server, 'key' : '', 'fail_time' : 0, - 'port' : '1812' + 'port' : '1812', + 'acct_port' : '1813' } conf.set_level(base_path + ['authentication', 'radius', 'server', server]) @@ -158,6 +159,9 @@ def get_config(): if conf.exists(['port']): radius['port'] = conf.return_value(['port']) + if conf.exists(['acct-port']): + radius['acct_port'] = conf.return_value(['acct-port']) + if conf.exists(['key']): radius['key'] = conf.return_value(['key']) diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 3149bbb2f..a8357f653 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -242,7 +242,8 @@ def get_config(): 'server' : server, 'key' : '', 'fail_time' : 0, - 'port' : '1812' + 'port' : '1812', + 'acct_port' : '1813' } conf.set_level(base_path + ['authentication', 'radius', 'server', server]) @@ -253,6 +254,9 @@ def get_config(): if conf.exists(['port']): radius['port'] = conf.return_value(['port']) + if conf.exists(['acct-port']): + radius['acct_port'] = conf.return_value(['acct-port']) + if conf.exists(['key']): radius['key'] = conf.return_value(['key']) diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index 88df2902e..26ad1af84 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -151,7 +151,8 @@ def get_config(): 'server' : server, 'key' : '', 'fail_time' : 0, - 'port' : '1812' + 'port' : '1812', + 'acct_port' : '1813' } conf.set_level(base_path + ['authentication', 'radius', 'server', server]) @@ -162,6 +163,9 @@ def get_config(): if conf.exists(['port']): radius['port'] = conf.return_value(['port']) + if conf.exists(['acct-port']): + radius['acct_port'] = conf.return_value(['acct-port']) + if conf.exists(['key']): radius['key'] = conf.return_value(['key']) diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py index 4536692d2..32cbadd74 100755 --- a/src/conf_mode/vpn_pptp.py +++ b/src/conf_mode/vpn_pptp.py @@ -111,7 +111,8 @@ def get_config(): 'server' : server, 'key' : '', 'fail_time' : 0, - 'port' : '1812' + 'port' : '1812', + 'acct_port' : '1813' } conf.set_level(base_path + ['authentication', 'radius', 'server', server]) @@ -122,6 +123,9 @@ def get_config(): if conf.exists(['port']): radius['port'] = conf.return_value(['port']) + if conf.exists(['acct-port']): + radius['acct_port'] = conf.return_value(['acct-port']) + if conf.exists(['key']): radius['key'] = conf.return_value(['key']) diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index 4c4d8e403..ddb499bf4 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -118,7 +118,8 @@ def get_config(): 'server' : server, 'key' : '', 'fail_time' : 0, - 'port' : '1812' + 'port' : '1812', + 'acct_port' : '1813' } conf.set_level(base_path + ['authentication', 'radius', 'server', server]) @@ -129,6 +130,9 @@ def get_config(): if conf.exists(['port']): radius['port'] = conf.return_value(['port']) + if conf.exists(['acct-port']): + radius['acct_port'] = conf.return_value(['acct-port']) + if conf.exists(['key']): radius['key'] = conf.return_value(['key']) -- cgit v1.2.3 From f583fa94ba00d4c29abe997d316a381c98cc65bf Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 2 Aug 2020 18:24:19 +0200 Subject: wwan: pppoe: op-mode: T2488: only call pppstats when interface is up Without this check an error will be shown: pppstats: nonexistent interface 'pppoe0' specified. --- op-mode-definitions/show-interfaces-pppoe.xml | 2 +- op-mode-definitions/show-interfaces-wirelessmodem.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/op-mode-definitions/show-interfaces-pppoe.xml b/op-mode-definitions/show-interfaces-pppoe.xml index 4263a2f0a..393ca912f 100644 --- a/op-mode-definitions/show-interfaces-pppoe.xml +++ b/op-mode-definitions/show-interfaces-pppoe.xml @@ -26,7 +26,7 @@ interfaces pppoe - /usr/sbin/pppstats $4 + if [ -d "/sys/class/net/$4" ]; then /usr/sbin/pppstats "$4"; fi diff --git a/op-mode-definitions/show-interfaces-wirelessmodem.xml b/op-mode-definitions/show-interfaces-wirelessmodem.xml index 46f872c85..c0ab9c66f 100644 --- a/op-mode-definitions/show-interfaces-wirelessmodem.xml +++ b/op-mode-definitions/show-interfaces-wirelessmodem.xml @@ -26,7 +26,7 @@ interfaces wirelessmodem - /usr/sbin/pppstats $4 + if [ -d "/sys/class/net/$4" ]; then /usr/sbin/pppstats "$4"; fi -- cgit v1.2.3 From 9f67250480d1e6727424d00212d69f008ffdb647 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 2 Aug 2020 19:34:34 +0200 Subject: op-mode: console-server: T2490: cleanup XML Move the connect part to connect-disconnect.xml --- op-mode-definitions/connect-disconnect.xml | 11 ++++++++++- op-mode-definitions/show-console-server.xml | 13 ------------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/op-mode-definitions/connect-disconnect.xml b/op-mode-definitions/connect-disconnect.xml index f0fcef5da..ea2883e0b 100644 --- a/op-mode-definitions/connect-disconnect.xml +++ b/op-mode-definitions/connect-disconnect.xml @@ -2,9 +2,18 @@ - Establish a connection + Establish connection + + + Connect to device attached to serial console server + + service console-server device + + + /usr/bin/console "$3" + Bring up a connection-oriented network interface diff --git a/op-mode-definitions/show-console-server.xml b/op-mode-definitions/show-console-server.xml index e47b6cfaa..77a7f3376 100644 --- a/op-mode-definitions/show-console-server.xml +++ b/op-mode-definitions/show-console-server.xml @@ -1,18 +1,5 @@ - - - - - Connect to device attached to serial console server - - service console-server device - - - /usr/bin/console "$3" - - - -- cgit v1.2.3 From f2cc87a259df1d2ab4d0a7eb366224a238422048 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 2 Aug 2020 19:36:39 +0200 Subject: op-mode: use individual XML files for connect and disconnect --- op-mode-definitions/connect-disconnect.xml | 46 ------------------------------ op-mode-definitions/connect.xml | 29 +++++++++++++++++++ op-mode-definitions/disconnect.xml | 20 +++++++++++++ 3 files changed, 49 insertions(+), 46 deletions(-) delete mode 100644 op-mode-definitions/connect-disconnect.xml create mode 100644 op-mode-definitions/connect.xml create mode 100644 op-mode-definitions/disconnect.xml diff --git a/op-mode-definitions/connect-disconnect.xml b/op-mode-definitions/connect-disconnect.xml deleted file mode 100644 index ea2883e0b..000000000 --- a/op-mode-definitions/connect-disconnect.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - Establish connection - - - - - Connect to device attached to serial console server - - service console-server device - - - /usr/bin/console "$3" - - - - Bring up a connection-oriented network interface - - interfaces pppoe - interfaces wirelessmodem - - - sudo ${vyos_op_scripts_dir}/connect_disconnect.py --connect "$3" - - - - - - Take down a connection - - - - - Take down a connection-oriented network interface - - interfaces pppoe - interfaces wirelessmodem - - - sudo ${vyos_op_scripts_dir}/connect_disconnect.py --disconnect "$3" - - - - diff --git a/op-mode-definitions/connect.xml b/op-mode-definitions/connect.xml new file mode 100644 index 000000000..1ec62949a --- /dev/null +++ b/op-mode-definitions/connect.xml @@ -0,0 +1,29 @@ + + + + + Establish connection + + + + + Connect to device attached to serial console server + + service console-server device + + + /usr/bin/console "$3" + + + + Bring up a connection-oriented network interface + + interfaces pppoe + interfaces wirelessmodem + + + sudo ${vyos_op_scripts_dir}/connect_disconnect.py --connect "$3" + + + + diff --git a/op-mode-definitions/disconnect.xml b/op-mode-definitions/disconnect.xml new file mode 100644 index 000000000..bf2c37b89 --- /dev/null +++ b/op-mode-definitions/disconnect.xml @@ -0,0 +1,20 @@ + + + + + Take down a connection + + + + + Take down a connection-oriented network interface + + interfaces pppoe + interfaces wirelessmodem + + + sudo ${vyos_op_scripts_dir}/connect_disconnect.py --disconnect "$3" + + + + -- cgit v1.2.3 From 091efc96bc0fbeea7c7bf1e8e234c72bc8df801b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 2 Aug 2020 20:14:30 +0200 Subject: op-mode: arping: T689: use full path to binary --- op-mode-definitions/force-arp.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/op-mode-definitions/force-arp.xml b/op-mode-definitions/force-arp.xml index 3eadabf0a..c7bcad413 100644 --- a/op-mode-definitions/force-arp.xml +++ b/op-mode-definitions/force-arp.xml @@ -27,13 +27,13 @@ Send gratuitous ARP reply for specified address - sudo arping -I $5 -c 1 -A $7 + sudo /usr/bin/arping -I $5 -c 1 -A $7 Send specified number of ARP replies - sudo arping -I $5 -c $9 -A $7 + sudo /usr/bin/arping -I $5 -c $9 -A $7 @@ -58,13 +58,13 @@ Send gratuitous ARP request for specified address - sudo arping -I $5 -c 1 -U $7 + sudo /usr/bin/arping -I $5 -c 1 -U $7 Send specified number of ARP requests - sudo arping -I $5 -c $9 -U $7 + sudo /usr/bin/arping -I $5 -c $9 -U $7 -- cgit v1.2.3 From 9ef82aa5d4db161fd70af4d015f1cf35cc984524 Mon Sep 17 00:00:00 2001 From: jack9603301 Date: Mon, 3 Aug 2020 17:43:00 +0800 Subject: ipv6-tool: T2724: Support for IPv6 Toolset --- op-mode-definitions/force-ipv6-nd.xml | 33 +++++++++++++++++++++++++++++++++ op-mode-definitions/force-ipv6-rd.xml | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 op-mode-definitions/force-ipv6-nd.xml create mode 100644 op-mode-definitions/force-ipv6-rd.xml diff --git a/op-mode-definitions/force-ipv6-nd.xml b/op-mode-definitions/force-ipv6-nd.xml new file mode 100644 index 000000000..64c8347ea --- /dev/null +++ b/op-mode-definitions/force-ipv6-nd.xml @@ -0,0 +1,33 @@ + + + + + + + ICMPv6 Neighbor Discovery tool + + + + + IPv6 Neighbor Discovery on a specific interface + + + + + + + + The IP address of the target + + <h:h:h:h:h:h:h:h> + + + ndisc6 $6 $4 -m + + + + + + + + diff --git a/op-mode-definitions/force-ipv6-rd.xml b/op-mode-definitions/force-ipv6-rd.xml new file mode 100644 index 000000000..722cb67bf --- /dev/null +++ b/op-mode-definitions/force-ipv6-rd.xml @@ -0,0 +1,34 @@ + + + + + + + IPv6 Router Discovery tool + + + + + IPv6 Router Discovery tool on a specific interface + + + + + rdisc6 $4 + + + + The IP address of the target + + <h:h:h:h:h:h:h:h> + + + rdisc6 $6 $4 -m + + + + + + + + -- cgit v1.2.3 From 84331764a81d7e31c5c4dd5466f347054283d377 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 12:56:13 +0200 Subject: ifconfig: T2740: pass config dict to DHCP class for IPv6 This removes additional code paths as we can instatly work with the input dict the same was as it was done for PPPoE. This fixes the entire DHCPv6-PD support on non PPPoE interfaces as this was lost in translation while processing T2653. --- data/templates/dhcp-client/ipv6.tmpl | 53 +++++++++++++++++--------------- data/templates/dhcp-client/ipv6_new.tmpl | 47 ---------------------------- python/vyos/ifconfig/dhcp.py | 36 +++++++++------------- python/vyos/ifconfig/interface.py | 49 ++++++++++++----------------- src/conf_mode/interfaces-pppoe.py | 2 +- 5 files changed, 63 insertions(+), 124 deletions(-) delete mode 100644 data/templates/dhcp-client/ipv6_new.tmpl diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl index 490f14726..112431c5f 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -4,41 +4,44 @@ interface {{ ifname }} { request domain-name-servers; request domain-name; -{% if dhcpv6_prm_only %} +{% if dhcpv6_options is defined %} +{% if dhcpv6_options.parameters_only is defined %} information-only; -{% endif %} -{% if not dhcpv6_temporary %} +{% endif %} +{% if dhcpv6_options.temporary is not defined %} send ia-na 1; # non-temporary address -{% endif %} -{% if dhcpv6_pd_interfaces %} +{% endif %} +{% if dhcpv6_options.prefix_delegation is defined %} send ia-pd 2; # prefix delegation +{% endif %} {% endif %} }; -{% if not dhcpv6_temporary %} +{% if dhcpv6_options is defined %} +{% if dhcpv6_options.temporary is not defined %} id-assoc na 1 { # Identity association NA }; -{% endif %} +{% endif %} -{% if dhcpv6_pd_interfaces %} +{% if dhcpv6_options.prefix_delegation is defined %} id-assoc pd 2 { -{% if dhcpv6_pd_length %} - prefix ::/{{ dhcpv6_pd_length }} infinity; -{% endif %} -{% for intf in dhcpv6_pd_interfaces %} - prefix-interface {{ intf.ifname }} { -{% if intf.sla_id %} - sla-id {{ intf.sla_id }}; -{% endif %} -{% if intf.sla_len %} - sla-len {{ intf.sla_len }}; -{% endif %} -{% if intf.if_id %} - ifid {{ intf.if_id }}; -{% endif %} +{% if dhcpv6_options.prefix_delegation.length is defined %} + prefix ::/{{ dhcpv6_options.prefix_delegation.length }} infinity; +{% endif %} +{% for interface in dhcpv6_options.prefix_delegation.interface %} + prefix-interface {{ interface }} { +{% if dhcpv6_options.prefix_delegation.interface[interface].sla_id is defined %} + sla-id {{ dhcpv6_options.prefix_delegation.interface[interface].sla_id }}; +{% endif %} +{% if dhcpv6_options.prefix_delegation.interface[interface].sla_len is defined %} + sla-len {{ dhcpv6_options.prefix_delegation.interface[interface].sla_len }}; +{% endif %} +{% if dhcpv6_options.prefix_delegation.interface[interface].address is defined %} + ifid {{ dhcpv6_options.prefix_delegation.interface[interface].address }}; +{% endif %} }; -{% endfor %} +{% endfor %} }; -{% endif %} - +{% endif %} +{% endif %} diff --git a/data/templates/dhcp-client/ipv6_new.tmpl b/data/templates/dhcp-client/ipv6_new.tmpl deleted file mode 100644 index 112431c5f..000000000 --- a/data/templates/dhcp-client/ipv6_new.tmpl +++ /dev/null @@ -1,47 +0,0 @@ -# generated by dhcp.py -# man https://www.unix.com/man-page/debian/5/dhcp6c.conf/ - -interface {{ ifname }} { - request domain-name-servers; - request domain-name; -{% if dhcpv6_options is defined %} -{% if dhcpv6_options.parameters_only is defined %} - information-only; -{% endif %} -{% if dhcpv6_options.temporary is not defined %} - send ia-na 1; # non-temporary address -{% endif %} -{% if dhcpv6_options.prefix_delegation is defined %} - send ia-pd 2; # prefix delegation -{% endif %} -{% endif %} -}; - -{% if dhcpv6_options is defined %} -{% if dhcpv6_options.temporary is not defined %} -id-assoc na 1 { - # Identity association NA -}; -{% endif %} - -{% if dhcpv6_options.prefix_delegation is defined %} -id-assoc pd 2 { -{% if dhcpv6_options.prefix_delegation.length is defined %} - prefix ::/{{ dhcpv6_options.prefix_delegation.length }} infinity; -{% endif %} -{% for interface in dhcpv6_options.prefix_delegation.interface %} - prefix-interface {{ interface }} { -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_id is defined %} - sla-id {{ dhcpv6_options.prefix_delegation.interface[interface].sla_id }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_len is defined %} - sla-len {{ dhcpv6_options.prefix_delegation.interface[interface].sla_len }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].address is defined %} - ifid {{ dhcpv6_options.prefix_delegation.interface[interface].address }}; -{% endif %} - }; -{% endfor %} -}; -{% endif %} -{% endif %} diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py index a8b9a2a87..bd37970a2 100644 --- a/python/vyos/ifconfig/dhcp.py +++ b/python/vyos/ifconfig/dhcp.py @@ -15,6 +15,7 @@ import os +from vyos.configverify import verify_dhcpv6 from vyos.dicts import FixedDict from vyos.ifconfig.control import Control from vyos.template import render @@ -82,39 +83,31 @@ class _DHCPv4 (Control): class _DHCPv6 (Control): def __init__(self, ifname): super().__init__() - self.options = FixedDict(**{ - 'ifname': ifname, - 'dhcpv6_prm_only': False, - 'dhcpv6_temporary': False, - 'dhcpv6_pd_interfaces': [], - 'dhcpv6_pd_length': '' - }) - self._conf_file = f'/run/dhcp6c/dhcp6c.{ifname}.conf' + self.options = {'ifname' : ifname} + self._config = f'/run/dhcp6c/dhcp6c.{ifname}.conf' def set(self): """ - Configure interface as DHCPv6 client. The dhclient binary is automatically - started in background! + Configure interface as DHCPv6 client. The client is automatically + started in background when address is configured as DHCP. Example: - >>> from vyos.ifconfig import Interface >>> j = Interface('eth0') >>> j.dhcp.v6.set() """ - # better save then sorry .. should be checked in interface script - # but if you missed it we are safe! - if self.options['dhcpv6_prm_only'] and self.options['dhcpv6_temporary']: - raise Exception( - 'DHCPv6 temporary and parameters-only options are mutually exclusive!') + # better save then sorry .. should be checked in interface script but if you + # missed it we are safe! + verify_dhcpv6(self.options) - render(self._conf_file, 'dhcp-client/ipv6.tmpl', self.options, trim_blocks=True) - return self._cmd('systemctl restart dhcp6c@{ifname}.service'.format(**self.options)) + render(self._config, 'dhcp-client/ipv6.tmpl', self.options, trim_blocks=True) + return self._cmd('systemctl restart dhcp6c@{ifname}.service'.format( + **self.options)) def delete(self): """ - De-configure interface as DHCPv6 clinet. All auto generated files like + De-configure interface as DHCPv6 client. All auto generated files like pid, config and lease will be removed. Example: @@ -126,9 +119,8 @@ class _DHCPv6 (Control): self._cmd('systemctl stop dhcp6c@{ifname}.service'.format(**self.options)) # cleanup old config files - if os.path.isfile(self._conf_file): - os.remove(self._conf_file) - + if os.path.isfile(self._config): + os.remove(self._config) class DHCP(object): def __init__(self, ifname): diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index d477153e8..f5e43e172 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -822,6 +822,22 @@ class Interface(Control): value = '2' if 'disable_link_detect' in config else '1' self.set_link_detect(value) + # DHCP options + if 'dhcp_options' in config: + dhcp_options = config.get('dhcp_options') + if 'client_id' in dhcp_options: + self.dhcp.v4.options['client_id'] = dhcp_options.get('client_id') + + if 'host_name' in dhcp_options: + self.dhcp.v4.options['hostname'] = dhcp_options.get('host_name') + + if 'vendor_class_id' in dhcp_options: + self.dhcp.v4.options['vendor_class_id'] = dhcp_options.get('vendor_class_id') + + # DHCPv6 options + if 'dhcpv6_options' in config: + self.dhcp.v6.options = config + # Configure assigned interface IP addresses. No longer # configured addresses will be removed first new_addr = config.get('address', []) @@ -849,35 +865,6 @@ class Interface(Control): # checked before self.set_vrf(config.get('vrf', '')) - # DHCP options - if 'dhcp_options' in config: - dhcp_options = config.get('dhcp_options') - if 'client_id' in dhcp_options: - self.dhcp.v4.options['client_id'] = dhcp_options.get('client_id') - - if 'host_name' in dhcp_options: - self.dhcp.v4.options['hostname'] = dhcp_options.get('host_name') - - if 'vendor_class_id' in dhcp_options: - self.dhcp.v4.options['vendor_class_id'] = dhcp_options.get('vendor_class_id') - - # DHCPv6 options - if 'dhcpv6_options' in config: - dhcpv6_options = config.get('dhcpv6_options') - if 'parameters_only' in dhcpv6_options: - self.dhcp.v6.options['dhcpv6_prm_only'] = True - - if 'temporary' in dhcpv6_options: - self.dhcp.v6.options['dhcpv6_temporary'] = True - - if 'prefix_delegation' in dhcpv6_options: - prefix_delegation = dhcpv6_options.get('prefix_delegation') - if 'length' in prefix_delegation: - self.dhcp.v6.options['dhcpv6_pd_length'] = prefix_delegation.get('length') - - if 'interface' in prefix_delegation: - self.dhcp.v6.options['dhcpv6_pd_interfaces'] = prefix_delegation.get('interface') - # Configure ARP cache timeout in milliseconds - has default value tmp = jmespath.search('ip.arp_cache_timeout', config) value = tmp if (tmp != None) else '30' @@ -982,9 +969,11 @@ class Interface(Control): self.del_vlan(vif_s_id) # create/update 802.1ad (Q-in-Q VLANs) + ifname = config['ifname'] for vif_s_id, vif_s in config.get('vif_s', {}).items(): tmp=get_ethertype(vif_s.get('ethertype', '0x88A8')) s_vlan = self.add_vlan(vif_s_id, ethertype=tmp) + vif_s['ifname'] = f'{ifname}.{vif_s_id}' s_vlan.update(vif_s) # remove no longer required client VLAN (vif-c) @@ -994,6 +983,7 @@ class Interface(Control): # create/update client VLAN (vif-c) interface for vif_c_id, vif_c in vif_s.get('vif_c', {}).items(): c_vlan = s_vlan.add_vlan(vif_c_id) + vif_c['ifname'] = f'{ifname}.{vif_s_id}.{vif_c_id}' c_vlan.update(vif_c) # remove no longer required 802.1q VLAN interfaces @@ -1003,4 +993,5 @@ class Interface(Control): # create/update 802.1q VLAN interfaces for vif_id, vif in config.get('vif', {}).items(): vlan = self.add_vlan(vif_id) + vif['ifname'] = f'{ifname}.{vif_id}' vlan.update(vif) diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index b9a88a949..928113b49 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -108,7 +108,7 @@ def generate(pppoe): if tmp and len(tmp) > 0: # ipv6.tmpl relies on ifname - this should be made consitent in the # future better then double key-ing the same value - render(config_wide_dhcp6c, 'dhcp-client/ipv6_new.tmpl', pppoe, trim_blocks=True) + render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe, trim_blocks=True) return None -- cgit v1.2.3 From b6dcb0a887a4fab89ca870d6142d6856cccc181f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 13:51:48 +0200 Subject: ifconfig: T2653: unify DHCPv4 configuration Pass the interface dictionary transparently to the DHCP module and render the DHCP client config template directly from the same source instead of transcoding it once more. --- data/templates/dhcp-client/daemon-options.tmpl | 2 +- data/templates/dhcp-client/ipv4.tmpl | 20 +++++------ python/vyos/ifconfig/dhcp.py | 48 +++++++++++++------------- python/vyos/ifconfig/interface.py | 10 +----- 4 files changed, 36 insertions(+), 44 deletions(-) diff --git a/data/templates/dhcp-client/daemon-options.tmpl b/data/templates/dhcp-client/daemon-options.tmpl index 12786b777..a0ba2c9ef 100644 --- a/data/templates/dhcp-client/daemon-options.tmpl +++ b/data/templates/dhcp-client/daemon-options.tmpl @@ -1 +1 @@ -DHCLIENT_OPTS="-nw -cf {{ conf_file }} -pf {{ pid_file }} -lf {{ lease_file }} {{ '-S' if dhcpv6_prm_only }} {{ '-T' if dhcpv6_temporary }} {{ ifname }}" +DHCLIENT_OPTS="-nw -cf /var/lib/dhcp/dhclient_{{ifname}}.conf -pf /var/lib/dhcp/dhclient_{{ifname}}.pid -lf /var/lib/dhcp/dhclient_{{ifname}}.leases {{ifname}}" diff --git a/data/templates/dhcp-client/ipv4.tmpl b/data/templates/dhcp-client/ipv4.tmpl index ab772b5f6..fe2a67f08 100644 --- a/data/templates/dhcp-client/ipv4.tmpl +++ b/data/templates/dhcp-client/ipv4.tmpl @@ -4,14 +4,14 @@ timeout 60; retry 300; interface "{{ ifname }}" { - send host-name "{{ hostname }}"; - {% if client_id -%} - send dhcp-client-identifier "{{ client_id }}"; - {% endif -%} - {% if vendor_class_id -%} - send vendor-class-identifier "{{ vendor_class_id }}"; - {% endif -%} - request subnet-mask, broadcast-address, routers, domain-name-servers, - rfc3442-classless-static-routes, domain-name, interface-mtu; - require subnet-mask; + send host-name "{{ dhcp_options.host_name }}"; +{% if dhcp_options.client_id is defined and dhcp_options.client_id is not none %} + send dhcp-client-identifier "{{ dhcp_options.client_id }}"; +{% endif %} +{% if dhcp_options.vendor_class_id is defined and dhcp_options.vendor_class_id is not none %} + send vendor-class-identifier "{{ dhcp_options.vendor_class_id }}"; +{% endif %} + request subnet-mask, broadcast-address, routers, domain-name-servers, + rfc3442-classless-static-routes, domain-name, interface-mtu; + require subnet-mask; } diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py index bd37970a2..5f99a0b7e 100644 --- a/python/vyos/ifconfig/dhcp.py +++ b/python/vyos/ifconfig/dhcp.py @@ -14,26 +14,22 @@ # License along with this library. If not, see . import os +import jmespath +from vyos.configdict import dict_merge from vyos.configverify import verify_dhcpv6 -from vyos.dicts import FixedDict from vyos.ifconfig.control import Control from vyos.template import render class _DHCPv4 (Control): def __init__(self, ifname): super().__init__() - config_base = r'/var/lib/dhcp/dhclient_' - self.options = FixedDict(**{ - 'ifname': ifname, - 'hostname': '', - 'client_id': '', - 'vendor_class_id': '', - 'conf_file': config_base + f'{ifname}.conf', - 'options_file': config_base + f'{ifname}.options', - 'pid_file': config_base + f'{ifname}.pid', - 'lease_file': config_base + f'{ifname}.leases', - }) + config_base = r'/var/lib/dhcp/dhclient' + self._conf_file = f'{config_base}_{ifname}.conf' + self._options_file = f'{config_base}_{ifname}.options' + self._pid_file = f'{config_base}_{ifname}.pid' + self._lease_file = f'{config_base}_{ifname}.leases' + self.options = {'ifname' : ifname} # replace dhcpv4/v6 with systemd.networkd? def set(self): @@ -42,19 +38,24 @@ class _DHCPv4 (Control): started in background! Example: - >>> from vyos.ifconfig import Interface >>> j = Interface('eth0') >>> j.dhcp.v4.set() """ - if not self.options['hostname']: + + if jmespath.search('dhcp_options.host_name', self.options) == None: # read configured system hostname. # maybe change to vyos hostd client ??? + hostname = 'vyos' with open('/etc/hostname', 'r') as f: - self.options['hostname'] = f.read().rstrip('\n') + hostname = f.read().rstrip('\n') + tmp = {'dhcp_options' : { 'host_name' : hostname}} + self.options = dict_merge(tmp, self.options) - render(self.options['options_file'], 'dhcp-client/daemon-options.tmpl', self.options) - render(self.options['conf_file'], 'dhcp-client/ipv4.tmpl', self.options) + render(self._options_file, 'dhcp-client/daemon-options.tmpl', + self.options, trim_blocks=True) + render(self._conf_file, 'dhcp-client/ipv4.tmpl', + self.options, trim_blocks=True) return self._cmd('systemctl restart dhclient@{ifname}.service'.format(**self.options)) @@ -64,21 +65,20 @@ class _DHCPv4 (Control): pid, config and lease will be removed. Example: - >>> from vyos.ifconfig import Interface >>> j = Interface('eth0') >>> j.dhcp.v4.delete() """ - if not os.path.isfile(self.options['pid_file']): + if not os.path.isfile(self._pid_file): self._debug_msg('No DHCP client PID found') return None self._cmd('systemctl stop dhclient@{ifname}.service'.format(**self.options)) # cleanup old config files - for name in ('conf_file', 'options_file', 'pid_file', 'lease_file'): - if os.path.isfile(self.options[name]): - os.remove(self.options[name]) + for file in [self._conf_file, self._options_file, self._pid_file, self._lease_file]: + if os.path.isfile(file): + os.remove(file) class _DHCPv6 (Control): def __init__(self, ifname): @@ -101,7 +101,8 @@ class _DHCPv6 (Control): # missed it we are safe! verify_dhcpv6(self.options) - render(self._config, 'dhcp-client/ipv6.tmpl', self.options, trim_blocks=True) + render(self._config, 'dhcp-client/ipv6.tmpl', + self.options, trim_blocks=True) return self._cmd('systemctl restart dhcp6c@{ifname}.service'.format( **self.options)) @@ -111,7 +112,6 @@ class _DHCPv6 (Control): pid, config and lease will be removed. Example: - >>> from vyos.ifconfig import Interface >>> j = Interface('eth0') >>> j.dhcp.v6.delete() diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index f5e43e172..214ece8cd 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -824,15 +824,7 @@ class Interface(Control): # DHCP options if 'dhcp_options' in config: - dhcp_options = config.get('dhcp_options') - if 'client_id' in dhcp_options: - self.dhcp.v4.options['client_id'] = dhcp_options.get('client_id') - - if 'host_name' in dhcp_options: - self.dhcp.v4.options['hostname'] = dhcp_options.get('host_name') - - if 'vendor_class_id' in dhcp_options: - self.dhcp.v4.options['vendor_class_id'] = dhcp_options.get('vendor_class_id') + self.dhcp.v4.options = config # DHCPv6 options if 'dhcpv6_options' in config: -- cgit v1.2.3 From 787879baa0516ade8d9ad91f19e970c510d7aff3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 14:05:12 +0200 Subject: configure: op-mode: move XML here --- op-mode-definitions/configure.xml | 9 +++++++++ src/op_mode/enter_config_mode.sh | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 op-mode-definitions/configure.xml create mode 100755 src/op_mode/enter_config_mode.sh diff --git a/op-mode-definitions/configure.xml b/op-mode-definitions/configure.xml new file mode 100644 index 000000000..dc534d586 --- /dev/null +++ b/op-mode-definitions/configure.xml @@ -0,0 +1,9 @@ + + + + + Enter configure mode + + ${vyos_op_scripts_dir}/enter_config_mode.sh + + diff --git a/src/op_mode/enter_config_mode.sh b/src/op_mode/enter_config_mode.sh new file mode 100755 index 000000000..082800ce4 --- /dev/null +++ b/src/op_mode/enter_config_mode.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +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" + fi + history -w + export _OFR_CONFIGURE=ok + newgrp vyattacfg + unset _OFR_CONFIGURE + _vyatta_op_do_key_bindings + history -r +fi -- cgit v1.2.3 From 3e657ba2acf68c9adc1426cc1fcfeefb848cc8d2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 16:41:21 +0200 Subject: T2546: rename op-mode scripts for bandwidth --- op-mode-definitions/bandwidth-monitor.xml | 23 -------------------- op-mode-definitions/bandwidth-test.xml | 29 -------------------------- op-mode-definitions/monitor-bandwidth-test.xml | 29 ++++++++++++++++++++++++++ op-mode-definitions/monitor-bandwidth.xml | 23 ++++++++++++++++++++ 4 files changed, 52 insertions(+), 52 deletions(-) delete mode 100644 op-mode-definitions/bandwidth-monitor.xml delete mode 100644 op-mode-definitions/bandwidth-test.xml create mode 100644 op-mode-definitions/monitor-bandwidth-test.xml create mode 100644 op-mode-definitions/monitor-bandwidth.xml diff --git a/op-mode-definitions/bandwidth-monitor.xml b/op-mode-definitions/bandwidth-monitor.xml deleted file mode 100644 index 9af0a9e70..000000000 --- a/op-mode-definitions/bandwidth-monitor.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - Monitor interface bandwidth in real time - - - - bmon -b -p $4 - - Monitor bandwidth usage on specified interface - - - - - - - - - - diff --git a/op-mode-definitions/bandwidth-test.xml b/op-mode-definitions/bandwidth-test.xml deleted file mode 100644 index d1e459b17..000000000 --- a/op-mode-definitions/bandwidth-test.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - Initiate or wait for bandwidth test - - - - - Wait for bandwidth test connections (port TCP/5001) - - iperf -s - - - - Initiate a bandwidth test to specified host (port TCP/5001) - - <hostname> <x.x.x.x> <h:h:h:h:h:h:h:h> - - - iperf -c $4 - - - - - - diff --git a/op-mode-definitions/monitor-bandwidth-test.xml b/op-mode-definitions/monitor-bandwidth-test.xml new file mode 100644 index 000000000..d1e459b17 --- /dev/null +++ b/op-mode-definitions/monitor-bandwidth-test.xml @@ -0,0 +1,29 @@ + + + + + + + Initiate or wait for bandwidth test + + + + + Wait for bandwidth test connections (port TCP/5001) + + iperf -s + + + + Initiate a bandwidth test to specified host (port TCP/5001) + + <hostname> <x.x.x.x> <h:h:h:h:h:h:h:h> + + + iperf -c $4 + + + + + + diff --git a/op-mode-definitions/monitor-bandwidth.xml b/op-mode-definitions/monitor-bandwidth.xml new file mode 100644 index 000000000..9af0a9e70 --- /dev/null +++ b/op-mode-definitions/monitor-bandwidth.xml @@ -0,0 +1,23 @@ + + + + + + + Monitor interface bandwidth in real time + + + + bmon -b -p $4 + + Monitor bandwidth usage on specified interface + + + + + + + + + + -- cgit v1.2.3 From ee45fcf5f5342d40b5dcd39429a4201c93a49b4c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 17:08:47 +0200 Subject: lldp: op-mode: T2323: "show lldp neighbors detail" only works when service runs The problem exists when LLDP is not configured but one wants to run the detail command. Running "show lldp neighbors" is not possible when LLDP is not running. This case is already handled for "show lldp neighbors" and has been extended for "show lldp neighbors detail". --- op-mode-definitions/lldp.xml | 2 +- src/op_mode/lldp_op.py | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/op-mode-definitions/lldp.xml b/op-mode-definitions/lldp.xml index 105bfe237..e954fb8cf 100644 --- a/op-mode-definitions/lldp.xml +++ b/op-mode-definitions/lldp.xml @@ -17,7 +17,7 @@ Show LLDP neighbor details - /usr/sbin/lldpctl -f plain + ${vyos_op_scripts_dir}/lldp_op.py --detail diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py index 5d48e3210..0df6749aa 100755 --- a/src/op_mode/lldp_op.py +++ b/src/op_mode/lldp_op.py @@ -14,19 +14,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - import argparse import jinja2 -from xml.dom import minidom from sys import exit from tabulate import tabulate +from xml.dom import minidom -from vyos.util import popen +from vyos.util import cmd from vyos.config import Config parser = argparse.ArgumentParser() parser.add_argument("-a", "--all", action="store_true", help="Show LLDP neighbors on all interfaces") +parser.add_argument("-d", "--detail", action="store_true", help="Show detailes LLDP neighbor information on all interfaces") parser.add_argument("-i", "--interface", action="store", help="Show LLDP neighbors on specific interface") # Please be careful if you edit the template. @@ -40,10 +40,8 @@ Device ID Local Proto Cap Platform Port ID {% endfor -%} """ -def _get_neighbors(): - command = '/usr/sbin/lldpcli -f xml show neighbors' - out,_ = popen(command) - return out +def get_neighbors(): + return cmd('/usr/sbin/lldpcli -f xml show neighbors') def extract_neighbor(neighbor): """ @@ -148,12 +146,17 @@ if __name__ == '__main__': exit(0) if args.all: - neighbors = minidom.parseString(_get_neighbors()) + neighbors = minidom.parseString(get_neighbors()) for neighbor in neighbors.getElementsByTagName('interface'): tmp['neighbors'].append( extract_neighbor(neighbor) ) + elif args.detail: + out = cmd('/usr/sbin/lldpctl -f plain') + print(out) + exit(0) + elif args.interface: - neighbors = minidom.parseString(_get_neighbors()) + neighbors = minidom.parseString(get_neighbors()) for neighbor in neighbors.getElementsByTagName('interface'): # check if neighbor appeared on proper interface if neighbor.getAttribute('name') == args.interface: -- cgit v1.2.3 From 43a7f4e8032e5285f3dd99785f1b41e3c2dd1ad0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 17:10:41 +0200 Subject: lldp: T393: improve all completion helper --- interface-definitions/lldp.xml.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface-definitions/lldp.xml.in b/interface-definitions/lldp.xml.in index 3a2899b57..8f6629d81 100644 --- a/interface-definitions/lldp.xml.in +++ b/interface-definitions/lldp.xml.in @@ -21,6 +21,7 @@ + all @@ -73,7 +74,7 @@ Datum should be WGS84, NAD83, or MLLW - (WGS84|NAD83|MLLW)$ + ^(WGS84|NAD83|MLLW)$ -- cgit v1.2.3 From e9eeaeb0e974e90168b0ce87b4f5553132af01a2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 17:16:22 +0200 Subject: op-mode: T2546: add "monitor log" command --- op-mode-definitions/monitor-log.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 op-mode-definitions/monitor-log.xml diff --git a/op-mode-definitions/monitor-log.xml b/op-mode-definitions/monitor-log.xml new file mode 100644 index 000000000..99efe5306 --- /dev/null +++ b/op-mode-definitions/monitor-log.xml @@ -0,0 +1,13 @@ + + + + + + + Monitor last lines of messages file + + tail --follow=name /var/log/messages + + + + -- cgit v1.2.3 From 6539f908e17c17a4a931e9638af9487e341bdcae Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 18:00:12 +0200 Subject: op-mode: ping: T1600: initial XML implementation --- Makefile | 4 ++++ op-mode-definitions/ping.xml | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 op-mode-definitions/ping.xml diff --git a/Makefile b/Makefile index 2773642d0..5b7e4da63 100644 --- a/Makefile +++ b/Makefile @@ -105,6 +105,10 @@ op_mode_definitions: rm -f $(OP_TMPL_DIR)/delete/node.def rm -f $(OP_TMPL_DIR)/reset/vpn/node.def + # XXX: ping must be able to recursivly call itself as the + # options are provided from the script itself + ln -s ../node.tag $(OP_TMPL_DIR)/ping/node.tag/node.tag/ + .PHONY: component_versions .ONESHELL: component_versions: $(BUILD_DIR) $(obj) diff --git a/op-mode-definitions/ping.xml b/op-mode-definitions/ping.xml new file mode 100644 index 000000000..4c25a59ab --- /dev/null +++ b/op-mode-definitions/ping.xml @@ -0,0 +1,23 @@ + + + + + Send Internet Control Message Protocol (ICMP) echo request + + <hostname> <x.x.x.x> <h:h:h:h:h:h:h:h> + + + ${vyos_op_scripts_dir}/ping.py ${@:2} + + + + Ping options + + + + + ${vyos_op_scripts_dir}/ping.py ${@:2} + + + + -- cgit v1.2.3 From ca2ab503f42a8446175954e9e7280ecc8e75e927 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 18:38:55 +0200 Subject: ssh: T1076: make configuration volatile Move sshd_config file to /run so it must be generated on every boot and is not stored accidently. --- data/templates/ssh/override.conf.tmpl | 3 ++- src/conf_mode/ssh.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/data/templates/ssh/override.conf.tmpl b/data/templates/ssh/override.conf.tmpl index 4276366ae..843aa927b 100644 --- a/data/templates/ssh/override.conf.tmpl +++ b/data/templates/ssh/override.conf.tmpl @@ -2,9 +2,10 @@ [Unit] StartLimitIntervalSec=0 After=vyos-router.service +ConditionPathExists={{config_file}} [Service] ExecStart= -ExecStart={{vrf_command}}/usr/sbin/sshd -D $SSHD_OPTS +ExecStart={{vrf_command}}/usr/sbin/sshd -f {{config_file}} -D $SSHD_OPTS RestartSec=10 diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index ffb0b700d..7b262565a 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -28,7 +28,7 @@ from vyos.xml import defaults from vyos import airbag airbag.enable() -config_file = r'/etc/ssh/sshd_config' +config_file = r'/run/ssh/sshd_config' systemd_override = r'/etc/systemd/system/ssh.service.d/override.conf' def get_config(): @@ -42,6 +42,8 @@ def get_config(): # options which we need to update into the dictionary retrived. default_values = defaults(base) ssh = dict_merge(default_values, ssh) + # pass config file path - used in override template + ssh['config_file'] = config_file return ssh -- cgit v1.2.3 From 6301c560df544ddac9838d0489941d84748cced3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 19:10:10 +0200 Subject: ipv6-tool: op-mode: T2724: use full path to binary and quote arguments --- op-mode-definitions/force-ipv6-nd.xml | 8 ++++---- op-mode-definitions/force-ipv6-rd.xml | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/op-mode-definitions/force-ipv6-nd.xml b/op-mode-definitions/force-ipv6-nd.xml index 64c8347ea..49de097f6 100644 --- a/op-mode-definitions/force-ipv6-nd.xml +++ b/op-mode-definitions/force-ipv6-nd.xml @@ -4,12 +4,12 @@ - ICMPv6 Neighbor Discovery tool + IPv6 Neighbor Discovery - IPv6 Neighbor Discovery on a specific interface + IPv6 Neighbor Discovery on specified interface @@ -17,12 +17,12 @@ - The IP address of the target + IPv6 address of node to lookup <h:h:h:h:h:h:h:h> - ndisc6 $6 $4 -m + /usr/bin/ndisc6 -m "$6" "$4" diff --git a/op-mode-definitions/force-ipv6-rd.xml b/op-mode-definitions/force-ipv6-rd.xml index 722cb67bf..8c901af25 100644 --- a/op-mode-definitions/force-ipv6-rd.xml +++ b/op-mode-definitions/force-ipv6-rd.xml @@ -4,26 +4,26 @@ - IPv6 Router Discovery tool + IPv6 Router Discovery - IPv6 Router Discovery tool on a specific interface + IPv6 Router Discovery on specified interface - rdisc6 $4 + /usr/bin/rdisc6 "$4" - The IP address of the target + IPv6 address of target <h:h:h:h:h:h:h:h> - rdisc6 $6 $4 -m + /usr/bin/rdisc6 -m "$6" "$4" -- cgit v1.2.3 From 6cdcbd0aae79a42e32c2f611083f287b80564930 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 20:42:28 +0200 Subject: ssh: config file is now volatile (moved to /run) --- scripts/cli/test_service_ssh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cli/test_service_ssh.py b/scripts/cli/test_service_ssh.py index ad56fb53b..3ee498f3d 100755 --- a/scripts/cli/test_service_ssh.py +++ b/scripts/cli/test_service_ssh.py @@ -22,7 +22,7 @@ from psutil import process_iter from vyos.configsession import ConfigSession, ConfigSessionError from vyos.util import read_file -SSHD_CONF = '/etc/ssh/sshd_config' +SSHD_CONF = '/run/ssh/sshd_config' base_path = ['service', 'ssh'] def get_config_value(key): -- cgit v1.2.3 From 9e6cd018464fcf03a6930116ff5570f68a7c6c45 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 3 Aug 2020 21:18:25 +0200 Subject: op-mode: T2546: use inline shell code for "configure" Commit 787879ba ("configure: op-mode: move XML here") migrated the op-mode command "configure" to vyos-1x. The used shell snipped in turn sometimes complained about: "_vyatta_op_do_key_bindings: command not found". This is now resolved by moving the code entirely into the node.def file. --- op-mode-definitions/configure.xml | 19 +++++++++++++++++-- src/op_mode/enter_config_mode.sh | 18 ------------------ 2 files changed, 17 insertions(+), 20 deletions(-) delete mode 100755 src/op_mode/enter_config_mode.sh diff --git a/op-mode-definitions/configure.xml b/op-mode-definitions/configure.xml index dc534d586..3dd5a0f45 100644 --- a/op-mode-definitions/configure.xml +++ b/op-mode-definitions/configure.xml @@ -2,8 +2,23 @@ - Enter configure mode + Enter configuration mode - ${vyos_op_scripts_dir}/enter_config_mode.sh + 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" + fi + history -w + export _OFR_CONFIGURE=ok + newgrp vyattacfg + unset _OFR_CONFIGURE + _vyatta_op_do_key_bindings + history -r + fi diff --git a/src/op_mode/enter_config_mode.sh b/src/op_mode/enter_config_mode.sh deleted file mode 100755 index 082800ce4..000000000 --- a/src/op_mode/enter_config_mode.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -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" - fi - history -w - export _OFR_CONFIGURE=ok - newgrp vyattacfg - unset _OFR_CONFIGURE - _vyatta_op_do_key_bindings - history -r -fi -- cgit v1.2.3 From f5ecc5a40fcf07d9ad08864fbac710717330c255 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 4 Aug 2020 08:29:38 +0200 Subject: dhcpv6-pd: T2741: support delegation on non existing interfaces We must ignore any return code when invoking dhcpc6 initially. This is required to enable DHCPv6-PD for interfaces which are yet not up and running and my be started later by VyOS. --- python/vyos/ifconfig/dhcp.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py index 5f99a0b7e..63224fc0f 100644 --- a/python/vyos/ifconfig/dhcp.py +++ b/python/vyos/ifconfig/dhcp.py @@ -103,7 +103,10 @@ class _DHCPv6 (Control): render(self._config, 'dhcp-client/ipv6.tmpl', self.options, trim_blocks=True) - return self._cmd('systemctl restart dhcp6c@{ifname}.service'.format( + + # 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('systemctl restart dhcp6c@{ifname}.service'.format( **self.options)) def delete(self): -- cgit v1.2.3 From 4526ceadf4a9a81b51ec394970188daccd69539b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 4 Aug 2020 08:34:52 +0200 Subject: cleanup: convert multiple files from DOS to UNIX line ending --- data/templates/frr/bgp.frr.tmpl | 2 +- data/templates/system/curlrc.tmpl | 16 +++++----- .../include/bgp-afi-aggregate-address.xml.i | 24 +++++++-------- .../systemd/system/radvd.service.d/override.conf | 34 +++++++++++----------- src/systemd/wpa_supplicant-macsec@.service | 34 +++++++++++----------- 5 files changed, 55 insertions(+), 55 deletions(-) diff --git a/data/templates/frr/bgp.frr.tmpl b/data/templates/frr/bgp.frr.tmpl index cd6f31c93..cdf4cb4fe 100644 --- a/data/templates/frr/bgp.frr.tmpl +++ b/data/templates/frr/bgp.frr.tmpl @@ -1 +1 @@ -! +! diff --git a/data/templates/system/curlrc.tmpl b/data/templates/system/curlrc.tmpl index 675e35a0c..3e5ce801c 100644 --- a/data/templates/system/curlrc.tmpl +++ b/data/templates/system/curlrc.tmpl @@ -1,8 +1,8 @@ -{% if http_client is defined %} -{% if http_client.source_interface is defined %} ---interface "{{ http_client.source_interface }}" -{% endif %} -{% if http_client.source_address is defined %} ---interface "{{ http_client.source_address }}" -{% endif %} -{% endif %} +{% if http_client is defined %} +{% if http_client.source_interface is defined %} +--interface "{{ http_client.source_interface }}" +{% endif %} +{% if http_client.source_address is defined %} +--interface "{{ http_client.source_address }}" +{% endif %} +{% endif %} diff --git a/interface-definitions/include/bgp-afi-aggregate-address.xml.i b/interface-definitions/include/bgp-afi-aggregate-address.xml.i index 40c030fc1..050ee0074 100644 --- a/interface-definitions/include/bgp-afi-aggregate-address.xml.i +++ b/interface-definitions/include/bgp-afi-aggregate-address.xml.i @@ -1,12 +1,12 @@ - - - Generate AS-set path information for this aggregate address - - - - - - Announce the aggregate summary network only - - - + + + Generate AS-set path information for this aggregate address + + + + + + Announce the aggregate summary network only + + + diff --git a/src/etc/systemd/system/radvd.service.d/override.conf b/src/etc/systemd/system/radvd.service.d/override.conf index 44c4345e1..c2f640cf5 100644 --- a/src/etc/systemd/system/radvd.service.d/override.conf +++ b/src/etc/systemd/system/radvd.service.d/override.conf @@ -1,17 +1,17 @@ -[Unit] -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 +[Unit] +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 diff --git a/src/systemd/wpa_supplicant-macsec@.service b/src/systemd/wpa_supplicant-macsec@.service index 21e189e4b..7e0bee8e1 100644 --- a/src/systemd/wpa_supplicant-macsec@.service +++ b/src/systemd/wpa_supplicant-macsec@.service @@ -1,17 +1,17 @@ -[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 - -# NetworkManager users will probably want the dbus version instead. - -[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 -i%I - -[Install] -WantedBy=multi-user.target +[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 + +# NetworkManager users will probably want the dbus version instead. + +[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 -i%I + +[Install] +WantedBy=multi-user.target -- cgit v1.2.3 From 3658058c6299a107dd5ce5973a9ec5e4be7064b0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 4 Aug 2020 10:12:21 +0200 Subject: router-advert: T2758: bugfix 'infinity' is not a valid integer number Problem was introduced in commit 740ace13 ("router-advert: T2609: add missing verify() for prefix lifetime"). The 'infinity' literal was not converted to its corresponding integer 0xffffffff (4294967295) for the check. --- src/conf_mode/service_router-advert.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index 1b333e1a5..4e1c432ab 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -73,7 +73,15 @@ def verify(rtradv): if 'prefix' in interface: for prefix in interface['prefix']: prefix = interface['prefix'][prefix] - if not (int(prefix['valid_lifetime']) > int(prefix['preferred_lifetime'])): + valid_lifetime = prefix['valid_lifetime'] + if valid_lifetime == 'infinity': + valid_lifetime = 4294967295 + + preferred_lifetime = prefix['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 preferred-lifetime') return None -- cgit v1.2.3 From 846e306700af191d22dc874992bbf5f04d2799c4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 4 Aug 2020 22:33:18 +0200 Subject: ssh: T2651: add cli options for source address When running SSH from the VyOS system the source IP address can be set by: set system options ssh-client source-address x.x.x.x --- data/templates/system/ssh_config.tmpl | 3 +++ interface-definitions/system-options.xml.in | 10 +++++++++- src/conf_mode/system-options.py | 31 ++++++++++++++++++++++++----- 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 data/templates/system/ssh_config.tmpl diff --git a/data/templates/system/ssh_config.tmpl b/data/templates/system/ssh_config.tmpl new file mode 100644 index 000000000..509bd5479 --- /dev/null +++ b/data/templates/system/ssh_config.tmpl @@ -0,0 +1,3 @@ +{% if ssh_client is defined and ssh_client.source_address is defined and ssh_client.source_address is not none %} +BindAddress {{ ssh_client.source_address }} +{% endif %} diff --git a/interface-definitions/system-options.xml.in b/interface-definitions/system-options.xml.in index 194773329..a5fec10db 100644 --- a/interface-definitions/system-options.xml.in +++ b/interface-definitions/system-options.xml.in @@ -46,13 +46,21 @@ - Global options used for HTTP based commands + Global options used for HTTP client #include #include + + + Global options used for SSH client + + + #include + + diff --git a/src/conf_mode/system-options.py b/src/conf_mode/system-options.py index d7c5c0443..0aacd19d8 100755 --- a/src/conf_mode/system-options.py +++ b/src/conf_mode/system-options.py @@ -22,11 +22,13 @@ from sys import exit from vyos.config import Config from vyos.template import render from vyos.util import call +from vyos.validate import is_addr_assigned from vyos import ConfigError from vyos import airbag airbag.enable() -config_file = r'/etc/curlrc' +curlrc_config = r'/etc/curlrc' +ssh_config = r'/etc/ssh/ssh_config' systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target' def get_config(): @@ -36,9 +38,9 @@ def get_config(): return options def verify(options): - if 'http_client' in options.keys(): + if 'http_client' in options: config = options['http_client'] - if 'source_interface' in config.keys(): + if 'source_interface' in config: if not config['source_interface'] in interfaces(): raise ConfigError(f'Source interface {source_interface} does not ' f'exist'.format(**config)) @@ -46,10 +48,21 @@ def verify(options): 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: + if not is_addr_assigned(config['source_address']): + raise ConfigError('No interface with give address specified!') + return None def generate(options): - render(config_file, 'system/curlrc.tmpl', options, trim_blocks=True) + render(curlrc_config, 'system/curlrc.tmpl', options, trim_blocks=True) + render(ssh_config, 'system/ssh_config.tmpl', options, trim_blocks=True) return None def apply(options): @@ -63,12 +76,20 @@ def apply(options): if os.path.exists(systemd_action_file): os.unlink(systemd_action_file) - if 'ctrl_alt_del_action' in options.keys(): + if 'ctrl_alt_del_action' in options: if options['ctrl_alt_del_action'] == 'reboot': os.symlink('/lib/systemd/system/reboot.target', systemd_action_file) elif options['ctrl_alt_del_action'] == 'poweroff': os.symlink('/lib/systemd/system/poweroff.target', systemd_action_file) + if 'http_client' not in options: + if os.path.exists(curlrc_config): + os.unlink(curlrc_config) + + if 'ssh_client' not in options: + if os.path.exists(ssh_config): + os.unlink(ssh_config) + # Reboot system on kernel panic with open('/proc/sys/kernel/panic', 'w') as f: if 'reboot_on_panic' in options.keys(): -- cgit v1.2.3 From 743627b9df6f7e8e34b544270ece4bc64a90b438 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 4 Aug 2020 22:40:53 +0200 Subject: router-advert: check 'infinity' option in script logic --- scripts/cli/test_service_router-advert.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/cli/test_service_router-advert.py b/scripts/cli/test_service_router-advert.py index cc251e355..ec2110c8a 100755 --- a/scripts/cli/test_service_router-advert.py +++ b/scripts/cli/test_service_router-advert.py @@ -47,6 +47,7 @@ class TestServiceRADVD(unittest.TestCase): def test_single(self): self.session.set(base_path + ['prefix', '::/64', 'no-on-link-flag']) self.session.set(base_path + ['prefix', '::/64', 'no-autonomous-flag']) + self.session.set(base_path + ['prefix', '::/64', 'valid-lifetime', 'infinity']) self.session.set(base_path + ['dnssl', '2001:db8::1234']) self.session.set(base_path + ['other-config-flag']) @@ -80,7 +81,7 @@ class TestServiceRADVD(unittest.TestCase): # this is a default value tmp = get_config_value('AdvValidLifetime') - self.assertEqual(tmp, '2592000') + self.assertEqual(tmp, 'infinity') # this is a default value tmp = get_config_value('AdvPreferredLifetime') -- cgit v1.2.3 From dbc682e40a0b3c93e3005d6b3d4e4c34965bba7d Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 6 Aug 2020 17:55:10 +0200 Subject: nat: T2764: increase maximum rule number to 999999 --- interface-definitions/include/nat-rule.xml.i | 6 +++--- interface-definitions/nat.xml.in | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/interface-definitions/include/nat-rule.xml.i b/interface-definitions/include/nat-rule.xml.i index f62a08987..a2d058479 100644 --- a/interface-definitions/include/nat-rule.xml.i +++ b/interface-definitions/include/nat-rule.xml.i @@ -2,13 +2,13 @@ Rule number for NAT - 1-9999 + 1-999999 Number for this NAT rule - + - NAT rule number must be between 1 and 9999 + NAT rule number must be between 1 and 999999 diff --git a/interface-definitions/nat.xml.in b/interface-definitions/nat.xml.in index f8415b7c0..8a14f4d25 100644 --- a/interface-definitions/nat.xml.in +++ b/interface-definitions/nat.xml.in @@ -61,13 +61,13 @@ NPTv6 rule number - 1-9999 + 1-999999 Number for this rule - + - NAT rule number must be between 1 and 9999 + NAT rule number must be between 1 and 999999 -- cgit v1.2.3 From 98e5105de4390780cf12494c59a06aae5433a67b Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sat, 8 Aug 2020 16:45:17 -0500 Subject: http api: T1431: update args of call to install-image commit 3f8884587 added an endpoint for image management; T2753 updated the command line options. Make script call consistent. --- python/vyos/configsession.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index f2524b37e..0994fd974 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -26,7 +26,7 @@ DISCARD = '/opt/vyatta/sbin/my_discard' SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig'] LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile'] SAVE_CONFIG = ['/opt/vyatta/sbin/vyatta-save-config.pl'] -INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image'] +INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image', '--url'] REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del'] GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate'] SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show'] -- cgit v1.2.3 From 909152a69e0844ad0cdd2b05467cd7fe379a5347 Mon Sep 17 00:00:00 2001 From: jack9603301 Date: Mon, 10 Aug 2020 21:40:57 +0800 Subject: tcptraceroute: T2723: Fix the problem that tcptraceroute6 cannot be executed --- debian/control | 3 ++- op-mode-definitions/traceroute.xml | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/debian/control b/debian/control index 3a441b47b..34ba4be8e 100644 --- a/debian/control +++ b/debian/control @@ -106,7 +106,8 @@ Depends: python3, conntrack, libatomic1, fastnetmon, - libndp-tools + libndp-tools, + tcptraceroute Description: VyOS configuration scripts and data VyOS configuration scripts, interface definitions, and everything diff --git a/op-mode-definitions/traceroute.xml b/op-mode-definitions/traceroute.xml index efbaaeb2f..b562eb2e6 100644 --- a/op-mode-definitions/traceroute.xml +++ b/op-mode-definitions/traceroute.xml @@ -26,7 +26,7 @@ /usr/bin/traceroute -4 "$3" - + Route tracing and port detection using TCP @@ -38,10 +38,10 @@ 0-65535 - sudo /usr/sbin/tcptraceroute "$3" $6 + sudo /usr/bin/tcptraceroute "$3" $6 - + @@ -53,7 +53,7 @@ /usr/bin/traceroute -6 "$3" - + Use TCP/IPv6 packets to perform a traceroute @@ -65,10 +65,10 @@ 0-65535 - sudo /usr/sbin/tcptraceroute6 "$3" $6 + sudo /usr/bin/tcptraceroute6 "$3" $6 - + -- cgit v1.2.3 From b39c21af1de08f87cc5c9131ae4f9afc4e9759a5 Mon Sep 17 00:00:00 2001 From: jack9603301 Date: Mon, 10 Aug 2020 21:41:43 +0800 Subject: tcptraceroute: T2723: Support VRF --- op-mode-definitions/traceroute.xml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/op-mode-definitions/traceroute.xml b/op-mode-definitions/traceroute.xml index b562eb2e6..640f0b90c 100644 --- a/op-mode-definitions/traceroute.xml +++ b/op-mode-definitions/traceroute.xml @@ -98,6 +98,24 @@ sudo /usr/sbin/ip vrf exec "$3" /usr/bin/traceroute -4 "$5" + + + + Route tracing and port detection using TCP + + + + + Perform operations on TCP ports + + 0-65535 + + + sudo /usr/sbin/ip vrf exec "$3" /usr/bin/tcptraceroute "$5" $8 + + + + @@ -107,6 +125,24 @@ sudo /usr/sbin/ip vrf exec "$3" /usr/bin/traceroute -6 "$5" + + + + Use TCP/IPv6 packets to perform a traceroute + + + + + Perform operations on TCP ports + + 0-65535 + + + sudo /usr/sbin/ip vrf exec "$3" /usr/bin/tcptraceroute6 "$5" $8 + + + + -- cgit v1.2.3 From 0393bbebeda9d8a610accf15377a3487b4fc1ddc Mon Sep 17 00:00:00 2001 From: jack9603301 Date: Mon, 10 Aug 2020 21:45:58 +0800 Subject: tcptraceroute: T2723: Improve command line options, support execution without port Because the ports of tcptraceroute and tcptraceroute6 do not need to be specified in this feature, modify the command line syntax --- op-mode-definitions/traceroute.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/op-mode-definitions/traceroute.xml b/op-mode-definitions/traceroute.xml index 640f0b90c..10ee3c07e 100644 --- a/op-mode-definitions/traceroute.xml +++ b/op-mode-definitions/traceroute.xml @@ -30,6 +30,7 @@ Route tracing and port detection using TCP + sudo /usr/bin/tcptraceroute "$3" @@ -57,6 +58,7 @@ Use TCP/IPv6 packets to perform a traceroute + sudo /usr/bin/tcptraceroute6 "$3" @@ -103,6 +105,7 @@ Route tracing and port detection using TCP + sudo /usr/sbin/ip vrf exec "$3" /usr/bin/tcptraceroute "$5" @@ -130,6 +133,7 @@ Use TCP/IPv6 packets to perform a traceroute + sudo /usr/sbin/ip vrf exec "$3" /usr/bin/tcptraceroute6 "$5" -- cgit v1.2.3 From 81e3f35cfbe860f9c45f109b66aeb7b826cad865 Mon Sep 17 00:00:00 2001 From: sever-sever Date: Mon, 10 Aug 2020 17:06:57 +0000 Subject: op-mode:T2779:Fix lldp neigh interface ethX --- op-mode-definitions/lldp.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-mode-definitions/lldp.xml b/op-mode-definitions/lldp.xml index e954fb8cf..297ccf1f4 100644 --- a/op-mode-definitions/lldp.xml +++ b/op-mode-definitions/lldp.xml @@ -26,7 +26,7 @@ - ${vyos_op_scripts_dir}/lldp_op.py --interface $4 + ${vyos_op_scripts_dir}/lldp_op.py --interface $5 -- cgit v1.2.3 From f8112b64a9ddfddf1c24a11ba3db236a15df14e6 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 10 Aug 2020 18:58:27 -0500 Subject: host_name: T2784: remove unused arg in verify, unneeded arg in get_config --- src/conf_mode/host_name.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index f2fa64233..65c981d6a 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -47,7 +47,9 @@ default_config_data = { hostsd_tag = 'system' -def get_config(conf): +def get_config(): + conf = Config() + hosts = copy.deepcopy(default_config_data) hosts['hostname'] = conf.return_value("system host-name") @@ -77,7 +79,7 @@ def get_config(conf): return hosts -def verify(conf, hosts): +def verify(hosts): if hosts is None: return None @@ -168,9 +170,8 @@ def apply(config): if __name__ == '__main__': try: - conf = Config() - c = get_config(conf) - verify(conf, c) + c = get_config() + verify(c) generate(c) apply(c) except ConfigError as e: -- cgit v1.2.3 From bcbc7c3eaa833d76feedf5676ef85df69cc506c1 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 10 Aug 2020 19:00:51 -0500 Subject: host_name: T2784: remove unused imports --- src/conf_mode/host_name.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 65c981d6a..9d66bd434 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -18,20 +18,16 @@ conf-mode script for 'system host-name' and 'system domain-name'. """ -import os import re import sys import copy -import glob -import argparse -import jinja2 import vyos.util import vyos.hostsd_client from vyos.config import Config from vyos import ConfigError -from vyos.util import cmd, call, run, process_named_running +from vyos.util import cmd, call, process_named_running from vyos import airbag airbag.enable() -- cgit v1.2.3 From 399bc459b94d4b918364d1e6b168d1842c8445b5 Mon Sep 17 00:00:00 2001 From: jack9603301 Date: Tue, 11 Aug 2020 11:45:42 +0800 Subject: tcptraceroute: T2723: Modify port description In response to #522 request, modify `Perform operations on TCP ports` to `TCP port to connect to for path tracing` --- op-mode-definitions/traceroute.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/op-mode-definitions/traceroute.xml b/op-mode-definitions/traceroute.xml index 10ee3c07e..6a44fc25b 100644 --- a/op-mode-definitions/traceroute.xml +++ b/op-mode-definitions/traceroute.xml @@ -34,7 +34,7 @@ - Perform operations on TCP ports + TCP port to connect to for path tracing 0-65535 @@ -62,7 +62,7 @@ - Perform operations on TCP ports + TCP port to connect to for path tracing 0-65535 @@ -109,7 +109,7 @@ - Perform operations on TCP ports + TCP port to connect to for path tracing 0-65535 @@ -137,7 +137,7 @@ - Perform operations on TCP ports + TCP port to connect to for path tracing 0-65535 -- cgit v1.2.3 From 21bc98f16110d0fa2d7d93409252da73f58878ed Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 12 Aug 2020 23:01:40 +0200 Subject: ifconfig: dhcp: T2767: client must not start when interface is disabled ISC DHCP client will always place an Interface in admin-up state once it is started. We must ensure that if an interface is placed in A/D state that the DHCP client proccess is not launched and terminated if it is running. --- python/vyos/ifconfig/dhcp.py | 131 -------------------------------------- python/vyos/ifconfig/interface.py | 95 ++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 147 deletions(-) delete mode 100644 python/vyos/ifconfig/dhcp.py diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py deleted file mode 100644 index 63224fc0f..000000000 --- a/python/vyos/ifconfig/dhcp.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright 2020 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 . - -import os -import jmespath - -from vyos.configdict import dict_merge -from vyos.configverify import verify_dhcpv6 -from vyos.ifconfig.control import Control -from vyos.template import render - -class _DHCPv4 (Control): - def __init__(self, ifname): - super().__init__() - config_base = r'/var/lib/dhcp/dhclient' - self._conf_file = f'{config_base}_{ifname}.conf' - self._options_file = f'{config_base}_{ifname}.options' - self._pid_file = f'{config_base}_{ifname}.pid' - self._lease_file = f'{config_base}_{ifname}.leases' - self.options = {'ifname' : ifname} - - # replace dhcpv4/v6 with systemd.networkd? - def set(self): - """ - Configure interface as DHCP client. The dhclient binary is automatically - started in background! - - Example: - >>> from vyos.ifconfig import Interface - >>> j = Interface('eth0') - >>> j.dhcp.v4.set() - """ - - if jmespath.search('dhcp_options.host_name', self.options) == None: - # read configured system hostname. - # maybe change to vyos hostd client ??? - hostname = 'vyos' - with open('/etc/hostname', 'r') as f: - hostname = f.read().rstrip('\n') - tmp = {'dhcp_options' : { 'host_name' : hostname}} - self.options = dict_merge(tmp, self.options) - - render(self._options_file, 'dhcp-client/daemon-options.tmpl', - self.options, trim_blocks=True) - render(self._conf_file, 'dhcp-client/ipv4.tmpl', - self.options, trim_blocks=True) - - return self._cmd('systemctl restart dhclient@{ifname}.service'.format(**self.options)) - - def delete(self): - """ - De-configure interface as DHCP clinet. All auto generated files like - pid, config and lease will be removed. - - Example: - >>> from vyos.ifconfig import Interface - >>> j = Interface('eth0') - >>> j.dhcp.v4.delete() - """ - if not os.path.isfile(self._pid_file): - self._debug_msg('No DHCP client PID found') - return None - - self._cmd('systemctl stop dhclient@{ifname}.service'.format(**self.options)) - - # cleanup old config files - for file in [self._conf_file, self._options_file, self._pid_file, self._lease_file]: - if os.path.isfile(file): - os.remove(file) - -class _DHCPv6 (Control): - def __init__(self, ifname): - super().__init__() - self.options = {'ifname' : ifname} - self._config = f'/run/dhcp6c/dhcp6c.{ifname}.conf' - - def set(self): - """ - Configure interface as DHCPv6 client. The client is automatically - started in background when address is configured as DHCP. - - Example: - >>> from vyos.ifconfig import Interface - >>> j = Interface('eth0') - >>> j.dhcp.v6.set() - """ - - # better save then sorry .. should be checked in interface script but if you - # missed it we are safe! - verify_dhcpv6(self.options) - - render(self._config, 'dhcp-client/ipv6.tmpl', - self.options, trim_blocks=True) - - # 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('systemctl restart dhcp6c@{ifname}.service'.format( - **self.options)) - - def delete(self): - """ - De-configure interface as DHCPv6 client. All auto generated files like - pid, config and lease will be removed. - - Example: - >>> from vyos.ifconfig import Interface - >>> j = Interface('eth0') - >>> j.dhcp.v6.delete() - """ - self._cmd('systemctl stop dhcp6c@{ifname}.service'.format(**self.options)) - - # cleanup old config files - if os.path.isfile(self._config): - os.remove(self._config) - -class DHCP(object): - def __init__(self, ifname): - self.v4 = _DHCPv4(ifname) - self.v6 = _DHCPv6(ifname) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 214ece8cd..36f258301 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -31,6 +31,8 @@ from netifaces import AF_INET6 from vyos import ConfigError from vyos.configdict import list_diff +from vyos.configdict import dict_merge +from vyos.template import render from vyos.util import mac2eui64 from vyos.validate import is_ipv4 from vyos.validate import is_ipv6 @@ -43,7 +45,6 @@ from vyos.validate import assert_positive from vyos.validate import assert_range from vyos.ifconfig.control import Control -from vyos.ifconfig.dhcp import DHCP from vyos.ifconfig.vrrp import VRRP from vyos.ifconfig.operational import Operational from vyos.ifconfig import Section @@ -216,7 +217,6 @@ class Interface(Control): # we must have updated config before initialising the Interface super().__init__(**kargs) self.ifname = ifname - self.dhcp = DHCP(ifname) if not self.exists(ifname): # Any instance of Interface, such as Interface('eth0') @@ -722,9 +722,9 @@ class Interface(Control): # add to interface if addr == 'dhcp': - self.dhcp.v4.set() + self.set_dhcp(True) elif addr == 'dhcpv6': - self.dhcp.v6.set() + self.set_dhcpv6(True) elif not is_intf_addr_assigned(self.ifname, addr): self._cmd(f'ip addr add "{addr}" ' f'{"brd + " if addr_is_v4 else ""}dev "{self.ifname}"') @@ -763,9 +763,9 @@ class Interface(Control): # remove from interface if addr == 'dhcp': - self.dhcp.v4.delete() + self.set_dhcp(False) elif addr == 'dhcpv6': - self.dhcp.v6.delete() + self.set_dhcpv6(False) elif is_intf_addr_assigned(self.ifname, addr): self._cmd(f'ip addr del "{addr}" dev "{self.ifname}"') else: @@ -784,8 +784,8 @@ class Interface(Control): Will raise an exception on error. """ # stop DHCP(v6) if running - self.dhcp.v4.delete() - self.dhcp.v6.delete() + self.set_dhcp(False) + self.set_dhcpv6(False) # flush all addresses self._cmd(f'ip addr flush dev "{self.ifname}"') @@ -809,12 +809,83 @@ class Interface(Control): return True + 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 = r'/var/lib/dhcp/dhclient' + config_file = f'{config_base}_{ifname}.conf' + options_file = f'{config_base}_{ifname}.options' + pid_file = f'{config_base}_{ifname}.pid' + lease_file = f'{config_base}_{ifname}.leases' + + if enable and 'disable' not in self._config: + if jmespath.search('dhcp_options.host_name', self._config) == None: + # read configured system hostname. + # maybe change to vyos hostd client ??? + hostname = 'vyos' + with open('/etc/hostname', 'r') as f: + hostname = f.read().rstrip('\n') + tmp = {'dhcp_options' : { 'host_name' : hostname}} + self._config = dict_merge(tmp, self._config) + + render(options_file, 'dhcp-client/daemon-options.tmpl', + self._config, trim_blocks=True) + render(config_file, 'dhcp-client/ipv4.tmpl', + self._config, trim_blocks=True) + + # '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. + return self._cmd(f'systemctl restart dhclient@{ifname}.service') + else: + self._cmd(f'systemctl stop dhclient@{ifname}.service') + + # cleanup old config files + for file in [config_file, options_file, pid_file, lease_file]: + if os.path.isfile(file): + os.remove(file) + + + 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_file = f'/run/dhcp6c/dhcp6c.{ifname}.conf' + + if enable and 'disable' not in self._config: + render(config_file, 'dhcp-client/ipv6.tmpl', + self._config, trim_blocks=True) + + # 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 dhcp6c@{ifname}.service') + else: + self._popen(f'systemctl stop dhcp6c@{ifname}.service') + + if os.path.isfile(config_file): + os.remove(config_file) + + 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()? + self._config = config + # Update interface description self.set_alias(config.get('description', '')) @@ -822,14 +893,6 @@ class Interface(Control): value = '2' if 'disable_link_detect' in config else '1' self.set_link_detect(value) - # DHCP options - if 'dhcp_options' in config: - self.dhcp.v4.options = config - - # DHCPv6 options - if 'dhcpv6_options' in config: - self.dhcp.v6.options = config - # Configure assigned interface IP addresses. No longer # configured addresses will be removed first new_addr = config.get('address', []) -- cgit v1.2.3 From e7372e132a152477104d7e208a0be956b9879a71 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 13 Aug 2020 07:08:37 +0200 Subject: ifconfig: dhcp: fix ModuleNotFoundError: No module named 'vyos.ifconfig.dhcp' Commit 21bc98f1 ("ifconfig: dhcp: T2767: client must not start when interface is disabled") dropped the vyos.ifconfig.dhcp module but not removed it from the modules import list. --- python/vyos/ifconfig/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index a7cdeadd1..9cd8d44c1 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -13,12 +13,10 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . - 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.dhcp import DHCP from vyos.ifconfig.vrrp import VRRP from vyos.ifconfig.bond import BondIf -- cgit v1.2.3 From 94d697f5e161293f92a42f88aafc10c7955dda73 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 13 Aug 2020 19:03:38 +0200 Subject: console-server: T2490: use get_first_key=True on get_config_dict() --- src/conf_mode/service_console-server.py | 37 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py index ace6b8ca4..613ec6879 100755 --- a/src/conf_mode/service_console-server.py +++ b/src/conf_mode/service_console-server.py @@ -31,11 +31,9 @@ def get_config(): conf = Config() base = ['service', 'console-server'] - if not conf.exists(base): - return None - # Retrieve CLI representation as dictionary - proxy = conf.get_config_dict(base, key_mangling=('-', '_')) + 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'}, @@ -47,9 +45,10 @@ def get_config(): # 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 = defaults(base + ['device']) - for device in proxy['device'].keys(): - tmp = dict_merge(default_values, proxy['device'][device]) - proxy['device'][device] = tmp + if 'device' in proxy: + for device in proxy['device']: + tmp = dict_merge(default_values, proxy['device'][device]) + proxy['device'][device] = tmp return proxy @@ -57,15 +56,14 @@ def verify(proxy): if not proxy: return None - for device in proxy['device']: - keys = proxy['device'][device].keys() - if 'speed' not in keys: - raise ConfigError(f'Serial port speed must be defined for "{tmp}"!') + if 'device' in proxy: + for device in proxy['device']: + if 'speed' not in proxy['device'][device]: + raise ConfigError(f'Serial port speed must be defined for "{device}"!') - if 'ssh' in keys: - ssh_keys = proxy['device'][device]['ssh'].keys() - if 'port' not in ssh_keys: - raise ConfigError(f'SSH port must be defined for "{tmp}"!') + if 'ssh' in proxy['device'][device]: + if 'port' not in proxy['device'][device]['ssh']: + raise ConfigError(f'SSH port must be defined for "{device}"!') return None @@ -86,10 +84,11 @@ def apply(proxy): call('systemctl restart conserver-server.service') - for device in proxy['device']: - if 'ssh' in proxy['device'][device].keys(): - port = proxy['device'][device]['ssh']['port'] - call(f'systemctl restart dropbear@{device}.service') + if 'device' in proxy: + for device in proxy['device']: + if 'ssh' in proxy['device'][device]: + port = proxy['device'][device]['ssh']['port'] + call(f'systemctl restart dropbear@{device}.service') return None -- cgit v1.2.3 From 9a85c946e5f4d125276da432bb3055d1369d47fd Mon Sep 17 00:00:00 2001 From: Christian Pössinger Date: Fri, 14 Aug 2020 07:39:05 +0200 Subject: templates: dhcp-client: fix "Autogenerated by" script name Commit 21bc98f1 ("ifconfig: dhcp: T2767: client must not start when interface is disabled") dropped dhcp.py which is now directly handled by interface.py. This change should also be reflected in the generated files. --- data/templates/dhcp-client/daemon-options.tmpl | 3 +++ data/templates/dhcp-client/ipv4.tmpl | 4 +++- data/templates/dhcp-client/ipv6.tmpl | 5 +++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/data/templates/dhcp-client/daemon-options.tmpl b/data/templates/dhcp-client/daemon-options.tmpl index a0ba2c9ef..290aefa49 100644 --- a/data/templates/dhcp-client/daemon-options.tmpl +++ b/data/templates/dhcp-client/daemon-options.tmpl @@ -1 +1,4 @@ +### Autogenerated by interface.py ### + DHCLIENT_OPTS="-nw -cf /var/lib/dhcp/dhclient_{{ifname}}.conf -pf /var/lib/dhcp/dhclient_{{ifname}}.pid -lf /var/lib/dhcp/dhclient_{{ifname}}.leases {{ifname}}" + diff --git a/data/templates/dhcp-client/ipv4.tmpl b/data/templates/dhcp-client/ipv4.tmpl index fe2a67f08..8a44a9761 100644 --- a/data/templates/dhcp-client/ipv4.tmpl +++ b/data/templates/dhcp-client/ipv4.tmpl @@ -1,4 +1,5 @@ -# generated by dhcp.py +### Autogenerated by interface.py ### + option rfc3442-classless-static-routes code 121 = array of unsigned integer 8; timeout 60; retry 300; @@ -15,3 +16,4 @@ interface "{{ ifname }}" { rfc3442-classless-static-routes, domain-name, interface-mtu; require subnet-mask; } + diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl index 112431c5f..5c0cea280 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -1,6 +1,6 @@ -# generated by dhcp.py -# man https://www.unix.com/man-page/debian/5/dhcp6c.conf/ +### Autogenerated by interface.py ### +# man https://www.unix.com/man-page/debian/5/dhcp6c.conf/ interface {{ ifname }} { request domain-name-servers; request domain-name; @@ -45,3 +45,4 @@ id-assoc pd 2 { }; {% endif %} {% endif %} + -- cgit v1.2.3 From 93fb90b4f1eca5a9d1828714e06123bb22a7a219 Mon Sep 17 00:00:00 2001 From: sever-sever Date: Fri, 14 Aug 2020 17:02:20 +0000 Subject: dhclient: T2277: Hook for default route in vrf --- src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper index f1167fcd2..60e001af7 100644 --- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper +++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper @@ -20,6 +20,7 @@ function iptovtysh () { local VTYSH_NETADDR="" local VTYSH_GATEWAY="" local VTYSH_DEV="" + local VTYSH_VRF_NAME=$(ip -d link show dev $interface | grep $interface | awk '{print $9}') # convert default route to 0.0.0.0/0 if [ "$4" == "default" ] ; then VTYSH_NETADDR="0.0.0.0/0" @@ -40,7 +41,14 @@ function iptovtysh () { elif [ "$7" == "dev" ]; then VTYSH_DEV=$8 fi - VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE" + + # check if vrf is present + if [ $(ip -d link show dev $interface | grep vrf | wc -l) -eq 0 ]; then + VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE" + elif [ $(ip -d link show dev $interface | grep vrf | wc -l) -eq 1 ]; then + VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE vrf $VTYSH_VRF_NAME" + fi + # delete route if the command is "del" if [ "$3" == "del" ] ; then VTYSH_CMD="no $VTYSH_CMD" -- cgit v1.2.3 From add4578ff74f630219b2c1abf0eb122e7cd3eb78 Mon Sep 17 00:00:00 2001 From: jack9603301 Date: Sat, 15 Aug 2020 11:58:57 +0800 Subject: monitor-ndp:T2706: Modify the comment of `monitor ndp type` Since the comment was incorrectly applied to the command when writing the command before, modify it here --- op-mode-definitions/monitor-ndp.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/op-mode-definitions/monitor-ndp.xml b/op-mode-definitions/monitor-ndp.xml index e25eccf3a..1ac6ce39b 100644 --- a/op-mode-definitions/monitor-ndp.xml +++ b/op-mode-definitions/monitor-ndp.xml @@ -4,7 +4,7 @@ - Monitors the NDP information received by the router through the device + Monitor the NDP information received by the router through the device sudo ndptool monitor @@ -20,7 +20,7 @@ sudo ndptool monitor --ifname=$4 --msg-type=$6 - Monitor ndp protocol on specified interface + Monitor specific types of NDP protocols rs ra ns na @@ -31,7 +31,7 @@ sudo ndptool monitor --msg-type=$4 - Monitor ndp protocol on specified interface + Monitor specific types of NDP protocols rs ra ns na -- cgit v1.2.3 From b082a6fb211ef19d75c4c81414be9aa1b9248b45 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 13 Aug 2020 18:31:08 +0200 Subject: lcd: T2564: flatten CLI interface * set system lcd device * set system lcd model Both device and model have completion helpers for supported interfaces and LCD displays. --- data/templates/lcd/LCDd.conf.tmpl | 126 ++ data/templates/lcd/lcdproc.conf.tmpl | 60 + data/templates/system-display/LCDd.conf.tmpl | 1500 -------------------- data/templates/system-display/lcdproc.conf.tmpl | 173 --- interface-definitions/system-display.xml.in | 235 --- interface-definitions/system-lcd.xml.in | 62 + python/vyos/util.py | 12 + src/conf_mode/interfaces-wirelessmodem.py | 12 +- src/conf_mode/system-display.py | 98 -- src/conf_mode/system_lcd.py | 84 ++ .../systemd/system/LCDd.service.d/override.conf | 8 + .../system/hostapd@.service.d/override.conf | 7 +- src/systemd/LCDd@.service | 10 - src/systemd/lcdproc.service | 13 + src/systemd/lcdproc@.service | 10 - 15 files changed, 369 insertions(+), 2041 deletions(-) create mode 100644 data/templates/lcd/LCDd.conf.tmpl create mode 100644 data/templates/lcd/lcdproc.conf.tmpl delete mode 100644 data/templates/system-display/LCDd.conf.tmpl delete mode 100644 data/templates/system-display/lcdproc.conf.tmpl delete mode 100644 interface-definitions/system-display.xml.in create mode 100644 interface-definitions/system-lcd.xml.in delete mode 100755 src/conf_mode/system-display.py create mode 100755 src/conf_mode/system_lcd.py create mode 100644 src/etc/systemd/system/LCDd.service.d/override.conf delete mode 100644 src/systemd/LCDd@.service create mode 100644 src/systemd/lcdproc.service delete mode 100644 src/systemd/lcdproc@.service diff --git a/data/templates/lcd/LCDd.conf.tmpl b/data/templates/lcd/LCDd.conf.tmpl new file mode 100644 index 000000000..da749d04a --- /dev/null +++ b/data/templates/lcd/LCDd.conf.tmpl @@ -0,0 +1,126 @@ +### Autogenerted by system-display.py ## + +# LCDd.conf -- configuration file for the LCDproc server daemon LCDd +# +# This file contains the configuration for the LCDd server. +# +# The format is ini-file-like. It is divided into sections that start at +# markers that look like [section]. Comments are all line-based comments, +# and are lines that start with '#' or ';'. +# +# The server has a 'central' section named [server]. For the menu there is +# a section called [menu]. Further each driver has a section which +# defines how the driver acts. +# +# The drivers are activated by specifying them in a driver= line in the +# server section, like: +# +# Driver=curses +# +# This tells LCDd to use the curses driver. +# The first driver that is loaded and is capable of output defines the +# size of the display. The default driver to use is curses. +# If the driver is specified using the -d command line option, +# the Driver= options in the config file are ignored. +# +# The drivers read their own options from the respective sections. + +## Server section with all kinds of settings for the LCDd server ## +[server] + +# Where can we find the driver modules ? +# NOTE: Always place a slash as last character ! +DriverPath=/usr/lib/x86_64-linux-gnu/lcdproc/ + +# Tells the server to load the given drivers. Multiple lines can be given. +# The name of the driver is case sensitive and determines the section +# where to look for further configuration options of the specific driver +# as well as the name of the dynamic driver module to load at runtime. +# The latter one can be changed by giving a File= directive in the +# driver specific section. +# +# The following drivers are supported: +# bayrad, CFontz, CFontzPacket, curses, CwLnx, ea65, EyeboxOne, futaba, +# g15, glcd, glcdlib, glk, hd44780, icp_a106, imon, imonlcd,, IOWarrior, +# irman, joy, lb216, lcdm001, lcterm, linux_input, lirc, lis, MD8800, +# mdm166a, ms6931, mtc_s16209x, MtxOrb, mx5000, NoritakeVFD, +# Olimex_MOD_LCD1x9, picolcd, pyramid, rawserial, sdeclcd, sed1330, +# sed1520, serialPOS, serialVFD, shuttleVFD, sli, stv5730, svga, t6963, +# text, tyan, ula200, vlsys_m428, xosd, yard2LCD + +{% if model is defined and model.startswith('CFA-') %} +Driver=CFontzPacket +{% endif %} + +# Tells the driver to bind to the given interface. [default: 127.0.0.1] +Bind=127.0.0.1 + +# Listen on this specified port. [default: 13666] +Port=13666 + +# Sets the reporting level; defaults to warnings and errors only. +# [default: 2; legal: 0-5] +ReportLevel=3 + +# Should we report to syslog instead of stderr? [default: no; legal: yes, no] +ReportToSyslog=yes + +# User to run as. LCDd will drop its root privileges and run as this user +# instead. [default: nobody] +User=nobody + +# The server will stay in the foreground if set to yes. +# [default: no, legal: yes, no] +Foreground=yes + +# Hello message: each entry represents a display line; default: builtin +Hello="Starting VyOS" +Hello=" ... " + +# GoodBye message: each entry represents a display line; default: builtin +GoodBye=" VyOS shutting" +GoodBye=" down... " + +# Sets the interval in microseconds for updating the display. +# [default: 125000 meaning 8Hz] +FrameInterval=250000 # 4 updates per second + +# Sets the default time in seconds to displays a screen. [default: 4] +WaitTime=1 + +# If set to no, LCDd will start with screen rotation disabled. This has the +# same effect as if the ToggleRotateKey had been pressed. Rotation will start +# if the ToggleRotateKey is pressed. Note that this setting does not turn off +# priority sorting of screens. [default: on; legal: on, off] +AutoRotate=on + +# If yes, the the serverscreen will be rotated as a usual info screen. If no, +# it will be a background screen, only visible when no other screens are +# active. The special value 'blank' is similar to no, but only a blank screen +# is displayed. [default: on; legal: on, off, blank] +ServerScreen=blank + +# Set master backlight setting. If set to 'open' a client may control the +# backlight for its own screens (only). [default: open; legal: off, open, on] +Backlight=on + +# Set master heartbeat setting. If set to 'open' a client may control the +# heartbeat for its own screens (only). [default: open; legal: off, open, on] +Heartbeat=off + +# set title scrolling speed [default: 10; legal: 0-10] +TitleSpeed=10 + +{% if model is defined and model is not none %} +{% if model.startswith('CFA-') %} +## CrystalFontz packet driver (for CFA533, CFA631, CFA633 & CFA635) ## +[CFontzPacket] +Model={{ model.split('-')[1] }} +Device={{ device }} +Contrast=350 +Brightness=500 +OffBrightness=50 +Reboot=yes +USB=yes +{% endif %} +{% endif %} diff --git a/data/templates/lcd/lcdproc.conf.tmpl b/data/templates/lcd/lcdproc.conf.tmpl new file mode 100644 index 000000000..c79f3cd0d --- /dev/null +++ b/data/templates/lcd/lcdproc.conf.tmpl @@ -0,0 +1,60 @@ +### autogenerated by system-lcd.py ### + +# LCDproc client configuration file + +[lcdproc] +Server=127.0.0.1 +Port=13666 + +# set reporting level +ReportLevel=3 + +# report to to syslog ? +ReportToSyslog=true + +Foreground=yes + +[CPU] +Active=true +OnTime=1 +OffTime=2 +ShowInvisible=false + +[SMP-CPU] +Active=false + +[Memory] +Active=false + +[Load] +Active=false + +[Uptime] +Active=true + +[ProcSize] +Active=false + +[Disk] +Active=false + +[About] +Active=false + +[TimeDate] +Active=true +TimeFormat="%H:%M:%S" + +[OldTime] +Active=false + +[BigClock] +Active=false + +[MiniClock] +Active=false + +# Display the title bar in two-line mode. Note that with four lines or more +# the title is always shown. [default: true; legal: true, false] +ShowTitle=false + diff --git a/data/templates/system-display/LCDd.conf.tmpl b/data/templates/system-display/LCDd.conf.tmpl deleted file mode 100644 index 1dd646202..000000000 --- a/data/templates/system-display/LCDd.conf.tmpl +++ /dev/null @@ -1,1500 +0,0 @@ -### Autogenerted by system-display.py ## -# LCDd.conf -- configuration file for the LCDproc server daemon LCDd -# -# This file contains the configuration for the LCDd server. -# -# The format is ini-file-like. It is divided into sections that start at -# markers that look like [section]. Comments are all line-based comments, -# and are lines that start with '#' or ';'. -# -# The server has a 'central' section named [server]. For the menu there is -# a section called [menu]. Further each driver has a section which -# defines how the driver acts. -# -# The drivers are activated by specifying them in a driver= line in the -# server section, like: -# -# Driver=curses -# -# This tells LCDd to use the curses driver. -# The first driver that is loaded and is capable of output defines the -# size of the display. The default driver to use is curses. -# If the driver is specified using the -d command line option, -# the Driver= options in the config file are ignored. -# -# The drivers read their own options from the respective sections. - - - -## Server section with all kinds of settings for the LCDd server ## -[server] - -# Where can we find the driver modules ? -# IMPORTANT: Make sure to change this setting to reflect your -# specific setup! Otherwise LCDd won't be able to find -# the driver modules and will thus not be able to -# function properly. -# NOTE: Always place a slash as last character ! -DriverPath=/usr/lib/x86_64-linux-gnu/lcdproc/ - -# Tells the server to load the given drivers. Multiple lines can be given. -# The name of the driver is case sensitive and determines the section -# where to look for further configuration options of the specific driver -# as well as the name of the dynamic driver module to load at runtime. -# The latter one can be changed by giving a File= directive in the -# driver specific section. -# -# The following drivers are supported: -# bayrad, CFontz, CFontzPacket, curses, CwLnx, ea65, EyeboxOne, futaba, -# g15, glcd, glcdlib, glk, hd44780, icp_a106, imon, imonlcd,, IOWarrior, -# irman, joy, lb216, lcdm001, lcterm, linux_input, lirc, lis, MD8800, -# mdm166a, ms6931, mtc_s16209x, MtxOrb, mx5000, NoritakeVFD, -# Olimex_MOD_LCD1x9, picolcd, pyramid, rawserial, sdeclcd, sed1330, -# sed1520, serialPOS, serialVFD, shuttleVFD, sli, stv5730, svga, t6963, -# text, tyan, ula200, vlsys_m428, xosd, yard2LCD -{%- if model == 'sdec' %} -Driver=sdeclcd -{%- endif %} - -{%- if model == 'ezio' %} -Driver=hd44780 -{%- endif %} - -{%- if model == 'test' %} -Driver=CFontzPacket -{%- endif %} - -# Tells the driver to bind to the given interface. [default: 127.0.0.1] -#Bind=127.0.0.1 - -# Listen on this specified port. [default: 13666] -#Port=13666 - -# Sets the reporting level; defaults to warnings and errors only. -# [default: 2; legal: 0-5] -#ReportLevel=3 - -# Should we report to syslog instead of stderr? [default: no; legal: yes, no] -#ReportToSyslog=yes - -# User to run as. LCDd will drop its root privileges and run as this user -# instead. [default: nobody] -User=nobody - -# The server will stay in the foreground if set to yes. -# [default: no, legal: yes, no] -#Foreground=yes - -# Hello message: each entry represents a display line; default: builtin -Hello="{%- if hello %}{{ hello }}{%- else %}Welcome to VyOS{%- endif %}" - -# GoodBye message: each entry represents a display line; default: builtin -GoodBye="{%- if bye %}{{ bye }}{%- else %}Bye from VyOS{%- endif %}" - -# Sets the interval in microseconds for updating the display. -# [default: 125000 meaning 8Hz] -#FrameInterval=125000 - -# Sets the default time in seconds to displays a screen. [default: 4] -WaitTime={%- if time %}{{ time }}{%- else%}4{%- endif %} - -# If set to no, LCDd will start with screen rotation disabled. This has the -# same effect as if the ToggleRotateKey had been pressed. Rotation will start -# if the ToggleRotateKey is pressed. Note that this setting does not turn off -# priority sorting of screens. [default: on; legal: on, off] -#AutoRotate=off - -# If yes, the the serverscreen will be rotated as a usual info screen. If no, -# it will be a background screen, only visible when no other screens are -# active. The special value 'blank' is similar to no, but only a blank screen -# is displayed. [default: on; legal: on, off, blank] -ServerScreen=no - -# Set master backlight setting. If set to 'open' a client may control the -# backlight for its own screens (only). [default: open; legal: off, open, on] -#Backlight=open - -# Set master heartbeat setting. If set to 'open' a client may control the -# heartbeat for its own screens (only). [default: open; legal: off, open, on] -#Heartbeat=open - -# set title scrolling speed [default: 10; legal: 0-10] -#TitleSpeed=10 - -# The "...Key=" lines define what the server does with keypresses that -# don't go to any client. The ToggleRotateKey stops rotation of screens, while -# the PrevScreenKey and NextScreenKey go back / forward one screen (even if -# rotation is disabled. -# Assign the key string returned by the driver to the ...Key setting. These -# are the defaults: -ToggleRotateKey=Enter -PrevScreenKey=Left -NextScreenKey=Right -#ScrollUpKey=Up -#ScrollDownKey=Down - -## The menu section. The menu is an internal LCDproc client. ## -[menu] -# If true the server allows transitions between different client's menus -# [default: false; legal: true, false] -#PermissiveGoto=false - -# You can configure what keys the menu should use. Note that the MenuKey -# will be reserved exclusively, the others work in shared mode. - -# Up to six keys are supported. The MenuKey (to enter and exit the menu), the -# EnterKey (to select values) and at least one movement keys are required. -# These are the default key assignments: -MenuKey=Escape -EnterKey=Enter -UpKey=Up -DownKey=Down -#LeftKey=Left -#RightKey=Right - - -### Driver sections are below this line, in alphabetical order ### - - -## EMAC BayRAD driver ## -[bayrad] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the communication speed [default: 9600; legal: 1200, 2400, 9600, 19200] -Speed=9600 - - - -## CrystalFontz driver (for CF632 & CF634) ## -[CFontz] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/ttyS0 -# Select the LCD size [default: 20x4] -Size=20x4 -# Set the initial contrast [default: 560; legal: 0 - 1000] -Contrast=350 -# Set the initial brightness [default: 1000; legal: 0 - 1000] -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=0 -# Set the communication speed [default: 9600; legal: 1200, 2400, 9600, 19200, -# 115200] -Speed=9600 -# Set the firmware version (New means >= 2.0) [default: no; legal: yes, no] -NewFirmware=no -# Reinitialize the LCD's BIOS [default: no; legal: yes, no] -# normally you shouldn't need this -Reboot=no - - - -## CrystalFontz packet driver (for CFA533, CFA631, CFA633 & CFA635) ## -[CFontzPacket] -{%- if model == 'test' %} -Model=533 -Device=/dev/serial/by-bus/usb0b1.1p1.0 -Contrast=350 -Brightness=1000 -OffBrightness=50 -Reboot=yes -USB=yes -{%- endif %} - - -## Curses driver ## -[curses] - -# color settings -# foreground color [default: blue] -Foreground=blue -# background color when "backlight" is off [default: cyan] -Background=cyan -# background color when "backlight" is on [default: red] -Backlight=red - -# display size [default: 20x4] -Size=20x2 - -# What position (X,Y) to start the left top corner at... -# Default: (7,7) -TopLeftX=7 -TopLeftY=7 - -# use ASC symbols for icons & bars [default: no; legal: yes, no] -UseACS=no - -# draw Border [default: yes; legal: yes, no] -DrawBorder=yes - - - -## Cwlinux driver ## -[CwLnx] - -# Select the LCD model [default: 12232; legal: 12232, 12832, 1602] -Model=12232 - -# Select the output device to use [default: /dev/lcd] -Device=/dev/ttyUSB0 - -# Select the LCD size. Default depends on model: -# 12232: 20x4 -# 12832: 21x4 -# 1602: 16x2 -Size=20x4 - -# Set the communication speed [default: 19200; legal: 9600, 19200] -Speed=19200 - -# Reinitialize the LCD's BIOS [default: no; legal: yes, no] -# normally you shouldn't need this -Reboot=no - -# If you have a keypad connected. Keypad layout is currently not -# configureable from the config file. -Keypad=yes - -# If you have a non-standard keypad you can associate any keystrings to keys. -# There are 6 input keys in the CwLnx hardware that generate characters -# from 'A' to 'F'. -# -# The following is the built-in default mapping hardcoded in the driver. -# You can leave those unchanged if you have a standard keypad. -# You can change it if you want to report other keystrings or have a non -# standard keypad. -# KeyMap_A=Up -# KeyMap_B=Down -# KeyMap_C=Left -# KeyMap_D=Right -# KeyMap_E=Enter -# KeyMap_F=Escape - -# keypad_test_mode permits one to test keypad assignment -# Default value is no -#keypad_test_mode=yes - - - -## ea65 driver for the display in AOpen XC Cube AV EA65 media barebones ## -[ea65] - -# Device is fixed /dev/ttyS1 -# Width and Height are fixed 9x1 - -# As the VFD is self luminescent we don't have a backlight -# But we can use the backlight functions to control the front LEDs -# Brightness 0 to 299 -> LEDs off -# Brightness 300 to 699 -> LEDs half bright -# Brightness 700 to 1000 -> LEDs full bright -Brightness=500 -# OffBrightness is the the value used for the 'backlight off' state -OffBrightness=0 - - - -## EyeboxOne driver ## -[EyeboxOne] - -# Select the output device to use [default: /dev/ttyS1] -# Device=/dev/cua01 -Device=/dev/ttyS1 - -# Set the display size [default: 20x4] -Size=20x4 - -# Switch on the backlight? [default: yes] -Backlight=yes - -# Switch on the cursor? [default: no] -Cursor=no - -# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] -Speed=19200 - -# Enter Key is a \r character, so it's hardcoded in the driver -LeftKey=D -RightKey=C -UpKey=A -DownKey=B -EscapeKey=P - -# You can find out which key of your display sends which -# character by setting keypad_test_mode to yes and running -# LCDd. LCDd will output all characters it receives. -# Afterwards you can modify the settings above and set -# keypad_set_mode to no again. -keypad_test_mode=no - -## Futaba TOSD-5711BB VFD Driver ## -[futaba] - -## g15 driver for Logitech G15 Keyboard LCDs ## -[g15] - -# Display size (currently unused) -size=20x5 - - - -## glcd generic graphical display driver -[glcd] -# Select what type of connection. See documentation for types. -ConnectionType=t6963 - -# Width and height of the display in pixel. The supported sizes may depend on -# the ConnectionType. [default: 128x64; legal: 1x1 - 640x480] -#Size=128x64 - -# Width and height of a character cell in pixels. This value is only used if -# the driver has been compiled with FreeType and it is enabled. Otherwise the -# default 6x8 cell is used. -#CellSize=12x16 - -# If LCDproc has been compiled with FreeType 2 support this option can be used -# to turn if off intentionally. [default: yes; legal: yes, no] -#useFT2=no - -# Path to font file to use for FreeType rendering. This font must be monospace -# and should contain some special Unicode characters like arrows (Andale Mono -# is recommended and can be fetched at http://corefonts.sf.net). -#normal_font=/usr/local/lib/X11/fonts/TTF/andalemo.ttf - -# Some fonts miss the Unicode characters used to represent icons. In this case -# the built-in 5x8 font can used if this option is turned off. [default: yes; -# legal: yes, no] -#fontHasIcons=no - -# Set the initial contrast if supported by connection type. -# [default: 600; legal: 0 - 1000] -#Contrast=600 - -# Set brightness of the backlight if the backlight is switched 'on'. -# [default: 800; legal: 0 - 1000] -#Brightness=1000 - -# Set brightness of the backlight if the backlight is switched 'off'. Set this -# to zero to completely turn off the backlight. [default: 100; legal: 0 - 1000] -#OffBrightness=0 - -# Time (ms) from first key report to first repeat. Set to 0 to disable repeated -# key reports. [default: 500; legal: 0 - 3000] -#KeyRepeatDelay=500 - -# Time (ms) between repeated key reports. Ignored if KeyRepeatDelay is disabled -# (set to zero). [default: 300; legal: 0 - 3000] -#KeyRepeatInterval=300 - -# Assign key strings to keys. There may be up to 16 keys numbered 'A' to 'Z'. -# By default keys 'A' to 'F' are assigned Up, Down, Left, Right, Enter, Escape. -KeyMap_A=Up -KeyMap_B=Down -KeyMap_C=Enter -KeyMap_D=Escape - -# --- t6963 options --- - -# Parallel port to use [default: 0x378; legal: 0x200 - 0x400] -#Port=0x378 - -# Use LPT port in bi-directional mode. This should work on most LPT port -# and is required for proper timing! [default: yes; legal: yes, no] -#bidirectional=yes - -# Insert additional delays into reads / writes. [default: no; legal: yes, no] -#delayBus=no - -# --- serdisplib options --- - -# Name of the underlying serdisplib driver, e.g. ctinclud. See -# serdisplib documentation for details. -serdisp_name=t6963 - -# The display device to use, e.g. serraw:/dev/ttyS0, -# parport:/dev/parport0 or USB:07c0/1501. -serdisp_device=/dev/ppi0 - -# Options string to pass to serdisplib during initialization. Use -# this to set any display related options (e.g. wiring). The display size is -# always set based on the Size configured above! By default, no options are -# set. -# Important: The value must be quoted as it contains equal signs! -#serdisp_options="INVERT=1" - -# --- x11 options --- - -# PixelSize is size of each dot in pixels + a pixel gap. [default: 3+1] -#x11_PixelSize=3+1 - -# Colors are in RRGGBB format prefixed with "0x". -# PixelColor: The color of each dot at full contrast. [default: 0x000000] -#x11_PixelColor=0x000000 - -# BacklightColor: The color of the backlight as full brightness. -# [default: 0x80FF80] -#x11_BacklightColor=0x80FF80 - -# Border: Adds a border (empty space) around the LCD portion of X11 window. -# [default: 20] -#x11_Border=20 - -# Inverted: inverts the pixels [default: no; legal: yes, no] -#x11_Inverted=no - -# --- picolcdgfx options --- - -# Time in ms for usb_read to wait on a key press. [default: 125; legal: >0] -#picolcdgfx_KeyTimeout=125 - -# Inverted: Inverts the pixels. [default: no; legal: yes or no] -#picolcdgfx_Inverted=no - - - -## glcdlib meta driver for graphical LCDs ## -[glcdlib] - -## mandatory: - -# which graphical display supported by graphlcd-base to use [default: image] -# (see /etc/graphlcd.conf for possible drivers) -Driver=noritake800 - -# no=use graphlcd bitmap fonts (they have only one size / font file) -# yes=use fonts supported by FreeType2 (needs Freetype2 support in -# libglcdprocdriver and its dependants) -UseFT2=yes - -# text resolution in fixed width characters [default: 16x4] -# (if it won't fit according to available physical pixel resolution -# and the minimum available font face size in pixels, then -# 'DebugBorder' will automatically be turned on) -TextResolution=20x4 - -# path to font file to use -FontFile=/usr/share/fonts/corefonts/courbd.ttf - -## these only apply if UseFT2=yes: - -# character encoding to use -CharEncoding=iso8859-2 - -# minimum size in pixels in which fonts should be rendered -MinFontFaceSize=7x12 - -## optional: -Brightness=50 # Brightness (in %) if applicable -Contrast=50 # Contrast (in %) if applicable -Backlight=no # Backlight if applicable -UpsideDown=no # flip image upside down -Invert=no # invert light/dark pixels -ShowDebugFrame=no # turns on/off 1 pixel thick debugging - # border within the usable text area, - # for setting up TextResolution and - # MinFontFaceSize (if using FT2); -ShowBigBorder=no # border around the unused area -ShowThinBorder=yes # border around the unused area -PixelShiftX=0 -PixelShiftY=2 - - - -## Matrix Orbital GLK driver ## -[glk] - -# select the serial device to use [default: /dev/lcd] -Device=/dev/lcd - -# set the initial contrast value [default: 500; legal: 0 - 1000] -Contrast=500 - -# set the serial port speed [default: 19200; legal: 9600, 19200, 38400, 57600, 115200] -Speed=19200 - - - -## Hitachi HD44780 driver ## -[hd44780] -{%- if model == 'ezio' %} -ConnectionType=ezio -Device=/dev/ttyUSB0 -Keypad=yes -Size=16x2 -KeyMatrix_4_1=Enter -KeyMatrix_4_2=Up -KeyMatrix_4_3=Down -KeyMatrix_4_4=Escape -{%- endif %} - -# Select what type of connection. See documentation for available types. -#ConnectionType=4bit - -# Select model if have non-standard one which require extra initialization or handling or -# just want extra features it offers. -# Available: standard (default), extended, winstar_oled, pt6314_vfd -# - standard is default, use for LCDs not mentioned below. -# - extended, hd66712, ks0073: allows use 4-line "extended" mode, -# same as deprecated now option ExtendedMode=yes -# - winstar_oled, weh00xxyya: changes initialization for WINSTAR's WEH00xxyyA displays -# and allows handling brightness -# - pt6314_vfd: allows handling brightness on PTC's PT6314 VFDs -# -# This option should be independent of connection type. -#Model = standard - -# I/O address of the LPT port. Usual values are: 0x278, 0x378 and 0x3BC. -# For I2C connections this sets the slave address (usually 0x20). -#Port=0x378 - -# Device of the serial, I2C, or SPI interface [default: /dev/lcd] -#Device=/dev/ttyS0 - -# Bitrate of the serial port (0 for interface default) -#Speed=0 - -# If you have a keypad connected. -# You may also need to configure the keypad layout further on in this file. -#Keypad=no - -# Set the initial contrast (bwctusb, lcd2usb, and usb4all) -# [default: 800; legal: 0 - 1000] -#Contrast=0 - -# Set brightness of the backlight (lcd2usb and usb4all): -# Brightness is the brightness while the backlight is set to 'on'. -# [default: 800; legal: 0 - 1000] -#Brightness=1000 - -# OffBrightness is the brightness while the backlight is set to 'off'. -# [default: 300; legal: 0 - 1000] -#OffBrightness=0 - -# Specify if you have a switchable backlight and if yes, can select method for turning it on/off: -# -# - none - no switchable backlight is available. For compability also boolean -# 0, n, no, off and false are aliases. -# - external - use external pin or any other method defined with ConnectionType backlight -# handling. For backward compability also this value is chosen for boolean -# TRUE values: 1, y, yes, on and true. -# - internal - means that backlight is handled using internal commands according -# to selected display model (with Model option). Depending on model, -# Brightness and OffBrightness options can be taken into account. -# - internalCmds - means that commands for turning on and off backlight are given -# with extra options BacklightOnCmd and BacklightOffCmd, which would be treated -# as catch up (last resort) for other types of displays which have similar features. -# -# You can provide multiple occurences of this option to use more than one method. -# Default is model specific: Winstar OLED and PT6314 VFD enables internal backlight mode, -# for others it is set to none. -#Backlight = none - -# Commands for enabling internal backlight for use with Backlight=internalCmds. -# Up to 4 bytes can be encoded, as integer number in big-endian order. -# -# NOTE: this is advanced option, if command contains bits other than only brighness handling, -# they must be set accordingly to not disrupt display state. If for example 'FUNCTION SET' command -# is used for this purpose, bits of interface length (4-bit / 8-bit) must be set according to -# selected ConnectionType. -#BacklightCmdOn=0x1223 - -# Commands for disabling internal backlight for use with Backlight=internalCmds. -# Up to 4 bytes can be encoded, as integer number in big-endian order. -#BacklightCmdOff=0x1234 - - -# If you have the additional output port ("bargraph") and you want to -# be able to control it with the lcdproc OUTPUT command -#OutputPort=no - -# Specifies if the last line is pixel addressable (yes) or it controls an -# underline effect (no). [default: yes; legal: yes, no] -#Lastline=yes - -# Specifies the size of the LCD. -# In case of multiple combined displays, this should be the total size. -#Size=20x4 - -# For multiple combined displays: how many lines does each display have. -# Vspan=2,2 means both displays have 2 lines. -#vspan=2,2 - -# If you have an HD66712, a KS0073 or another controller with 'extended mode', -# set this flag to get into 4-line mode. On displays with just two lines, do -# not set this flag. -# As an additional restriction, controllers with and without extended mode -# AND 4 lines cannot be mixed for those connection types that support more -# than one display! -# NOTE: This option is deprecated in favour of choosing Model=extended option. -#ExtendedMode=yes - -# In extended mode, on some controllers like the ST7036 (in 3 line mode) -# the next line in DDRAM won't start 0x20 higher. [default: 0x20] -#LineAddress=0x10 - -# Character map to to map ISO-8859-1 to the LCD's character set -# [default: hd44780_default; legal: hd44780_default, hd44780_euro, ea_ks0073, -# sed1278f_0b, hd44780_koi8_r, hd44780_cp1251, hd44780_8859_5, upd16314, -# weh001602a_1] -# (hd44780_koi8_r, hd44780_cp1251, hd44780_8859_5, upd16314 and weh001602a_1 -# are possible if compiled with additional charmaps) -CharMap=hd44780_default - -# Font bank to be used for some displays such as the WINSTAR WEH001602A -# 0: English/Japanese (default) -# 1: Western Europe I -# 2: English/Rusian -# 3: Western Europe II -#FontBank=0 - -# If your display is slow and cannot keep up with the flow of data from -# LCDd, garbage can appear on the LCDd. Set this delay factor to 2 or 4 -# to increase the delays. Default: 1. -#DelayMult=2 - -# Some displays (e.g. vdr-wakeup) need a message from the driver to that it -# is still alive. When set to a value bigger then null the character in the -# upper left corner is updated every seconds. Default: 0. -#KeepAliveDisplay=0 - -# If you experience occasional garbage on your display you can use this -# option as workaround. If set to a value bigger than null it forces a -# full screen refresh seconds. Default: 0. -#RefreshDisplay=5 - -# You can reduce the inserted delays by setting this to false. -# On fast PCs it is possible your LCD does not respond correctly. -# Default: true. -#DelayBus=true - -# If you have a keypad you can assign keystrings to the keys. -# See documentation for used terms and how to wire it. -# For example to give directly connected key 4 the string "Enter", use: -# KeyDirect_4=Enter -# For matrix keys use the X and Y coordinates of the key: -# KeyMatrix_1_3=Enter -#KeyMatrix_4_1=Enter -#KeyMatrix_4_2=Up -#KeyMatrix_4_3=Down -#KeyMatrix_4_4=Escape - -## ICP Peripheral Comminication Protocol driver ## -# Supports A125 and A106 -# -# Short Press Select: Down -# Long Press Select: Up -# Short Press Enter: Enter -# Long Press Enter: Escape -# -[icp_a106] -Device=/dev/ttyS1 - -# Display dimensions -Size=20x2 - - -## Code Mercenaries IO-Warrior driver ## -[IOWarrior] - -# display dimensions -Size=20x4 - -# serial number. Must be exactly as listed by usbview -# (if not given, the 1st IOWarrior found gets used) -#SerialNumber=00000674 - -# If you have an HD66712, a KS0073 or another 'almost HD44780-compatible', -# set this flag to get into extended mode (4-line linear). -#ExtendedMode=yes - -# Specifies if the last line is pixel addressable (yes) or it controls an -# underline effect (no). [default: yes; legal: yes, no] -#Lastline=yes - - - -## Soundgraph/Ahanix/Silverstone/Uneed/Accent iMON driver ## -[imon] - -# select the device to use -Device=/dev/lcd0 - -# display dimensions -Size=16x2 - -# Character map to to map ISO-8859-1 to the displays character set. -# [default: none; legal: none, hd44780_euro, upd16314, hd44780_koi8_r, -# hd44780_cp1251, hd44780_8859_5 ] (upd16314, hd44780_koi8_r, -# hd44780_cp1251, hd44780_8859_5 are possible if compiled with additional -# charmaps) -CharMap=hd44780_euro - -## Soundgraph iMON LCD ## -[imonlcd] -# Specify which iMon protocol should be used -# [legal: 0, 1; default: 0] -# Choose 0 for 15c2:ffdc device, -# Choose 1 for 15c2:0038 device -Protocol=0 - -# Set the exit behavior [legal: 0-2; default: 1] -# 0 means leave shutdown message, -# 1 means show the big clock, -# 2 means blank device -#OnExit=2 - -# Select the output device to use [default: /dev/lcd0] -Device=/dev/lcd0 - -# Select the displays contrast [default: 200; legal: 0-1000] -Contrast=200 - -# Specify the size of the display in pixels [default: 96x16] -#Size=96x16 - -# Set the backlight state [default: on; legal: on, off] -#Backlight=on - -# Set the disc mode [legal: 0,1; default: 0] -# 0 => spin the "slim" disc - two disc segments, -# 1 => their complement spinning; -#DiscMode=0 - - - -## IrMan driver ## -[IrMan] -# in case of trouble with IrMan, try the Lirc emulator for IrMan - -# Select the input device to use -#Device=/dev/irman - -# Select the configuration file to use -#Config=/etc/irman.cfg - - - -## IRtrans driver ## -[irtrans] - -# Does the device have a backlight? [default: no; legal: yes, no] -#Backlight=no - -# IRTrans device to connect to [default: localhost] -#Hostname=localhost - -# display dimensions -Size=16x2 - - - -## Joystick driver ## -[joy] - -# Select the input device to use [default: /dev/js0] -Device=/dev/js0 - -# set the axis map -Map_Axis1neg=Left -Map_Axis1pos=Right -Map_Axis2neg=Up -Map_Axis2pos=Down - -# set the button map -Map_Button1=Enter -Map_Button2=Escape - - -## JW-002 driver ## -[jw002] - -# Select the output device to use [default: /dev/lcd] -#Device=/dev/ttyS0 - -# Set the display size [default: 24x8] -Size=24x8 - -# Optional X and Y offsets (in characters) to center a smaller display -# size on the full 24x8 panel -X_offset=0 -Y_offset=0 - -# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] -Speed=19200 - -# Pick which font page to use [default: 0] -# Note that different fonts probably have their bargraph chars in different -# spots. For ROM-based fonts 0-3, those characters are already known. -Font=0 - -# The following table translates from jw002 key letters to logical key names. -# By default no keys are mapped, meaning the keypad is not used at all. -#KeyMap_I=Left -#KeyMap_J=Right -#KeyMap_H=Up -#KeyMap_K=Down -#KeyMap_L=Enter -#KeyMap_A=Escape -# See the [menu] section for an explanation of the key mappings - -# You can find out which key of your display sends which -# character by setting keypad_test_mode to yes and running -# LCDd. LCDd will output all characters it receives. -# Afterwards you can modify the settings above and set -# keypad_set_mode to no again. -keypad_test_mode=no - - -## LB216 driver ## -[lb216] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the initial brightness [default: 255; legal: 0 - 255] -Brightness=255 - -# Set the communication speed [default: 9600; legal: 2400, 9600] -Speed=9600 - -# Reinitialize the LCD's BIOS [default: no; legal: yes, no] -Reboot=no - - - -## LCDM001 driver ## -[lcdm001] - -Device=/dev/ttyS1 - -# keypad settings -# Keyname Function -# Normal context Menu context -# ------- -------------- ------------ -# PauseKey Pause/Continue Enter/select -# BackKey Back(Go to previous screen) Up/Left -# ForwardKey Forward(Go to next screen) Down/Right -# MainMenuKey Open main menu Exit/Cancel -PauseKey=LeftKey -BackKey=UpKey -ForwardKey=DownKey -MainMenuKey=RightKey - -# You can rearrange the settings here. -# If your device is broken, have a look at server/drivers/lcdm001.h - - - -## HNE LCTerm driver ## -[lcterm] -Device=/dev/ttyS1 -Size=16x2 - - -## Linux event device input driver ## -[linux_input] - -# Select the input device to use [default: /dev/input/event0]. This may be -# either an absolute path to the input node, starting with '/', or -# an input device name, e.g. "Logitech Gaming Keyboard Gaming Keys". -# Device=/dev/input/event0 - -# specify a non-default key map -#key=1,Escape -#key=28,Enter -#key=96,Enter -#key=105,Left -#key=106,Right -#key=103,Up -#key=108,Down - - -## LIRC input driver ## -[lirc] - -# Specify an alternative location of the lircrc file [default: ~/.lircrc] -#lircrc=/etc/lircrc.lcdproc - -# Must be the same as in your lircrc -#prog=lcdd - - - -## LIS MCE 2005 driver ## -[lis] - -# Set the initial brightness [default: 1000; legal: 0 - 1000] -# 0-250 = 25%, 251-500 = 50%, 501-750 = 75%, 751-1000 = 100% -#Brightness=1000 - -# Columns by lines [default: 20x2] -#Size=20x2 - -# USB Vendor ID [default: 0x0403] -# Change only if testing a compatible device. -#VendorID=0x0403 - -# USB Product ID [default: 0x6001] -# Change only if testing a compatible device. -#ProductID=0x6001 - -# Specifies if the last line is pixel addressable (yes) or it only controls an -# underline effect (no). [default: yes; legal: yes, no] -#Lastline=yes - - - -##The driver for the VFD of the Medion MD8800 PC ## -[MD8800] -# device to use [default: /dev/ttyS1] -#Device=/dev/ttyS1 - -# display size [default: 16x2] -#Size=16x2 - -# Set the initial brightness [default: 1000; legal: 0 - 1000] -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=50 - - - -## Futuba MDM166A Display -[mdm166a] -# Show self-running clock after LCDd shutdown -# Possible values: [default: no; legal: no, small, big] -Clock=big -# Dim display, no dimming gives full brightness [default: no, legal: yes, no] -Dimming=no -# Dim display in case LCDd is inactive [default: no, legal: yes, no] -OffDimming=yes - - - -## MSI MS-6931 driver for displays in 1HU servers ## -[ms6931] - -# device to use [default: /dev/ttyS1] -Device=/dev/ttyS1 - -# display size [default: 16x2] -#Size=16x2 - - - -## MTC-S16209x driver ## -[mtc_s16209x] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the initial brightness [default: 255; legal: 0 - 255] -Brightness=255 - -# Reinitialize the LCD's BIOS [default: no; legal: yes, no] -Reboot=no - - - -## Matrix Orbital driver ## -[MtxOrb] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/ttyS0 - -# Set the display size [default: 20x4] -Size=20x4 - -# Set the display type [default: lcd; legal: lcd, lkd, vfd, vkd] -Type=lkd - -# Set the initial contrast [default: 480] -# NOTE: The driver will ignore this if the display -# is a vfd or vkd as they don't have this feature -Contrast=480 - -# Some old displays do not have an adjustable backlight but only can -# switch the backlight on/off. If you experience randomly appearing block -# characters, try setting this to false. [default: yes; legal: yes, no] -hasAdjustableBacklight=no - -# Set the initial brightness [default: 1000; legal: 0 - 1000] -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=0 - -# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200] -Speed=19200 - -# The following table translates from MtxOrb key letters to logical key names. -# By default no keys are mapped, meaning the keypad is not used at all. -#KeyMap_A=Left -#KeyMap_B=Right -#KeyMap_C=Up -#KeyMap_D=Down -#KeyMap_E=Enter -#KeyMap_F=Escape -# See the [menu] section for an explanation of the key mappings - -# You can find out which key of your display sends which -# character by setting keypad_test_mode to yes and running -# LCDd. LCDd will output all characters it receives. -# Afterwards you can modify the settings above and set -# keypad_set_mode to no again. -keypad_test_mode=no - - - -## mx5000 driver for LCD display on the Logitech MX5000 keyboard ## -[mx5000] - -# Select the output device to use [default: /dev/hiddev0] -Device = /dev/hiddev0 -# Time to wait in ms after the refresh screen has been sent [default: 1000] -WaitAfterRefresh = 1000 - - - -## Noritake VFD driver ## -[NoritakeVFD] -# device where the VFD is. Usual values are /dev/ttyS0 and /dev/ttyS1 -# [default: /dev/lcd] -Device=/dev/ttyS0 -# Specifies the size of the LCD. -Size=20x4 -# Set the initial brightness [default: 1000; legal: 0 - 1000] -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=50 -# set the serial port speed [default: 9600, legal: 1200, 2400, 9600, 19200, 115200] -Speed=9600 -# Set serial data parity [default: 0; legal: 0-2 ] -# Meaning: 0(=none), 1(=odd), 2(=even) -Parity=0 -# re-initialize the VFD [default: no; legal: yes, no] -Reboot=no - - - -## Olimex MOD-LCD1x9 driver ## -[Olimex_MOD_LCD1x9] - -# device file of the i2c controler -Device=/dev/i2c-0 - - -## Mini-box.com picoLCD (usblcd) driver ## -[picolcd] - -# KeyTimeout is only used if the picoLCD driver is built with libusb-0.1. When -# built with libusb-1.0 key and IR data is input asynchronously so there is no -# need to wait for the USB data. -# KeyTimeout is the time in ms that LCDd spends waiting for a key press before -# cycling through other duties. Higher values make LCDd use less CPU time and -# make key presses more detectable. Lower values make LCDd more responsive -# but a little prone to missing key presses. 500 (.5 second) is the default -# and a balanced value. [default: 500; legal: 0 - 1000] -KeyTimeout=500 - -# Key auto repeat is only available if the picoLCD driver is built with -# libusb-1.0. Use KeyRepeatDelay and KeyRepeatInterval to configure key auto -# repeat. -# -# Key auto repeat delay (time in ms from first key report to first repeat). Use -# zero to disable auto repeat. [default: 300; legal: 0 - 3000] -KeyRepeatDelay=300 - -# Key auto repeat interval (time in ms between repeat reports). Only used if -# KeyRepeatDelay is not zero. [default: 200; legal: 0 - 3000] -KeyRepeatInterval=200 - -# Sets the initial state of the backlight upon start-up. -# [default: on; legal: on, off] -#Backlight=on - -# Set the initial brightness [default: 1000; legal: 0 - 1000]. Works only -# with the 20x4 device -Brightness=1000 - -# Set the brightness while the backlight is 'off' [default: 0; legal: 0 - 1000]. -# Works only with the 20x4 device. -#OffBrightness=0 - -# Set the initial contrast [default: 1000; legal: 0 - 1000] -Contrast=1000 - -# Link the key lights to the backlight? [default: on; legal: on, off] -#LinkLights=off - -# Light the keys? [default: on; legal: on, off] -Keylights=on - -# If Keylights is on, the you can unlight specific keys below: -# Key0 is the directional pad. Key1 - Key5 correspond to the F1 - F5 keys. -# There is no LED for the +/- keys. This is a handy way to indicate to users -# which keys are disabled. [default: on; legal: on, off] -Key0Light=on -Key1Light=on -Key2Light=on -Key3Light=on -Key4Light=on -Key5Light=on - -# Host name or IP address of the LIRC instance that is to receive IR codes -# If not set, or set to an empty value, IR support is disabled. -#LircHost=127.0.0.1 - -# UDP port on which LIRC is listening [default: 8765; legal: 1 - 65535] -LircPort=8765 - -# UDP data time unit for LIRC [default: off; legal: on, off] -# On: times sent in microseconds (requires LIRC UDP driver that accepts this). -# Off: times sent in 'jiffies' (1/16384s) (supported by standard LIRC UDP driver). -LircTime_us=on - -# Threshold in microseconds of the gap that triggers flushing the IR data -# to lirc [default: 8000; legal: 1000 - ] -# If LircTime_us is on values greater than 32.767ms will disable the flush -# If LircTime_us is off values greater than 1.999938s will disable the flush -LircFlushThreshold=10000 - - - -## Pyramid LCD driver ## -[pyramid] - -# device to connect to [default: /dev/lcd] -Device=/dev/ttyUSB0 - - - -## rawserial driver ## -[rawserial] - -# Select the output device to use [default: /dev/cuaU0] -Device=/dev/ttyS0 - -# Serial port baudrate [default: 9600] -Speed=9600 - -# Specifies the size of the LCD. If this driver is loaded as a secondary driver -# it always adopts to the size of the primary driver. If loaded as the only -# (or primary) driver, the size can be set. [default: 40x4] -#Size=16x2 - -# How often to dump the LCD contents out the port, in Hertz (times per second) -# 1 = once per second, 4 is 4 times per second, 0.1 is once every 10 seconds. -# [default: 1; legal: 0.0005 - 10] -UpdateRate=1 - - - -## SDEC driver for Watchguard Firebox ## -[sdeclcd] -# No options - - -## Seiko Epson 1330 driver ## -[sed1330] - -# Port where the LPT is. Common values are 0x278, 0x378 and 0x3BC -Port=0x378 - -# Type of LCD module (legal: G321D, G121C, G242C, G191D, G2446, SP14Q002) -# Note: Currently only tested with G321D & SP14Q002. -Type=G321D - -# Width x Height of a character cell in pixels [legal: 6x7 - 8x16; default: 6x10] -CellSize=6x10 - -# Select what type of connection [legal: classic, bitshaker; default: classic] -ConnectionType=classic - - - -## Seiko Epson 1520 driver ## -[sed1520] - -# Port where the LPT is. Usual values are 0x278, 0x378 and 0x3BC -Port=0x378 - -# Select the interface type (wiring) for the display. Supported values are -# 68 for 68-style connection (RESET level high) and 80 for 80-style connection -# (RESET level low). [legal: 68, 80; default: 80] -InterfaceType=80 - -# On fast machines it may be necessary to slow down transfer to the display. -# If this value is set to zero, delay is disabled. Any value greater than -# zero slows down each write by one microsecond. [legal: 0-1000; default: 1] -DelayMult=0 - -# The original wiring used an inverter to drive the control lines. If you do -# not use an inverter set haveInverter to no. [default: yes; legal: yes, no] -HaveInverter=no - -# On some displays column data in memory is mapped to segment lines from right -# to left. This is called inverted mapping (not to be confused with -# 'haveInverter' from above). [default: no; legal: yes, no] -#InvertedMapping=yes - -# At least one display is reported (Everbouquet MG1203D) that requires sending -# three times 0xFF before a reset during initialization. -# [default: no; legal: yes, no] -#UseHardReset=yes - - -## serial POS display driver ## -[serialPOS] - -# Device to use in serial mode [default: /dev/ttyS0] -Device=/dev/ttyS0 - -# Specifies the size of the display in characters. [default: 16x2] -Size=16x2 - -# Specifies the cell size of each character cell on the display in characters. -# [default: 5x8] -Cellsize=5x8 - -# Specifies the number of custom characters supported by the display. -# [default: 0] -Custom_chars=0 - -# Set the communication protocol to use with the POS display. -# [default: AEDEX; legal: AEDEX, CD5220, Epson, Emax, LogicControls, Ultimate] -Type=AEDEX - -# communication baud rate with the display [default: 9600; legal: 1200, 2400, -# 4800, 9600, 19200, 115200] -Speed=9600 - - - -## Serial VFD driver ## -## Drives various (see below) serial 5x7dot VFD's. ## -[serialVFD] - -# Specifies the displaytype.[default: 0] -# 0 NEC (FIPC8367 based) VFDs. -# 1 KD Rev 2.1. -# 2 Noritake VFDs (*). -# 3 Futaba VFDs -# 4 IEE S03601-95B -# 5 IEE S03601-96-080 (*) -# 6 Futaba NA202SD08FA (allmost IEE compatible) -# 7 Samsung 20S207DA4 and 20S207DA6 -# 8 Nixdorf BA6x / VT100 -# (* most should work, not tested yet.) -Type=0 - -# "no" if display connected serial, "yes" if connected parallel. [default: no] -# I.e. serial by default -use_parallel=no - -# Number of Custom-Characters. default is display type dependent -#Custom-Characters=0 - -# Portaddress where the LPT is. Used in parallel mode only. Usual values are -# 0x278, 0x378 and 0x3BC. -Port=0x378 - -# Set parallel port timing delay (us). Used in parallel mode only. -# [default: 2; legal: 0 - 255] -#PortWait=2 - -# Device to use in serial mode. Usual values are /dev/ttyS0 and /dev/ttyS1 -Device=/dev/ttyS1 - -# Specifies the size of the VFD. -Size=20x2 - -# Set the initial brightness [default: 1000; legal: 0 - 1000] -# (4 steps 0-250, 251-500, 501-750, 751-1000) -Brightness=1000 -# Set the initial off-brightness [default: 0; legal: 0 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -# (4 steps 0-250, 251-500, 501-750, 751-1000) -OffBrightness=0 - -# set the serial port speed [default: 9600; legal: 1200, 2400, 9600, 19200, 115200] -Speed=9600 - -# enable ISO 8859 1 compatibility [default: yes; legal: yes, no] -#ISO_8859_1=yes - - - -## shuttleVFD driver ## -[shuttleVFD] -# No options - - - -## stv5730 driver ## -[stv5730] - -# Port the device is connected to [default: 0x378] -Port=0x378 - - -[SureElec] - -# Port the device is connected to (by default first USB serial port) -Device=/dev/ttyUSB0 - -# Edition level of the device (can be 1, 2 or 3) [default: 2] -#Edition=1 - -# set display size -# Note: The size can be obtained directly from device for edition 2 & 3. -#Size=16x2 - -# Set the initial contrast [default: 480; legal: 0 - 1000] -#Contrast=200 - -# Set the initial brightness [default: 480; legal: 1 - 1000] -#Brightness=480 - -# Set the initial off-brightness [default: 100; legal: 1 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -#OffBrightness=100 - - -## SVGAlib driver ## -[svga] - -# svgalib mode to use [default: G320x240x256 ] -# legal values are supported svgalib modes -#Mode=G640x480x256 - -# set display size [default: 20x4] -Size=20x4 - -# Set the initial contrast [default: 500; legal: 0 - 1000] -# Can be set but does not change anything internally -Contrast=500 - -# Set the initial brightness [default: 1000; legal: 1 - 1000] -Brightness=1000 - -# Set the initial off-brightness [default: 500; legal: 1 - 1000] -# This value is used when the display is normally -# switched off in case LCDd is inactive -OffBrightness=500 - - - -## Text driver ## -[text] -# Set the display size [default: 20x4] -Size=20x4 - - - -## Toshiba T6963 driver ## -[t6963] - -# set display size in pixels [default: 128x64] -Size=128x64 - -# port to use [default: 0x378; legal: 0x200 - 0x400] -Port=0x378 - -# Use LPT port in bi-directional mode. This should work on most LPT port and -# is required for proper timing! [default: yes; legal: yes, no] -#bidirectional=yes - -# Insert additional delays into reads / writes. [default: no; legal: yes, no] -#delayBus=no - -# Clear graphic memory on start-up. [default: no; legal: yes, no] -#ClearGraphic=no - - - -## Tyan Barebones LCD driver (GS10 & GS12 series) ## -[tyan] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the communication speed [default: 9600; legal: 4800, 9600] -Speed=9600 - -# set display size [default: 16x2] -Size=16x2 - - - -## ELV ula200 driver ## -[ula200] - -# Select the LCD size [default: 20x4] -Size=20x4 - -# If you have a non standard keypad you can associate any keystrings to keys. -# There are 6 input key in the CwLnx hardware that generate characters -# from 'A' to 'F'. -# -# The following it the built-in default mapping hardcoded in the driver. -# You can leave those unchanged if you have a standard keypad. -# You can change it if you want to report other keystrings or have a non -# standard keypad. -# KeyMap_A=Up -# KeyMap_B=Down -# KeyMap_C=Left -# KeyMap_D=Right -# KeyMap_E=Enter -# KeyMap_F=Escape - - - -## Wirz SLI LCD driver ## -[sli] - -# Select the output device to use [default: /dev/lcd] -Device=/dev/lcd - -# Set the communication speed [default: 19200; legal: 1200, 2400, 9600, 19200, -# 38400, 57600, 115200] -Speed=19200 - - - -## vlsys_m428 for VFD/IR combination in Moneual MonCaso 320 ## -[vlsys_m428] - -# Select the output device to use [default: /dev/ttyUSB0] -#Device=/dev/ttyUSB0 - - - -## OnScreen Display using libxosd ## -[xosd] - -# set display size [default: 20x4] -Size=20x4 - -# Offset in pixels from the top-left corner of the monitor [default: 0x0] -Offset=200x200 - -# X font to use, in XLFD format, as given by "xfontsel" -Font=-*-terminus-*-r-*-*-*-320-*-*-*-*-* - -## Y.A.R.D.2 LCD section -[yard2LCD] -Size=20x4 -# If rendering rate is too high, change in server\main.h #define RENDER_FREQ 8 to "1" - -# EOF diff --git a/data/templates/system-display/lcdproc.conf.tmpl b/data/templates/system-display/lcdproc.conf.tmpl deleted file mode 100644 index 92aee8efe..000000000 --- a/data/templates/system-display/lcdproc.conf.tmpl +++ /dev/null @@ -1,173 +0,0 @@ -### autogenerated by system-display.py ### - -# system display show host (CPU|SMP-CPU|CPU-Graph|Load|Memory|Proc-Size|Disk|Uptime) -# network interface alias -# units (bps|Bps|pps) -# clock (big|mini|date-time) - -# LCDproc client configuration file - -## general options ## -[lcdproc] -# address of the LCDd server to connect to -Server=127.0.0.1 - -# Port of the server to connect to -Port=13666 - -# set reporting level -#ReportLevel=2 - -# report to to syslog ? -ReportToSyslog=true - -# run in foreground [default: false; legal: true, false] -#Foreground=true - -# PidFile location when running as daemon [default: /var/run/lcdproc.pid] -#PidFile=/var/run/lcdproc.pid - -# slow down initial announcement of modes (in 1/100s) -#delay=2 - - -## screen specific configuration options ## -{%- if show %} -# display name for the main menu [default: LCDproc HOST] -DisplayName="{%- if show['title'] %}{{ show['title'] }}{%- else %}VyOS{%- endif %}" - -{%- if show['host'] %} - -[CPU] -# Show screen -Active={%- if 'cpu' in show['host'] %}true{%- else %}false{%- endif %} -OnTime=1 -OffTime=2 -ShowInvisible=false - -[SMP-CPU] -# Show screen -Active={%- if 'cpu-all' in show['host'] %}true{%- else %}false{%- endif %} - -[Memory] -# Show screen -Active={%- if 'memory' in show['host'] %}true{%- else %}false{%- endif %} - -[Load] -# Show screen -Active={%- if 'load-hist' in show['host'] %}true{%- else %}false{%- endif %} -# Min Load Avg at which the backlight will be turned off [default: 0.05] -LowLoad=0.05 -# Max Load Avg at which the backlight will start blinking [default: 1.3] -HighLoad=1.3 - -[Uptime] -# Show screen -Active={%- if 'uptime' in show['host'] %}true{%- else %}false{%- endif %} - -[CPUGraph] -# Show screen -Active={%- if 'cpu-hist' in show['host'] %}true{%- else %}false{%- endif %} - -[ProcSize] -# Show screen -Active={%- if 'proc' in show['host'] %}true{%- else %}false{%- endif %} - -[Disk] -# Show screen -Active={%- if 'disk' in show['host'] %}true{%- else %}false{%- endif %} -{%- else %} {# if show['host'] #} -{# Turn off sections that default active #} - -[CPU] -Active=false - -[Memory] -Active=false - -[Load] -Active=false - -{%- endif %} {# if show['host'] #} - -[TimeDate] -# Show screen -Active={%- if show['clock'] == 'date-time' %}true{%- else %}false{%- endif %} -# time format [default: %H:%M:%S; legal: see strftime(3)] -TimeFormat="%H:%M:%S" -# date format [default: %x; legal: see strftime(3)] -DateFormat="%x" - -[BigClock] -# Show screen -Active={%- if show['clock'] == 'big' %}true{%- else %}false{%- endif %} - -[MiniClock] -# Show screen -Active={%- if show['clock'] == 'mini' %}true{%- else %}false{%- endif %} -# time format [default: %H:%M; legal: see strftime(3)] -TimeFormat="%H:%M" - -{%- if show['network'] %} -[Iface] -# Show screen -Active={%- if show['network']['interface'] %}true{%- else %}false{%- endif %} -{%- for i in show['network']['interface'] %} -# Show stats for Interface {{ i }} -Interface{{ loop.index0 }}={{ i }} -{%- if show['network']['interface'][i]['alias'] %} -# Interface alias name to display [default: ] -Alias{{ loop.index0 }}={{ show['network']['interface'][i]['alias'] }} -{%- endif %} -{%- endfor %} - -# Units to display [default: byte; legal: byte, bit, packet] -{%- if show['network']['units'] == 'bps' %} -unit=bit -{%- elif show['network']['units'] == 'Bps'%} -unit=byte -{%- elif show['network']['units'] == 'pps' %} -unit=packet -{%- else %} -unit=bit -{%- endif %} -# add screen with transferred traffic -#transfer=TRUE -{%- endif %} {# if show['network'] #} - -{%- else %}{# if show #} -{# Turn off sections that default active #} - -[CPU] -Active=false - -[Memory] -Active=false - -[Load] -Active=false - -[TimeDate] -Active=false -{%- endif %}{# if show #} - -[Battery] -# Show screen -Active=false - -[About] -# Show screen -Active=false - -[OldTime] -# Show screen -Active=false -# time format [default: %H:%M:%S; legal: see strftime(3)] -TimeFormat="%H:%M:%S" -# date format [default: %x; legal: see strftime(3)] -DateFormat="%x" -# Display the title bar in two-line mode. Note that with four lines or more -# the title is always shown. [default: true; legal: true, false] -#ShowTitle=false - -# EOF diff --git a/interface-definitions/system-display.xml.in b/interface-definitions/system-display.xml.in deleted file mode 100644 index fbd897996..000000000 --- a/interface-definitions/system-display.xml.in +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - - - System display LCD/VFD/LED - 400 - - - - - - Model of the display attached to this system [REQUIRED] - - sdec ezio test - - - (sdec|ezio|test) - - Invalid system display model - - sdec - Display model for Lanner, Watchguard, Nexcom NSA, Sophos UTM appliances with built-in SDEC LCD - - - ezio - Display model for Portwell, Caswell appliances with built-in EZIO-100 or EZIO-300 LCD - - - test - Test model for USB CrystalFonz CF533 - - - - - - - Disable sytem display - - - - - - Select the screens for the system display [REQUIRED] - - - - - - - Select host screens for the system display - - cpu cpu-all cpu-hist disk load-hist memory proc uptime - - - (cpu|cpu-all|cpu-hist|disk|load-hist|memory|proc|uptime) - - Invalid host screen - - cpu - Detailed CPU usage - - - cpu-all - CPU usage overview (one line per CPU) - - - cpu-hist - CPU usage histogram - - - disk - File systems fill level - - - load-hist - Load histogram - - - memory - Memory and swap usage - - - proc - Top processes by size - - - uptime - System uptime - - - - - - - Network settings for system display - - - - - - Show network traffic on the system display [Max 3 interfaces] - - - - - - - - Interface alias - - [A-Za-z0-9]{1,10} - - Invalid alias, must be 1 to 10 char or digit - - - - - - - - Unit for network details - - bps Bps pps - - - (bps|Bps|pps) - - Invalid network detail unit - - bps - Bit(s) per second - - - Bps - Byte(s) per second - - - pps - Packet(s) per second - - - - - - - - - - Show a clock on the system display - - big mini date-time - - - (big|mini|date-time) - - Invalid clock format - - big - Multi-line clock - - - mini - Minimal clock - - - date-time - Clock with Date and Time - - - - - - - Screen title to show on the system display - - [A-Za-z0-9]{1,16} - - Invalid title, must be 1 to 16 char or digit - - - - - - - - Time in sec to show each screen on the system display - - 1-30 - Numer of seconds - - - - - - - - - - Message to show when system display first starts - - .{1,16} - - Hello message must be 1 to 16 char - - - - - - Message to show when system display stops - - .{1,16} - - Bye message must be 1 to 16 char - - - - - - - diff --git a/interface-definitions/system-lcd.xml.in b/interface-definitions/system-lcd.xml.in new file mode 100644 index 000000000..ad59acb6b --- /dev/null +++ b/interface-definitions/system-lcd.xml.in @@ -0,0 +1,62 @@ + + + + + + + System LCD display + 100 + + + + + Model of the display attached to this system [REQUIRED] + + CFA-533 CFA-631 CFA-633 CFA-635 + + + CFA-533 + Crystalfontz CFA-533 + + + CFA-631 + Crystalfontz CFA-631 + + + CFA-633 + Crystalfontz CFA-633 + + + CFA-635 + Crystalfontz CFA-635 + + + ^(CFA-533|CFA-631|CFA-633|CFA-635)$ + + + + + + Physical device used by LCD display + + + + + + ttySXX + TTY device name, regular serial port + + + usbNbXpY + TTY device name, USB based + + + ^(ttyS[0-9]+|usb[0-9]+b.*)$ + + + + + + + + diff --git a/python/vyos/util.py b/python/vyos/util.py index 7078762df..c07fef599 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -661,3 +661,15 @@ def check_kmod(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 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 diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 4081be3c9..6d168d918 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -16,7 +16,6 @@ import os -from fnmatch import fnmatch from sys import exit from vyos.config import Config @@ -25,22 +24,13 @@ from vyos.configverify import verify_vrf from vyos.template import render from vyos.util import call from vyos.util import check_kmod +from vyos.util import find_device_file from vyos import ConfigError from vyos import airbag airbag.enable() k_mod = ['option', 'usb_wwan', 'usbserial'] -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 """ - 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 get_config(): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the diff --git a/src/conf_mode/system-display.py b/src/conf_mode/system-display.py deleted file mode 100755 index 3eafc30c0..000000000 --- a/src/conf_mode/system-display.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2020 Francois Mertz fireboxled at 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 . - -import os - -from sys import exit - -from vyos.config import Config -from vyos import ConfigError -from vyos.util import run -from vyos.template import render - -from vyos import airbag -airbag.enable() - -def get_config(): - conf = Config() - base = ['system', 'display'] - display = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # Return a (possibly empty) configuration dictionary - return display - -def verify(config_dict): - if not config_dict: - return None - - if 'model' not in config_dict: - raise ConfigError('Display model is [REQUIRED]') - - if ( 'show' not in config_dict - or ( 'clock' not in config_dict['show'] - and 'network' not in config_dict['show'] - and 'host' not in config_dict['show'] - ) - ): - raise ConfigError('Display show must have a clock, host or network') - - if ( 'network' in config_dict['show'] - and 'interface' not in config_dict['show']['network'] - ): - raise ConfigError('Display show network must have an interface') - - if ( 'network' in config_dict['show'] - and 'interface' in config_dict['show']['network'] - and len(config_dict['show']['network']['interface']) > 3 - ): - raise ConfigError('Display show network cannot have > 3 interfaces') - - return None - -def generate(config_dict): - if not config_dict: - return None - # Render config file for daemon LCDd - render('/run/LCDd/LCDd.lo.conf', 'system-display/LCDd.conf.tmpl', config_dict) - # Render config file for client lcdproc - render('/run/lcdproc/lcdproc.lo.conf', 'system-display/lcdproc.conf.tmpl', config_dict) - - return None - -def apply(config_dict): - # Stop client - run('systemctl stop lcdproc@lo.service') - - if not config_dict or 'disabled' in config_dict: - # Stop server - run('systemctl stop LCDd@lo.service') - return None - - # Restart server - run('systemctl restart LCDd@lo.service') - # Start client - run('systemctl start lcdproc@lo.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_lcd.py b/src/conf_mode/system_lcd.py new file mode 100755 index 000000000..0ad1318f0 --- /dev/null +++ b/src/conf_mode/system_lcd.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# +# Copyright 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 . + +import os + +from sys import exit + +from vyos.config import Config +from vyos.util import call +from vyos.util 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(): + 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 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.tmpl', lcd, trim_blocks=True) + # Render config file for client lcdproc + render(lcdproc_conf, 'lcd/lcdproc.conf.tmpl', lcd, trim_blocks=True) + + 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/etc/systemd/system/LCDd.service.d/override.conf b/src/etc/systemd/system/LCDd.service.d/override.conf new file mode 100644 index 000000000..5f3f0dc95 --- /dev/null +++ b/src/etc/systemd/system/LCDd.service.d/override.conf @@ -0,0 +1,8 @@ +[Unit] +After= +After=vyos-router.service + +[Service] +ExecStart= +ExecStart=/usr/sbin/LCDd -c /run/LCDd/LCDd.conf + diff --git a/src/etc/systemd/system/hostapd@.service.d/override.conf b/src/etc/systemd/system/hostapd@.service.d/override.conf index bb8e81d7a..b03dbc299 100644 --- a/src/etc/systemd/system/hostapd@.service.d/override.conf +++ b/src/etc/systemd/system/hostapd@.service.d/override.conf @@ -3,8 +3,7 @@ After= After=vyos-router.service [Service] -WorkingDirectory=/run/hostapd -EnvironmentFile= +WorkingDirectory=/run/LCDd ExecStart= -ExecStart=/usr/sbin/hostapd -B -P /run/hostapd/%i.pid /run/hostapd/%i.conf -PIDFile=/run/hostapd/%i.pid +ExecStart=/usr/sbin/LCDd -s 1 -f -c /run/LCDd/LCDd.conf + diff --git a/src/systemd/LCDd@.service b/src/systemd/LCDd@.service deleted file mode 100644 index a4604cf21..000000000 --- a/src/systemd/LCDd@.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=LCD display daemon on %I -Documentation=man:LCDd(8) http://www.lcdproc.org/ - -[Service] -User=root -ExecStart=/usr/sbin/LCDd -s 1 -f -c /run/LCDd/LCDd.%I.conf - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/src/systemd/lcdproc.service b/src/systemd/lcdproc.service new file mode 100644 index 000000000..5aa99ec78 --- /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 +After=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/lcdproc@.service b/src/systemd/lcdproc@.service deleted file mode 100644 index 9a1723dba..000000000 --- a/src/systemd/lcdproc@.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=LCDproc system status information viewer on %I -Documentation=man:lcdproc(8) http://www.lcdproc.org/ - -[Service] -User=root -ExecStart=/usr/bin/lcdproc -f -c /run/lcdproc/lcdproc.%I.conf - -[Install] -WantedBy=multi-user.target \ No newline at end of file -- cgit v1.2.3 From b8aca00fc5454f3afdb690ca7eb30e49fac48848 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Aug 2020 17:07:14 +0200 Subject: pppoe-server: T2796: at least one server interface must be defined Add missing verify() check that at least one interface must be defined to listen to PPPoE data. --- src/conf_mode/service_pppoe-server.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index a8357f653..39d34a7e2 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -421,6 +421,9 @@ def verify(pppoe): if len(pppoe['dnsv6']) > 3: raise ConfigError('Not more then three IPv6 DNS name-servers can be configured') + if not pppoe['interfaces']: + raise ConfigError('At least one listen interface must be defined!') + # local ippool and gateway settings config checks if pppoe['client_ip_subnets'] or pppoe['client_ip_pool']: if not pppoe['ppp_gw']: -- cgit v1.2.3 From 39fe36151eac3652ec9a2283974c19efdd728dc1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Aug 2020 18:08:34 +0200 Subject: vyos.configdict: keep is_member import as local as possible --- python/vyos/configdict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 126d6195a..010eda45c 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -23,7 +23,6 @@ from enum import Enum from copy import deepcopy from vyos import ConfigError -from vyos.validate import is_member def retrieve_config(path_hash, base_path, config): """ @@ -203,6 +202,7 @@ def get_interface_dict(config, base, ifname=''): Will return a dictionary with the necessary interface configuration """ from vyos.xml import defaults + from vyos.validate import is_member if not ifname: # determine tagNode instance -- cgit v1.2.3 From 10fd2aa6ab205ef22e0bd3eef3767cb1ab00e164 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Aug 2020 19:17:29 +0200 Subject: vyos.configverify: no need to call .keys() when searching dict --- python/vyos/configverify.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index bb590a514..d1519b0ac 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -29,11 +29,11 @@ def verify_vrf(config): recurring validation of VRF configuration. """ from netifaces import interfaces - if 'vrf' in config.keys(): + if 'vrf' in config: if config['vrf'] not in interfaces(): raise ConfigError('VRF "{vrf}" does not exist'.format(**config)) - if 'is_bridge_member' in config.keys(): + 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)) @@ -57,7 +57,7 @@ def verify_bridge_delete(config): perform recurring validation of IP address assignmenr when interface also is part of a bridge. """ - if 'is_bridge_member' in config.keys(): + if 'is_bridge_member' in config: raise ConfigError( 'Interface "{ifname}" cannot be deleted as it is a ' 'member of bridge "{is_bridge_member}"!'.format(**config)) @@ -101,20 +101,20 @@ def verify_vlan_config(config): recurring validation of interface VLANs """ # 802.1q VLANs - for vlan in config.get('vif', {}).keys(): + for vlan in config.get('vif', {}): vlan = config['vif'][vlan] verify_dhcpv6(vlan) verify_address(vlan) verify_vrf(vlan) # 802.1ad (Q-in-Q) VLANs - for vlan in config.get('vif_s', {}).keys(): + for vlan in config.get('vif_s', {}): vlan = config['vif_s'][vlan] verify_dhcpv6(vlan) verify_address(vlan) verify_vrf(vlan) - for vlan in config.get('vif_s', {}).get('vif_c', {}).keys(): + for vlan in config.get('vif_s', {}).get('vif_c', {}): vlan = config['vif_c'][vlan] verify_dhcpv6(vlan) verify_address(vlan) -- cgit v1.2.3 From 1e44607c15002e111bf65573207877cc87f13ab7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Aug 2020 19:17:35 +0200 Subject: pseudo-ethernet: T2800: source-interface must not be member of a bridge --- src/conf_mode/interfaces-pseudo-ethernet.py | 30 +++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index 4afea2b3a..fe2d7b1be 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -28,6 +28,7 @@ from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_source_interface from vyos.configverify import verify_vlan_config from vyos.ifconfig import MACVLANIf +from vyos.validate import is_member from vyos import ConfigError from vyos import airbag @@ -35,8 +36,8 @@ airbag.enable() def get_config(): """ - Retrive CLI config as dictionary. Dictionary can never be empty, as at least the - interface name will be added or a deleted flag + Retrive CLI config as dictionary. Dictionary can never be empty, as at + least the interface name will be added or a deleted flag """ conf = Config() base = ['interfaces', 'pseudo-ethernet'] @@ -46,6 +47,17 @@ def get_config(): if mode: peth.update({'mode_old' : mode}) + # Check if source-interface is member of a bridge device + if 'source_interface' in peth: + bridge = is_member(conf, peth['source_interface'], 'bridge') + if bridge: + peth.update({'source_interface_is_bridge_member' : bridge}) + + # Check if we are a member of a bond device + bond = is_member(conf, peth['source_interface'], 'bonding') + if bond: + peth.update({'source_interface_is_bond_member' : bond}) + return peth def verify(peth): @@ -57,6 +69,16 @@ def verify(peth): verify_vrf(peth) verify_address(peth) + if 'source_interface_is_bridge_member' in peth: + raise ConfigError( + 'Source interface "{source_interface}" can not be used as it is already a ' + 'member of bridge "{source_interface_is_bridge_member}"!'.format(**peth)) + + if 'source_interface_is_bond_member' in peth: + raise ConfigError( + 'Source interface "{source_interface}" can not be used as it is already a ' + 'member of bond "{source_interface_is_bond_member}"!'.format(**peth)) + # use common function to verify VLAN configuration verify_vlan_config(peth) return None @@ -71,8 +93,8 @@ def apply(peth): return None # Check if MACVLAN interface already exists. Parameters like the underlaying - # source-interface device or mode can not be changed on the fly and the interface - # needs to be recreated from the bottom. + # source-interface device or mode can not be changed on the fly and the + # interface needs to be recreated from the bottom. if 'mode_old' in peth: MACVLANIf(peth['ifname']).remove() -- cgit v1.2.3 From 9455ae27d5ea9517867c2dff3142db31873cbfb4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Aug 2020 21:40:47 +0200 Subject: ethernet: check interface disable state --- scripts/cli/test_interfaces_ethernet.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/scripts/cli/test_interfaces_ethernet.py b/scripts/cli/test_interfaces_ethernet.py index c31a606da..761ec7506 100755 --- a/scripts/cli/test_interfaces_ethernet.py +++ b/scripts/cli/test_interfaces_ethernet.py @@ -42,5 +42,27 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): if not '.' in tmp: self._interfaces.append(tmp) + def test_dhcp_disable(self): + """ + When interface is configured as admin down, it must be admin down even + """ + for interface in self._interfaces: + self.session.set(self._base_path + [interface, 'disable']) + for option in self._options.get(interface, []): + self.session.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://phabricator.vyos.net/T2767 + self.session.set(self._base_path + [interface, 'address', 'dhcp']) + + self.session.commit() + + # Validate interface state + for interface in self._interfaces: + with open(f'/sys/class/net/{interface}/flags', 'r') as f: + flags = f.read() + self.assertEqual(int(flags, 16) & 1, 0) + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 910fc06cfd4160705f12d890ca6a6b8436326a67 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Aug 2020 22:54:23 +0200 Subject: dhclient: T2277: retrieve VRF inform from sysfs rather then iproute2 --- src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper index 60e001af7..1e102c2d7 100644 --- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper +++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper @@ -20,7 +20,7 @@ function iptovtysh () { local VTYSH_NETADDR="" local VTYSH_GATEWAY="" local VTYSH_DEV="" - local VTYSH_VRF_NAME=$(ip -d link show dev $interface | grep $interface | awk '{print $9}') + local VTYSH_VRF_NAME=$(basename /sys/class/net/eth1/upper_* | sed -e 's/upper_//') # convert default route to 0.0.0.0/0 if [ "$4" == "default" ] ; then VTYSH_NETADDR="0.0.0.0/0" @@ -42,12 +42,11 @@ function iptovtysh () { VTYSH_DEV=$8 fi - # check if vrf is present - if [ $(ip -d link show dev $interface | grep vrf | wc -l) -eq 0 ]; then - VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE" - elif [ $(ip -d link show dev $interface | grep vrf | wc -l) -eq 1 ]; then - VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE vrf $VTYSH_VRF_NAME" + # Add route to VRF routing table + if [ -n $VTYSH_VRF_NAME ]; then + VTYSH_VRF="vrf $VTYSH_VRF_NAME" fi + VTYSH_CMD="ip route $VTYSH_NETADDR $VTYSH_GATEWAY $VTYSH_DEV tag $VTYSH_TAG $VTYSH_DISTANCE $VTYSH_VRF" # delete route if the command is "del" if [ "$3" == "del" ] ; then -- cgit v1.2.3 From 3104c0ea8284ac5039e1dbab4a3ccd193e748ede Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Aug 2020 12:37:50 +0200 Subject: ping: op-mode: T2762: always run in default VRF unless defined otherwise When connected to VyOS by SSH through any VRF, every command is executed in the VRF context thus e.g. ping will run in VRF context but no VRF was defined on the CLI. ping should always run in the default VRF no matter where it is executed, unless a VRF instance is explicitly defined by CLI. --- src/op_mode/ping.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/op_mode/ping.py b/src/op_mode/ping.py index e56952c38..29b430d53 100755 --- a/src/op_mode/ping.py +++ b/src/op_mode/ping.py @@ -118,7 +118,8 @@ options = { 'vrf': { 'ping': 'sudo ip vrf exec {value} {command}', 'type': '', - 'help': 'Use specified VRF table' + 'help': 'Use specified VRF table', + 'dflt': 'default', }, 'verbose': { 'ping': '{command} -v', @@ -207,6 +208,11 @@ if __name__ == '__main__': sys.stdout.write(options[matched[0]]['type']) 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 socket.gaierror: -- cgit v1.2.3 From b17b8a0129f42c757c1e9a740664406e8da3dd42 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Aug 2020 14:27:03 +0200 Subject: dhclient: T2277: remove VRF route when DHCP client exits --- src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper | 2 +- src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper index 1e102c2d7..d1161e704 100644 --- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper +++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper @@ -20,7 +20,6 @@ function iptovtysh () { local VTYSH_NETADDR="" local VTYSH_GATEWAY="" local VTYSH_DEV="" - local VTYSH_VRF_NAME=$(basename /sys/class/net/eth1/upper_* | sed -e 's/upper_//') # convert default route to 0.0.0.0/0 if [ "$4" == "default" ] ; then VTYSH_NETADDR="0.0.0.0/0" @@ -43,6 +42,7 @@ function iptovtysh () { fi # Add route to VRF routing table + local VTYSH_VRF_NAME=$(basename /sys/class/net/$VTYSH_DEV/upper_* | sed -e 's/upper_//') if [ -n $VTYSH_VRF_NAME ]; then VTYSH_VRF="vrf $VTYSH_VRF_NAME" 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 index 01981ad04..b768e1ae5 100644 --- a/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup +++ b/src/etc/dhcp/dhclient-exit-hooks.d/01-vyos-cleanup @@ -15,8 +15,14 @@ if [[ $reason =~ (EXPIRE|FAIL|RELEASE|STOP) ]]; then # try to delete default ip route for router in $old_routers; do - logmsg info "Deleting default route: via $router dev ${interface}" - ip -4 route del default via $router dev ${interface} + # check if we are bound to a VRF + local vrf_name=$(basename /sys/class/net/${interface}/upper_* | sed -e 's/upper_//') + if [ -n $vrf_name ]; then + vrf="vrf $vrf_name" + fi + + logmsg info "Deleting default route: via $router dev ${interface} ${vrf}" + ip -4 route del default via $router dev ${interface} ${vrf} done # delete rfc3442 routes -- cgit v1.2.3 From b9658cf8fa85d5d2bdc503d10340dc82c9c2a8c0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 18 Aug 2020 16:39:50 +0200 Subject: ifconfig: T2653: bugfix when removing DHCP address DHCP service was not stopped when an DHCP address got removed from the interface. DHCP service is now always stopped if it is not configured explicitly. --- python/vyos/ifconfig/interface.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 36f258301..892495dec 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -901,6 +901,11 @@ class Interface(Control): if isinstance(new_addr, str): new_addr = [new_addr] + # ensure DHCP/DHCPv6 is stopped (when not configured explicitly) + for proto in ['dhcp', 'dhcpv6']: + if proto not in new_addr: + self.del_addr(proto) + # 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 cur_addr = self.get_addr() -- cgit v1.2.3 From b1675e420479cd6b582c66c0902d6e326340658f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 18 Aug 2020 16:40:51 +0200 Subject: lcd: T2564: add support for Lanner, Watchguard, Symantec boxes --- data/templates/lcd/LCDd.conf.tmpl | 12 ++++++++++-- interface-definitions/system-lcd.xml.in | 16 ++++++++++------ src/conf_mode/system_lcd.py | 4 ++++ src/systemd/lcdproc.service | 4 ++-- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/data/templates/lcd/LCDd.conf.tmpl b/data/templates/lcd/LCDd.conf.tmpl index da749d04a..bde177b6a 100644 --- a/data/templates/lcd/LCDd.conf.tmpl +++ b/data/templates/lcd/LCDd.conf.tmpl @@ -48,8 +48,12 @@ DriverPath=/usr/lib/x86_64-linux-gnu/lcdproc/ # sed1520, serialPOS, serialVFD, shuttleVFD, sli, stv5730, svga, t6963, # text, tyan, ula200, vlsys_m428, xosd, yard2LCD -{% if model is defined and model.startswith('CFA-') %} +{% if model is defined %} +{% if model.startswith('cfa-') %} Driver=CFontzPacket +{% elif model == 'sdec' %} +Driver=sdeclcd +{% endif %} {% endif %} # Tells the driver to bind to the given interface. [default: 127.0.0.1] @@ -112,7 +116,7 @@ Heartbeat=off TitleSpeed=10 {% if model is defined and model is not none %} -{% if model.startswith('CFA-') %} +{% if model.startswith('cfa-') %} ## CrystalFontz packet driver (for CFA533, CFA631, CFA633 & CFA635) ## [CFontzPacket] Model={{ model.split('-')[1] }} @@ -122,5 +126,9 @@ Brightness=500 OffBrightness=50 Reboot=yes USB=yes +{% elif model == 'sdec' %} +## SDEC driver for Lanner, Watchguard, Sophos sppliances ## +[sdeclcd] +# No options {% endif %} {% endif %} diff --git a/interface-definitions/system-lcd.xml.in b/interface-definitions/system-lcd.xml.in index ad59acb6b..36116ae1b 100644 --- a/interface-definitions/system-lcd.xml.in +++ b/interface-definitions/system-lcd.xml.in @@ -12,26 +12,30 @@ Model of the display attached to this system [REQUIRED] - CFA-533 CFA-631 CFA-633 CFA-635 + cfa-533 cfa-631 cfa-633 cfa-635 sdec - CFA-533 + cfa-533 Crystalfontz CFA-533 - CFA-631 + cfa-631 Crystalfontz CFA-631 - CFA-633 + cfa-633 Crystalfontz CFA-633 - CFA-635 + cfa-635 Crystalfontz CFA-635 + + sdec + Lanner, Watchguard, Nexcom NSA, Sophos UTM appliances + - ^(CFA-533|CFA-631|CFA-633|CFA-635)$ + ^(cfa-533|cfa-631|cfa-633|cfa-635|sdec)$ diff --git a/src/conf_mode/system_lcd.py b/src/conf_mode/system_lcd.py index 0ad1318f0..31a09252d 100755 --- a/src/conf_mode/system_lcd.py +++ b/src/conf_mode/system_lcd.py @@ -41,6 +41,10 @@ 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!') diff --git a/src/systemd/lcdproc.service b/src/systemd/lcdproc.service index 5aa99ec78..ef717667a 100644 --- a/src/systemd/lcdproc.service +++ b/src/systemd/lcdproc.service @@ -1,8 +1,8 @@ [Unit] Description=LCDproc system status information viewer on %I Documentation=man:lcdproc(8) http://www.lcdproc.org/ -After=vyos-router.service -After=LCDd.service +After=vyos-router.service LCDd.service +Requires=LCDd.service [Service] User=root -- cgit v1.2.3 From 3282250a9a7c7a1df5c32e76fdc0024fd1ce9d9f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 18 Aug 2020 17:36:08 +0200 Subject: dhcpv6: T2510: fix missing id-assoc for non temporary address --- data/templates/dhcp-client/ipv6.tmpl | 42 ++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl index 5c0cea280..9673f302b 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -4,45 +4,41 @@ interface {{ ifname }} { request domain-name-servers; request domain-name; -{% if dhcpv6_options is defined %} -{% if dhcpv6_options.parameters_only is defined %} +{% if dhcpv6_options is defined and dhcpv6_options.parameters_only is defined %} information-only; -{% endif %} -{% if dhcpv6_options.temporary is not defined %} +{% endif %} +{% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} send ia-na 1; # non-temporary address -{% endif %} -{% if dhcpv6_options.prefix_delegation is defined %} +{% endif %} +{% if dhcpv6_options is defined and dhcpv6_options.prefix_delegation is defined %} send ia-pd 2; # prefix delegation -{% endif %} -{% endif %} +{% endif %} }; -{% if dhcpv6_options is defined %} -{% if dhcpv6_options.temporary is not defined %} +{% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} id-assoc na 1 { # Identity association NA }; -{% endif %} +{% endif %} -{% if dhcpv6_options.prefix_delegation is defined %} +{% if dhcpv6_options is defined and dhcpv6_options.prefix_delegation is defined %} id-assoc pd 2 { -{% if dhcpv6_options.prefix_delegation.length is defined %} +{% if dhcpv6_options.prefix_delegation.length is defined %} prefix ::/{{ dhcpv6_options.prefix_delegation.length }} infinity; -{% endif %} -{% for interface in dhcpv6_options.prefix_delegation.interface %} +{% endif %} +{% for interface in dhcpv6_options.prefix_delegation.interface %} prefix-interface {{ interface }} { -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_id is defined %} +{% if dhcpv6_options.prefix_delegation.interface[interface].sla_id is defined %} sla-id {{ dhcpv6_options.prefix_delegation.interface[interface].sla_id }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_len is defined %} +{% endif %} +{% if dhcpv6_options.prefix_delegation.interface[interface].sla_len is defined %} sla-len {{ dhcpv6_options.prefix_delegation.interface[interface].sla_len }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].address is defined %} +{% endif %} +{% if dhcpv6_options.prefix_delegation.interface[interface].address is defined %} ifid {{ dhcpv6_options.prefix_delegation.interface[interface].address }}; -{% endif %} +{% endif %} }; -{% endfor %} +{% endfor %} }; -{% endif %} {% endif %} -- cgit v1.2.3 From ed73f993107d9855f31236267b2b88b680ec8971 Mon Sep 17 00:00:00 2001 From: sever-sever Date: Tue, 18 Aug 2020 09:33:40 +0000 Subject: op-mode: T2791: Add monitor traceroute explicit for ipv4-ipv6 and vrf --- op-mode-definitions/traceroute.xml | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/op-mode-definitions/traceroute.xml b/op-mode-definitions/traceroute.xml index 6a44fc25b..1b619ed43 100644 --- a/op-mode-definitions/traceroute.xml +++ b/op-mode-definitions/traceroute.xml @@ -165,6 +165,24 @@ + + + IPv4 fully qualified domain name (FQDN) + + <fqdn> + + + /usr/bin/mtr -4 "$4" + + + + IPv6 fully qualified domain name (FQDN) + + <fqdn> + + + /usr/bin/mtr -6 "$4" + Monitor path to destination in realtime via given VRF @@ -173,8 +191,24 @@ - + + + IPv4 fully qualified domain name (FQDN) + + <fqdn> + + + sudo /usr/sbin/ip vrf exec "$4" /usr/bin/mtr -4 "$6" + + + + IPv6 fully qualified domain name (FQDN) + + <fqdn> + + + sudo /usr/sbin/ip vrf exec "$4" /usr/bin/mtr -6 "$6" + Track network path to specified node via given VRF -- cgit v1.2.3 From 865cba395223afaa263ff7581fb53ee509f36266 Mon Sep 17 00:00:00 2001 From: Eshenko Dmitriy Date: Wed, 19 Aug 2020 00:37:00 +0300 Subject: anyconnect: T2036: add anyconnect VPN support --- data/templates/ocserv/ocserv_config.tmpl | 82 +++++++ data/templates/ocserv/ocserv_passwd.tmpl | 6 + data/templates/ocserv/radius_conf.tmpl | 22 ++ data/templates/ocserv/radius_servers.tmpl | 7 + debian/control | 1 + interface-definitions/vpn_anyconnect.xml.in | 258 +++++++++++++++++++++ op-mode-definitions/anyconnect.xml | 20 ++ src/conf_mode/vpn_anyconnect.py | 132 +++++++++++ .../systemd/system/ocserv.service.d/override.conf | 14 ++ src/op_mode/anyconnect-control.py | 67 ++++++ 10 files changed, 609 insertions(+) create mode 100644 data/templates/ocserv/ocserv_config.tmpl create mode 100644 data/templates/ocserv/ocserv_passwd.tmpl create mode 100644 data/templates/ocserv/radius_conf.tmpl create mode 100644 data/templates/ocserv/radius_servers.tmpl create mode 100644 interface-definitions/vpn_anyconnect.xml.in create mode 100644 op-mode-definitions/anyconnect.xml create mode 100755 src/conf_mode/vpn_anyconnect.py create mode 100644 src/etc/systemd/system/ocserv.service.d/override.conf create mode 100755 src/op_mode/anyconnect-control.py diff --git a/data/templates/ocserv/ocserv_config.tmpl b/data/templates/ocserv/ocserv_config.tmpl new file mode 100644 index 000000000..6aaeff693 --- /dev/null +++ b/data/templates/ocserv/ocserv_config.tmpl @@ -0,0 +1,82 @@ +### generated by vpn_anyconnect.py ### + +tcp-port = {{ listen_ports.tcp }} +udp-port = {{ listen_ports.udp }} + +run-as-user = nobody +run-as-group = daemon + +{% if "radius" in authentication.mode %} +auth = "radius [config=/run/ocserv/radiusclient.conf]" +{% else %} +auth = "plain[/run/ocserv/ocpasswd]" +{% endif %} + +{% if ssl.cert_file %} +server-cert = {{ ssl.cert_file }} +{% endif %} + +{% if ssl.key_file %} +server-key = {{ ssl.key_file }} +{% endif %} + +{% if ssl.ca_cert_file %} +ca-cert = {{ ssl.ca_cert_file }} +{% endif %} + +socket-file = /run/ocserv/ocserv.socket +occtl-socket-file = /run/ocserv/occtl.socket +use-occtl = true +isolate-workers = true +keepalive = 300 +dpd = 60 +mobile-dpd = 300 +switch-to-tcp-timeout = 30 +tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-RSA:-VERS-SSL3.0:-ARCFOUR-128" +auth-timeout = 240 +idle-timeout = 1200 +mobile-idle-timeout = 1800 +min-reauth-time = 3 +cookie-timeout = 300 +rekey-method = ssl +try-mtu-discovery = true +cisco-client-compat = true +dtls-legacy = true + + +# The name to use for the tun device +device = sslvpn + +# An alternative way of specifying the network: +{% if network_settings %} +# DNS settings +{% if network_settings.name_server is string %} +dns = {{ network_settings.name_server }} +{% else %} +{% for dns in network_settings.name_server %} +dns = {{ dns }} +{% endfor %} +{% endif %} +# IPv4 network pool +{% if network_settings.client_ip_settings %} +{% if network_settings.client_ip_settings.subnet %} +ipv4-network = {{ network_settings.client_ip_settings.subnet }} +{% endif %} +{% endif %} +# IPv6 network pool +{% if network_settings.client_ipv6_pool %} +{% if network_settings.client_ipv6_pool.prefix %} +ipv6-network = {{ network_settings.client_ipv6_pool.prefix }} +ipv6-subnet-prefix = {{ network_settings.client_ipv6_pool.mask }} +{% endif %} +{% endif %} +{% endif %} + +{% if network_settings.push_route is string %} +route = {{ network_settings.push_route }} +{% else %} +{% for route in network_settings.push_route %} +route = {{ route }} +{% endfor %} +{% endif %} + diff --git a/data/templates/ocserv/ocserv_passwd.tmpl b/data/templates/ocserv/ocserv_passwd.tmpl new file mode 100644 index 000000000..ffadb4860 --- /dev/null +++ b/data/templates/ocserv/ocserv_passwd.tmpl @@ -0,0 +1,6 @@ +#:: +{% for user in username if username is defined %} +{% if not "disable" in username[user] %} +{{ user }}:*:{{ username[user].hash }} +{% endif %} +{% endfor %} \ No newline at end of file diff --git a/data/templates/ocserv/radius_conf.tmpl b/data/templates/ocserv/radius_conf.tmpl new file mode 100644 index 000000000..2d19306a0 --- /dev/null +++ b/data/templates/ocserv/radius_conf.tmpl @@ -0,0 +1,22 @@ +### generated by cpn_anyconnect.py ### +nas-identifier VyOS +{% for srv in server %} +{% if not "disable" in server[srv] %} +{% if "port" in server[srv] %} +authserver {{ srv }}:{{server[srv]["port"]}} +{% else %} +authserver {{ srv }} +{% endif %} +{% endif %} +{% endfor %} +radius_timeout {{ timeout }} +{% if source_address %} +bindaddr {{ source_address }} +{% else %} +bindaddr * +{% endif %} +servers /run/ocserv/radius_servers +dictionary /etc/radcli/dictionary +default_realm +radius_retries 3 +# \ No newline at end of file diff --git a/data/templates/ocserv/radius_servers.tmpl b/data/templates/ocserv/radius_servers.tmpl new file mode 100644 index 000000000..ba21fa074 --- /dev/null +++ b/data/templates/ocserv/radius_servers.tmpl @@ -0,0 +1,7 @@ +### generated by cpn_anyconnect.py ### +# server key +{% for srv in server %} +{% if not "disable" in server[srv] %} +{{ srv }} {{ server[srv].key }} +{% endif %} +{% endfor %} diff --git a/debian/control b/debian/control index 34ba4be8e..520401d57 100644 --- a/debian/control +++ b/debian/control @@ -107,6 +107,7 @@ Depends: python3, libatomic1, fastnetmon, libndp-tools, + ocserv, tcptraceroute Description: VyOS configuration scripts and data VyOS configuration scripts, interface definitions, and everything diff --git a/interface-definitions/vpn_anyconnect.xml.in b/interface-definitions/vpn_anyconnect.xml.in new file mode 100644 index 000000000..e74326986 --- /dev/null +++ b/interface-definitions/vpn_anyconnect.xml.in @@ -0,0 +1,258 @@ + + + + + + + SSL VPN AnyConnect + 901 + + + + + Authentication for remote access SSL VPN Server + + + + + Authentication mode used by this server + + local + Use local username/password configuration + + + radius + Use RADIUS server for user autentication + + + (local|radius) + + + local radius + + + + + + Local user authentication for SSL VPN server + + + + + User name for authentication + + + + + Option to disable a SSL VPN Server user + + + + + + Password for authentication + + + + + + + #include + + + + + Session timeout + + 1-30 + Session timeout in seconds (default: 2) + + + + + Timeout must be between 1 and 30 seconds + + 2 + + + + + + + + SSL Certificate, SSL Key and CA (/config/auth) + + + + + tcp port number to accept connections (default: 443) + + 1-65535 + Numeric IP port (default: 443) + + + + + + 443 + + + + udp port number to accept connections (default: 443) + + 1-65535 + Numeric IP port (default: 443) + + + + + + 443 + + + + + + SSL Certificate, SSL Key and CA (/config/auth) + + + + + Certificate Authority certificate + + + + + file + File in /config/auth directory + + + + + + + + + Server Certificate + + file + File in /config/auth directory + + + + + + + + + Privat Key of the Server Certificate + + file + File in /config/auth directory + + + + + + + + + + + Network settings + + + + + Route to be pushed to the client + + ipv4net + IPv4 network and prefix length + + + ipv6net + IPv6 network and prefix length + + + + + + + + + + Client IP pools settings + + + + + Client IP subnet (CIDR notation) + + ipv4net + IPv4 address and prefix length + + + + + Not a valid CIDR formatted prefix + + + + + + + Pool of client IPv6 addresses + + + + + Pool of addresses used to assign to clients + + ipv6net + IPv6 address and prefix length + + + + + + + + + Prefix length used for individual client + + <48-128> + Client prefix length (default: 64) + + + + + + 64 + + + + + + Domain Name Server (DNS) propagated to client + + ipv4 + Domain Name Server (DNS) IPv4 address + + + ipv6 + Domain Name Server (DNS) IPv6 address + + + + + + + + + + + + + + + diff --git a/op-mode-definitions/anyconnect.xml b/op-mode-definitions/anyconnect.xml new file mode 100644 index 000000000..7e8cdd35b --- /dev/null +++ b/op-mode-definitions/anyconnect.xml @@ -0,0 +1,20 @@ + + + + + + + show anyconnect-server information + + + + + Show active anyconnect server sessions + + ${vyos_op_scripts_dir}/anyconnect-control.py --action="show_sessions" + + + + + + diff --git a/src/conf_mode/vpn_anyconnect.py b/src/conf_mode/vpn_anyconnect.py new file mode 100755 index 000000000..45c06bffa --- /dev/null +++ b/src/conf_mode/vpn_anyconnect.py @@ -0,0 +1,132 @@ +#!/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 . + +import os +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.xml import defaults +from vyos.template import render +from vyos.util import call +from vyos import ConfigError +from crypt import crypt, mksalt, METHOD_SHA512 + +from vyos import airbag +airbag.enable() + +cfg_dir = '/run/ocserv' +ocserv_conf = cfg_dir + '/ocserv.conf' +ocserv_passwd = cfg_dir + '/ocpasswd' +radius_cfg = cfg_dir + '/radiusclient.conf' +radius_servers = cfg_dir + '/radius_servers' + + +# Generate hash from user cleartext password +def get_hash(password): + return crypt(password, mksalt(METHOD_SHA512)) + + +def get_config(): + conf = Config() + base = ['vpn', 'anyconnect'] + ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + default_values = defaults(base) + ocserv = dict_merge(default_values, ocserv) + return ocserv + + +def verify(ocserv): + if ocserv is None: + return None + + # Check authentication + if "authentication" in ocserv: + if "mode" in ocserv["authentication"]: + if "local" in ocserv["authentication"]["mode"]: + if not ocserv["authentication"]["local_users"] or not ocserv["authentication"]["local_users"]["username"]: + raise ConfigError('Anyconect mode local required at leat one user') + else: + for user in ocserv["authentication"]["local_users"]["username"]: + if not "password" in ocserv["authentication"]["local_users"]["username"][user]: + raise ConfigError(f'password required for user {user}') + else: + raise ConfigError('anyconnect authentication mode required') + else: + raise ConfigError('anyconnect authentication credentials required') + + # Check ssl + if "ssl" in ocserv: + req_cert = ['ca_cert_file', 'cert_file', 'key_file'] + for cert in req_cert: + if not cert in ocserv["ssl"]: + raise ConfigError('anyconnect ssl {0} required'.format(cert.replace('_', '-'))) + else: + raise ConfigError('anyconnect ssl required') + + # 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('anyconnect network settings required') + + +def generate(ocserv): + if not ocserv: + return None + + if "radius" in ocserv["authentication"]["mode"]: + # Render radius client configuration + render(radius_cfg, 'ocserv/radius_conf.tmpl', ocserv["authentication"]["radius"], trim_blocks=True) + # Render radius servers + render(radius_servers, 'ocserv/radius_servers.tmpl', ocserv["authentication"]["radius"], trim_blocks=True) + 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.tmpl', ocserv["authentication"]["local_users"], trim_blocks=True) + + # Render config + render(ocserv_conf, 'ocserv/ocserv_config.tmpl', ocserv, trim_blocks=True) + + + +def apply(ocserv): + if not ocserv: + call('systemctl stop ocserv.service') + for file in [ocserv_conf, ocserv_passwd]: + if os.path.exists(file): + os.unlink(file) + else: + call('systemctl restart ocserv.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/etc/systemd/system/ocserv.service.d/override.conf b/src/etc/systemd/system/ocserv.service.d/override.conf new file mode 100644 index 000000000..89dbb153f --- /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/op_mode/anyconnect-control.py b/src/op_mode/anyconnect-control.py new file mode 100755 index 000000000..6382016b7 --- /dev/null +++ b/src/op_mode/anyconnect-control.py @@ -0,0 +1,67 @@ +#!/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 . + +import sys +import argparse +import json + +from vyos.config import Config +from vyos.util import popen, run, DEVNULL +from tabulate import tabulate + +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 anyconnect 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 anyconnect sessions") + +def is_ocserv_configured(): + if not Config().exists_effective('vpn anyconnect'): + print("vpn anyconnect 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 IPoE configured + is_ocserv_configured() + + if args.action == "restart": + run("systemctl restart ocserv") + sys.exit(0) + elif args.action == "show_sessions": + show_sessions() + +if __name__ == '__main__': + main() -- cgit v1.2.3 From 9627888b89ddb712eec06a19bcc149367e8c3036 Mon Sep 17 00:00:00 2001 From: DmitriyEshenko Date: Wed, 19 Aug 2020 10:16:00 +0000 Subject: anyconnect: T2811: Return None if anyconnect not configured --- src/conf_mode/vpn_anyconnect.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/conf_mode/vpn_anyconnect.py b/src/conf_mode/vpn_anyconnect.py index 45c06bffa..158e1a117 100755 --- a/src/conf_mode/vpn_anyconnect.py +++ b/src/conf_mode/vpn_anyconnect.py @@ -43,6 +43,9 @@ def get_hash(password): def get_config(): conf = Config() base = ['vpn', 'anyconnect'] + if not conf.exists(base): + return None + ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) default_values = defaults(base) ocserv = dict_merge(default_values, ocserv) -- cgit v1.2.3 From 65e6432298811d4a7586b7e299cd482a97226227 Mon Sep 17 00:00:00 2001 From: DmitriyEshenko Date: Wed, 19 Aug 2020 11:02:13 +0000 Subject: anyconnect: T2812: add basic testing --- scripts/cli/test_vpn_anyconnect.py | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100755 scripts/cli/test_vpn_anyconnect.py diff --git a/scripts/cli/test_vpn_anyconnect.py b/scripts/cli/test_vpn_anyconnect.py new file mode 100755 index 000000000..dd8ab1609 --- /dev/null +++ b/scripts/cli/test_vpn_anyconnect.py @@ -0,0 +1,58 @@ +#!/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 . + +import re +import os +import unittest + +from psutil import process_iter +from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.util import read_file + +OCSERV_CONF = '/run/ocserv/ocserv.conf' +base_path = ['vpn', 'anyconnect'] +cert = '/etc/ssl/certs/ssl-cert-snakeoil.pem' +cert_key = '/etc/ssl/private/ssl-cert-snakeoil.key' + +class TestVpnAnyconnect(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + + def tearDown(self): + # Delete vpn anyconnect configuration + self.session.delete(base_path) + self.session.commit() + + del self.session + + def test_vpn(self): + user = 'vyos_user' + password = 'vyos_pass' + self.session.delete(base_path) + self.session.set(base_path + ["authentication", "local-users", "username", user, "password", password]) + self.session.set(base_path + ["authentication", "mode", "local"]) + self.session.set(base_path + ["network-settings", "client-ip-settings", "subnet", "192.0.2.0/24"]) + self.session.set(base_path + ["ssl", "ca-cert-file", cert]) + self.session.set(base_path + ["ssl", "cert-file", cert]) + self.session.set(base_path + ["ssl", "key-file", cert_key]) + + self.session.commit() + + # Check for running process + self.assertTrue("ocserv-main" in (p.name() for p in process_iter())) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From 509f347d0858e37de9fdeaa1a7aeeb6888fb4b7b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 19 Aug 2020 18:30:33 +0200 Subject: lldp: op-mode: convert data processing from XML to JSON --- src/op_mode/lldp_op.py | 180 ++++++++++++++++++------------------------------- 1 file changed, 66 insertions(+), 114 deletions(-) diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py index 0df6749aa..06958c605 100755 --- a/src/op_mode/lldp_op.py +++ b/src/op_mode/lldp_op.py @@ -16,10 +16,10 @@ import argparse import jinja2 +import json from sys import exit from tabulate import tabulate -from xml.dom import minidom from vyos.util import cmd from vyos.config import Config @@ -35,106 +35,61 @@ lldp_out = """Capability Codes: R - Router, B - Bridge, W - Wlan r - Repeater, S Device ID Local Proto Cap Platform Port ID --------- ----- ----- --- -------- ------- -{% for n in neighbors -%} -{{ "%-25s" | format(n.chassis) }} {{ "%-9s" | format(n.interface) }} {{ "%-6s" | format(n.proto) }} {{ "%-5s" | format(n.cap) }} {{ "%-20s" | format(n.platform) }} {{ n.port }} -{% endfor -%} +{% for neighbor in neighbors %} +{% for local_if, info in neighbor.items() %} +{{ "%-25s" | format(info.chassis) }} {{ "%-9s" | format(local_if) }} {{ "%-6s" | format(info.proto) }} {{ "%-5s" | format(info.capabilities) }} {{ "%-20s" | format(info.platform[:18]) }} {{ info.remote_if }} +{% endfor %} +{% endfor %} """ def get_neighbors(): - return cmd('/usr/sbin/lldpcli -f xml show neighbors') - -def extract_neighbor(neighbor): - """ - Extract LLDP neighbor information from XML document passed as param neighbor - - - - - 00:50:56:9d:a6:11 - VyOS - VyOS unknown - 172.18.254.203 - fe80::250:56ff:fe9d:a611 - - - - - - - 00:50:56:9d:a6:11 - eth0 - 120 - - 10GigBaseCX4 - X copper over 8 pair 100-Ohm balanced cable - - - eth0.203 - - Network Connectivity Device - - - - - - - - None - 4.19.54-amd64-vyos - 6.00 - VMware-42 1d cf 87 ab 7f da 7e-3 - VMware, Inc. - VMware Virtual Platform - No Asset Tag - - - - - """ - - device = { - 'interface' : neighbor.getAttribute('name'), - 'chassis' : '', - 'proto' : neighbor.getAttribute('via'), - 'descr' : '', - 'cap' : '', - 'platform' : '', - 'port' : '' - } - - # first change to node and then retrieve and - chassis = neighbor.getElementsByTagName('chassis') - device['chassis'] = chassis[0].getElementsByTagName('name')[0].firstChild.data - # Cisco IOS comes with a ',' remove character .... - device['platform'] = chassis[0].getElementsByTagName('descr')[0].firstChild.data[:20].replace(',',' ') - - # extract capabilities - for capability in chassis[0].getElementsByTagName('capability'): - # we are only interested in enabled capabilities ... - if capability.getAttribute('enabled') == "on": - if capability.getAttribute('type') == "Router": - device['cap'] += 'R' - elif capability.getAttribute('type') == "Bridge": - device['cap'] += 'B' - elif capability.getAttribute('type') == "Wlan": - device['cap'] += 'W' - elif capability.getAttribute('type') == "Station": - device['cap'] += 'S' - elif capability.getAttribute('type') == "Repeater": - device['cap'] += 'r' - elif capability.getAttribute('type') == "Telephone": - device['cap'] += 'T' - elif capability.getAttribute('type') == "Docsis": - device['cap'] += 'D' - elif capability.getAttribute('type') == "Other": - device['cap'] += 'O' - - # first change to node and then retrieve - port = neighbor.getElementsByTagName('port') - port = port[0].getElementsByTagName('descr')[0].firstChild.data - device['port'] = port - - - return device + return cmd('/usr/sbin/lldpcli -f json show neighbors') + +def parse_data(data): + output = [] + for tmp in data: + for local_if, values in tmp.items(): + for chassis, c_value in values.get('chassis', {}).items(): + capabilities = c_value['capability'] + if isinstance(capabilities, dict): + capabilities = [capabilities] + + cap = '' + 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' + + + remote_if = 'Unknown' + if 'descr' in values.get('port', {}): + remote_if = values.get('port', {}).get('descr') + elif 'id' in values.get('port', {}): + remote_if = values.get('port', {}).get('id').get('value', 'Unknown') + + output.append({local_if: {'chassis': chassis, + 'remote_if': remote_if, + 'proto': values.get('via','Unknown'), + 'platform': c_value.get('descr', 'Unknown'), + 'capabilities': cap}}) + + + output = {'neighbors': output} + return output if __name__ == '__main__': args = parser.parse_args() @@ -145,29 +100,26 @@ if __name__ == '__main__': print('Service LLDP is not configured') exit(0) - if args.all: - neighbors = minidom.parseString(get_neighbors()) - for neighbor in neighbors.getElementsByTagName('interface'): - tmp['neighbors'].append( extract_neighbor(neighbor) ) - - elif args.detail: - out = cmd('/usr/sbin/lldpctl -f plain') - print(out) + if args.detail: + print(cmd('/usr/sbin/lldpctl -f plain')) exit(0) + elif args.all or args.interface: + tmp = json.loads(get_neighbors()) - elif args.interface: - neighbors = minidom.parseString(get_neighbors()) - for neighbor in neighbors.getElementsByTagName('interface'): - # check if neighbor appeared on proper interface - if neighbor.getAttribute('name') == args.interface: - tmp['neighbors'].append( extract_neighbor(neighbor) ) + if args.all: + neighbors = tmp['lldp']['interface'] + elif args.interface: + neighbors = [] + for neighbor in tmp['lldp']['interface']: + if args.interface in neighbor: + neighbors.append(neighbor) else: parser.print_help() exit(1) - tmpl = jinja2.Template(lldp_out) - config_text = tmpl.render(tmp) + tmpl = jinja2.Template(lldp_out, trim_blocks=True) + config_text = tmpl.render(parse_data(neighbors)) print(config_text) exit(0) -- cgit v1.2.3 From 93c3a4a4f05014914dd2e9353ddd676e20bced1a Mon Sep 17 00:00:00 2001 From: sever-sever Date: Wed, 19 Aug 2020 17:07:14 +0000 Subject: mpls-ldp: T915: Add discovery hello timers --- data/templates/frr/ldpd.frr.tmpl | 12 ++++++++++++ interface-definitions/protocols-mpls.xml.in | 26 +++++++++++++++++++++++++- src/conf_mode/protocols_mpls.py | 24 +++++++++++++++++++++--- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/data/templates/frr/ldpd.frr.tmpl b/data/templates/frr/ldpd.frr.tmpl index bbff88ae5..dbaa917e8 100644 --- a/data/templates/frr/ldpd.frr.tmpl +++ b/data/templates/frr/ldpd.frr.tmpl @@ -21,6 +21,18 @@ no discovery transport-address {{ old_ldp.d_transp_ipv4 }} {% if ldp.d_transp_ipv4 -%} discovery transport-address {{ ldp.d_transp_ipv4 }} {% endif -%} +{% if old_ldp.hello_holdtime -%} +no discovery hello holdtime {{ old_ldp.hello_holdtime }} +{% endif -%} +{% if ldp.hello_holdtime -%} +discovery hello holdtime {{ ldp.hello_holdtime }} +{% endif -%} +{% if old_ldp.hello_interval -%} +no discovery hello interval {{ old_ldp.hello_interval }} +{% endif -%} +{% if ldp.hello_interval -%} +discovery hello interval {{ ldp.hello_interval }} +{% endif -%} {% for interface in old_ldp.interfaces -%} no interface {{interface}} {% endfor -%} diff --git a/interface-definitions/protocols-mpls.xml.in b/interface-definitions/protocols-mpls.xml.in index 376323855..3e9edbf72 100644 --- a/interface-definitions/protocols-mpls.xml.in +++ b/interface-definitions/protocols-mpls.xml.in @@ -54,6 +54,30 @@ + + + Hello holdtime + + 1-65535 + Time in seconds + + + + + + + + + Hello interval + + 1-65535 + Time in seconds + + + + + + Transport ipv4 address @@ -95,4 +119,4 @@ - \ No newline at end of file + diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index 72208ffa1..bcb16fa04 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# 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 @@ -38,13 +38,17 @@ def get_config(): 'interfaces' : [], 'neighbors' : {}, 'd_transp_ipv4' : None, - 'd_transp_ipv6' : None + 'd_transp_ipv6' : None, + 'hello_holdtime' : None, + 'hello_interval' : None }, 'ldp' : { 'interfaces' : [], 'neighbors' : {}, 'd_transp_ipv4' : None, - 'd_transp_ipv6' : None + 'd_transp_ipv6' : None, + 'hello_holdtime' : None, + 'hello_interval' : None } } if not (conf.exists('protocols mpls') or conf.exists_effective('protocols mpls')): @@ -61,6 +65,20 @@ def get_config(): if conf.exists('router-id'): mpls_conf['router_id'] = conf.return_value('router-id') + # Get hello holdtime + if conf.exists_effective('discovery hello-holdtime'): + mpls_conf['old_ldp']['hello_holdtime'] = conf.return_effective_value('discovery hello-holdtime') + + if conf.exists('discovery hello-holdtime'): + mpls_conf['ldp']['hello_holdtime'] = conf.return_value('discovery hello-holdtime') + + # Get hello interval + if conf.exists_effective('discovery hello-interval'): + mpls_conf['old_ldp']['hello_interval'] = conf.return_effective_value('discovery hello-interval') + + if conf.exists('discovery hello-interval'): + mpls_conf['ldp']['hello_interval'] = conf.return_value('discovery hello-interval') + # Get discovery transport-ipv4-address if conf.exists_effective('discovery transport-ipv4-address'): mpls_conf['old_ldp']['d_transp_ipv4'] = conf.return_effective_value('discovery transport-ipv4-address') -- cgit v1.2.3 From 0bc94e588fea31888445d182f54803598e82b615 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 19 Aug 2020 23:15:07 -0500 Subject: certbot: T2815: change config dir to /config/auth/letsencrypt --- python/vyos/defaults.py | 3 ++- src/conf_mode/le_cert.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 3062ed31c..9921e3b5f 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -21,7 +21,8 @@ directories = { "current": "/opt/vyatta/etc/config-migrate/current", "migrate": "/opt/vyatta/etc/config-migrate/migrate", "log": "/var/log/vyatta", - "templates": "/usr/share/vyos/templates/" + "templates": "/usr/share/vyos/templates/", + "certbot": "/config/auth/letsencrypt" } cfg_group = 'vyattacfg' diff --git a/src/conf_mode/le_cert.py b/src/conf_mode/le_cert.py index 5b965f95f..755c89966 100755 --- a/src/conf_mode/le_cert.py +++ b/src/conf_mode/le_cert.py @@ -27,6 +27,7 @@ from vyos import airbag airbag.enable() vyos_conf_scripts_dir = vyos.defaults.directories['conf_mode'] +vyos_certbot_dir = vyos.defaults.directories['certbot'] dependencies = [ 'https.py', @@ -45,7 +46,7 @@ def request_certbot(cert): else: domain_flag = '' - certbot_cmd = 'certbot certonly -n --nginx --agree-tos --no-eff-email --expand {0} {1}'.format(email_flag, domain_flag) + certbot_cmd = f'certbot certonly --config-dir {vyos_certbot_dir} -n --nginx --agree-tos --no-eff-email --expand {email_flag} {domain_flag}' cmd(certbot_cmd, raising=ConfigError, -- cgit v1.2.3 From 41c847593551496770c4d08e09e32af638a30f40 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 19 Aug 2020 23:16:35 -0500 Subject: https: T2815: adjust for change in certbot config directory --- data/templates/https/nginx.default.tmpl | 8 ++++---- src/conf_mode/https.py | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/data/templates/https/nginx.default.tmpl b/data/templates/https/nginx.default.tmpl index f4f2c1848..aaa652e09 100644 --- a/data/templates/https/nginx.default.tmpl +++ b/data/templates/https/nginx.default.tmpl @@ -25,10 +25,10 @@ server { {% endfor %} {% if server.certbot %} - ssl_certificate /etc/letsencrypt/live/{{ server.certbot_dir }}/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/{{ server.certbot_dir }}/privkey.pem; - include /etc/letsencrypt/options-ssl-nginx.conf; - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + ssl_certificate {{ server.certbot_dir }}/live/{{ server.certbot_domain_dir }}/fullchain.pem; + ssl_certificate_key {{ server.certbot_dir }}/live/{{ server.certbot_domain_dir }}/privkey.pem; + include {{ server.certbot_dir }}/options-ssl-nginx.conf; + ssl_dhparam {{ server.certbot_dir }}/ssl-dhparams.pem; {% elif server.vyos_cert %} include {{ server.vyos_cert.conf }}; {% else %} diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index 7acb629bd..3dae0fddb 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -31,6 +31,7 @@ from vyos import airbag airbag.enable() config_file = '/etc/nginx/sites-available/default' +certbot_dir = vyos.defaults.directories['certbot'] default_server_block = { 'id' : '', @@ -86,8 +87,9 @@ def get_config(): if sub_list: for sb in sub_list: sb['certbot'] = True + sb['certbot_dir'] = certbot_dir # certbot organizes certificates by first domain - sb['certbot_dir'] = certbot_domains[0] + sb['certbot_domain_dir'] = certbot_domains[0] api_somewhere = False api_data = {} -- cgit v1.2.3 From 1658ff2a8b639fe89d48c48710beea0da1a02032 Mon Sep 17 00:00:00 2001 From: erkin Date: Thu, 20 Aug 2020 16:21:43 +0300 Subject: VRRP: T2761: Extend "show vrrp" op-mode command with router priority --- python/vyos/ifconfig/vrrp.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py index 5e6387881..01a7cc7ab 100644 --- a/python/vyos/ifconfig/vrrp.py +++ b/python/vyos/ifconfig/vrrp.py @@ -96,7 +96,7 @@ class VRRP(object): pid = util.read_file(cls.location['pid']) os.kill(int(pid), cls._signal[what]) - # shoud look for file size change ? + # should look for file size change? sleep(0.2) return util.read_file(fname) except FileNotFoundError: @@ -126,8 +126,8 @@ class VRRP(object): return disabled @classmethod - def format (cls, data): - headers = ["Name", "Interface", "VRID", "State", "Last Transition"] + def format(cls, data): + headers = ["Name", "Interface", "VRID", "State", "Priority", "Last Transition"] groups = [] data = json.loads(data) @@ -138,11 +138,12 @@ class VRRP(object): 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 = util.seconds_to_human(since) - groups.append([name, intf, vrid, state, last]) + groups.append([name, intf, vrid, state, priority, last]) # add to the active list disabled instances groups.extend(cls.disabled()) -- cgit v1.2.3 From 20e66514c00cf6495370bac6bbbe45a505afe242 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 20 Aug 2020 22:27:30 +0200 Subject: lcd: T2564: remove lcdproc-extra-drivers package --- debian/control | 1 - 1 file changed, 1 deletion(-) diff --git a/debian/control b/debian/control index 520401d57..9be41df33 100644 --- a/debian/control +++ b/debian/control @@ -74,7 +74,6 @@ Depends: python3, udp-broadcast-relay, pdns-recursor, lcdproc, - lcdproc-extra-drivers, openvpn, openvpn-auth-ldap, openvpn-auth-radius, -- cgit v1.2.3 From 50a7544ad69cb7b631a499299f9ab32b2e8918a0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 20 Aug 2020 22:54:50 +0200 Subject: Debian: alphabetically sort (build-)dependencies --- debian/control | 169 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 86 insertions(+), 83 deletions(-) diff --git a/debian/control b/debian/control index 9be41df33..d6b4420c7 100644 --- a/debian/control +++ b/debian/control @@ -2,112 +2,115 @@ Source: vyos-1x Section: contrib/net Priority: extra Maintainer: VyOS Package Maintainers -Build-Depends: debhelper (>= 9), - quilt, +Build-Depends: + debhelper (>= 9), + fakeroot, + libvyosconfig0 (>= 0.0.7), python3, - python3-setuptools, - python3-xmltodict, + python3-coverage, python3-lxml, python3-nose, - python3-coverage, - whois, - fakeroot, - libvyosconfig0 (>= 0.0.7) + python3-setuptools, + python3-xmltodict, + quilt, + whois Standards-Version: 3.9.6 Package: vyos-1x Architecture: all -Depends: python3, - ${python3:Depends}, - python3-netifaces, - python3-jinja2, - python3-pystache, - python3-psutil, - python3-tabulate, - python3-six, - python3-isc-dhcp-leases, - python3-hurry.filesize, - python3-vici (>= 5.7.2), - python3-flask, - python3-waitress, - python3-netaddr, - python3-zmq, - python3-jmespath, - python3-xmltodict, - python3-pyudev, - python3-voluptuous, +Depends: + accel-ppp, + beep, + bmon, bsdmainutils, + conntrack, + conserver-client, + conserver-server, + crda, cron, - etherwake, - systemd, + dbus, + dropbear, easy-rsa, + etherwake, + fastnetmon, + file, + frr, + frr-pythontools, + hostapd (>= 0.6.8), + hvinfo, + igmpproxy, ipaddrcheck, - tcpdump, + iperf, + iperf3, + iputils-arping, isc-dhcp-client, - wide-dhcpv6-client, - bmon, - hvinfo, + isc-dhcp-relay, + isc-dhcp-server, + iw, + keepalived (>=2.0.5), + lcdproc, + libatomic1, + libndp-tools, + libpam-radius-auth (>= 1.5.0), + libvyosconfig0, + lldpd, lm-sensors, - file, lsscsi, - pciutils, - usbutils, - procps, - snmp, snmpd, - openssh-server, + mdns-repeater, + mtr-tiny, + nftables (>= 0.9.3), + nginx-light, ntp, ntpdate, - iputils-arping, - libvyosconfig0, - beep, - dropbear, - conserver-server, - conserver-client, - isc-dhcp-server, - isc-dhcp-relay, - keepalived (>=2.0.5), - wireguard, - tftpd-hpa, - igmpproxy, - accel-ppp, - mdns-repeater, - udp-broadcast-relay, - pdns-recursor, - lcdproc, + ocserv, + openssh-server, openvpn, openvpn-auth-ldap, openvpn-auth-radius, - libpam-radius-auth (>= 1.5.0), - mtr-tiny, - telnet, - traceroute, - ssl-cert, - nginx-light, - lldpd, - iperf, - iperf3, - frr, - frr-pythontools, - radvd, - dbus, - usb-modeswitch, - hostapd (>= 0.6.8), - wpasupplicant (>= 0.6.7), - iw, - crda, - wireless-regdb, + pciutils, + pdns-recursor, pmacct (>= 1.6.0), - python3-certbot-nginx, pppoe, + procps, + python3, + python3-certbot-nginx, + ${python3:Depends}, + python3-flask, + python3-hurry.filesize, + python3-isc-dhcp-leases, + python3-jinja2, + python3-jmespath, + python3-netaddr, + python3-netifaces, + python3-psutil, + python3-pystache, + python3-pyudev, + python3-six, + python3-tabulate, + python3-vici (>= 5.7.2), + python3-voluptuous, + python3-waitress, + python3-xmltodict, + python3-zmq, + radvd, salt-minion, + snmp, + snmpd, + ssl-cert, + systemd, + tcpdump, + tcptraceroute, + telnet, + tftpd-hpa, + traceroute, + udp-broadcast-relay, + usb-modeswitch, + usbutils, vyos-utils, - nftables (>= 0.9.3), - conntrack, - libatomic1, - fastnetmon, - libndp-tools, - ocserv, - tcptraceroute + wide-dhcpv6-client, + wireguard, + wireless-regdb, + wpasupplicant (>= 0.6.7) Description: VyOS configuration scripts and data VyOS configuration scripts, interface definitions, and everything -- cgit v1.2.3 From 98c2c5e0585e0806099a353de207f392223eff9a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 22 Aug 2020 23:15:29 +0200 Subject: dhcpv6-pd: T2677: optimize CLI interface for PD configuration The current CLI did not support multiple prefix-delegations per interface. Some ISPs only send one /64 to a client per prefix-delegation request, but they allow the customer to request multiple prefixes. The 'dhcpv6-options prefix-delegation' node has been renamed and converted to a tag node named 'dhcpv6-options pd'. The tag node specifies a PD request (>=0). In the past the user needed to know what prefix will be assigned and required to calculate the sla-len by himself. The 'sla-len' node was dropped and is now calculated in the background from the 'dhcpv6-options pd 0 length' node. It is no longer mandatory to supply the 'sla-id' node, if sla-id is not specified it is 'guessed' by counting upwards. Example configuration: ---------------------- ethernet eth1 { address dhcpv6 dhcpv6-options { pd 0 { length 56 interface eth2 { address 1 } } } } This will request a /56 assignment from the ISP and will delegate a /64 network to interface eth2. VyOS will use the interface address ::1 on the delegate interface (eth2) as its local address. --- data/templates/dhcp-client/ipv6.tmpl | 48 +++++++++------- interface-definitions/include/dhcp-options.xml.i | 2 +- interface-definitions/include/dhcpv6-options.xml.i | 40 ++++++-------- python/vyos/configdict.py | 64 ++++++++++++++++++---- src/conf_mode/interfaces-pppoe.py | 4 +- src/migration-scripts/interfaces/11-to-12 | 58 ++++++++++++++++++++ 6 files changed, 157 insertions(+), 59 deletions(-) create mode 100755 src/migration-scripts/interfaces/11-to-12 diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl index 9673f302b..e9285d86b 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -8,37 +8,43 @@ interface {{ ifname }} { information-only; {% endif %} {% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} - send ia-na 1; # non-temporary address + send ia-na 0; # non-temporary address {% endif %} -{% if dhcpv6_options is defined and dhcpv6_options.prefix_delegation is defined %} - send ia-pd 2; # prefix delegation +{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} +{% for pd in dhcpv6_options.pd %} + send ia-pd {{ pd }}; # prefix delegation #{{ pd }} +{% endfor %} {% endif %} }; {% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} -id-assoc na 1 { - # Identity association NA +id-assoc na 0 { + # Identity association for non temporary address }; {% endif %} -{% if dhcpv6_options is defined and dhcpv6_options.prefix_delegation is defined %} -id-assoc pd 2 { -{% if dhcpv6_options.prefix_delegation.length is defined %} - prefix ::/{{ dhcpv6_options.prefix_delegation.length }} infinity; -{% endif %} -{% for interface in dhcpv6_options.prefix_delegation.interface %} +{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} +{% for pd in dhcpv6_options.pd %} +id-assoc pd {{ pd }} { +{# length got a default value #} + prefix ::/{{ dhcpv6_options.pd[pd].length }} infinity; +{% set sla_len = 64 - dhcpv6_options.pd[pd].length|int %} +{% set count = namespace(value=0) %} +{% for interface in dhcpv6_options.pd[pd].interface if dhcpv6_options.pd[pd].interface is defined %} prefix-interface {{ interface }} { -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_id is defined %} - sla-id {{ dhcpv6_options.prefix_delegation.interface[interface].sla_id }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_len is defined %} - sla-len {{ dhcpv6_options.prefix_delegation.interface[interface].sla_len }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].address is defined %} - ifid {{ dhcpv6_options.prefix_delegation.interface[interface].address }}; -{% endif %} + sla-len {{ sla_len }}; +{% if dhcpv6_options.pd[pd].interface[interface].sla_id is defined and dhcpv6_options.pd[pd].interface[interface].sla_id is not none %} + sla-id {{ dhcpv6_options.pd[pd].interface[interface].sla_id }}; +{% else %} + sla-id {{ count.value }}; +{% endif %} +{% if dhcpv6_options.pd[pd].interface[interface].address is defined and dhcpv6_options.pd[pd].interface[interface].address is not none %} + ifid {{ dhcpv6_options.pd[pd].interface[interface].address }}; +{% endif %} }; -{% endfor %} +{% set count.value = count.value + 1 %} +{% endfor %} }; +{% endfor %} {% endif %} diff --git a/interface-definitions/include/dhcp-options.xml.i b/interface-definitions/include/dhcp-options.xml.i index 0f71d9321..9989291fc 100644 --- a/interface-definitions/include/dhcp-options.xml.i +++ b/interface-definitions/include/dhcp-options.xml.i @@ -1,6 +1,6 @@ - DHCP options + DHCP client settings/options diff --git a/interface-definitions/include/dhcpv6-options.xml.i b/interface-definitions/include/dhcpv6-options.xml.i index 98a87dba2..5d088b83d 100644 --- a/interface-definitions/include/dhcpv6-options.xml.i +++ b/interface-definitions/include/dhcpv6-options.xml.i @@ -1,11 +1,24 @@ - DHCPv6 options + DHCPv6 client settings/options - + + + Acquire only config parameters, no address + + + + - DHCPv6 Prefix Delegation Options + DHCPv6 prefix delegation interface statement + + instance number + Prefix delegation instance (>= 0) + + + + @@ -19,6 +32,7 @@ + 64 @@ -52,28 +66,10 @@ - - - Site-Level aggregator (SLA) length - - 0-128 - Length of delegated prefix - - - - - - - - - - Acquire only config parameters, no address - - - + IPv6 "temporary" address diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 010eda45c..c1e93955e 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -93,7 +93,7 @@ def dict_merge(source, destination): tmp = deepcopy(destination) for key, value in source.items(): - if key not in tmp.keys(): + if key not in tmp: tmp[key] = value elif isinstance(source[key], dict): tmp[key] = dict_merge(source[key], tmp[key]) @@ -109,15 +109,15 @@ def T2665_default_dict_cleanup(dict): """ Cleanup default keys for tag nodes https://phabricator.vyos.net/T2665. """ # Cleanup for vif in ['vif', 'vif_s']: - if vif in dict.keys(): - for key in ['ip', 'mtu']: - if key in dict[vif].keys(): + if vif in dict: + for key in ['ip', 'mtu', 'dhcpv6_options']: + if key in dict[vif]: del dict[vif][key] # cleanup VIF-S defaults - if 'vif_c' in dict[vif].keys(): - for key in ['ip', 'mtu']: - if key in dict[vif]['vif_c'].keys(): + if 'vif_c' in dict[vif]: + for key in ['ip', 'mtu', 'dhcpv6_options']: + if key in dict[vif]['vif_c']: del dict[vif]['vif_c'][key] # If there is no vif-c defined and we just cleaned the default # keys - we can clean the entire vif-c dict as it's useless @@ -129,6 +129,21 @@ def T2665_default_dict_cleanup(dict): if not dict[vif]: del dict[vif] + if 'dhcpv6_options' in dict and 'pd' in dict['dhcpv6_options']: + if 'length' in dict['dhcpv6_options']['pd']: + del dict['dhcpv6_options']['pd']['length'] + + # delete empty dicts + if 'dhcpv6_options' in dict: + if 'pd' in dict['dhcpv6_options']: + # test if 'pd' is an empty node so we can remove it + if not dict['dhcpv6_options']['pd']: + del dict['dhcpv6_options']['pd'] + + # test if 'dhcpv6_options' is an empty node so we can remove it + if not dict['dhcpv6_options']: + del dict['dhcpv6_options'] + return dict def leaf_node_changed(conf, path): @@ -193,6 +208,16 @@ def get_removed_vlans(conf, dict): return dict + +def dict_add_dhcpv6pd_defaults(defaults, config_dict): + # Implant default dictionary for DHCPv6-PD instances + if 'dhcpv6_options' in config_dict and 'pd' in config_dict['dhcpv6_options']: + for pd, pd_config in config_dict['dhcpv6_options']['pd'].items(): + config_dict['dhcpv6_options']['pd'][pd] = dict_merge( + defaults, pd_config) + + return config_dict + def get_interface_dict(config, base, ifname=''): """ Common utility function to retrieve and mandgle the interfaces available @@ -257,15 +282,30 @@ def get_interface_dict(config, base, ifname=''): # remove wrongly inserted values dict = T2665_default_dict_cleanup(dict) - # The values are identical for vif, vif-s and vif-c as the all include the same - # XML definitions which hold the defaults + # Implant default dictionary for DHCPv6-PD instances + default_pd_values = defaults(base + ['dhcpv6-options', 'pd']) + dict = dict_add_dhcpv6pd_defaults(default_pd_values, dict) + + # Implant default dictionary in vif/vif-s VLAN interfaces. Values are + # identical for all types of VLAN interfaces as they all include the same + # XML definitions which hold the defaults. default_vif_values = defaults(base + ['vif']) for vif, vif_config in dict.get('vif', {}).items(): - vif_config = dict_merge(default_vif_values, vif_config) + dict['vif'][vif] = dict_add_dhcpv6pd_defaults( + default_pd_values, vif_config) + dict['vif'][vif] = T2665_default_dict_cleanup( + dict_merge(default_vif_values, vif_config)) + for vif_s, vif_s_config in dict.get('vif_s', {}).items(): - vif_s_config = dict_merge(default_vif_values, vif_s_config) + dict['vif_s'][vif_s] = dict_add_dhcpv6pd_defaults( + default_pd_values, vif_s_config) + dict['vif_s'][vif_s] = T2665_default_dict_cleanup( + dict_merge(default_vif_values, vif_s_config)) for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): - vif_c_config = dict_merge(default_vif_values, vif_c_config) + dict['vif_s'][vif_s]['vif_c'][vif_c] = dict_add_dhcpv6pd_defaults( + default_pd_values, vif_c_config) + dict['vif_s'][vif_s]['vif_c'][vif_c] = T2665_default_dict_cleanup( + dict_merge(default_vif_values, vif_c_config)) # Check vif, vif-s/vif-c VLAN interfaces for removal dict = get_removed_vlans(config, dict) diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 928113b49..901ea769c 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -15,7 +15,6 @@ # along with this program. If not, see . import os -import jmespath from sys import exit from copy import deepcopy @@ -104,8 +103,7 @@ def generate(pppoe): render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', pppoe, trim_blocks=True, permission=0o755) - tmp = jmespath.search('dhcpv6_options.prefix_delegation.interface', pppoe) - if tmp and len(tmp) > 0: + if 'dhcpv6_options' in pppoe and 'pd' in pppoe['dhcpv6_options']: # ipv6.tmpl relies on ifname - this should be made consitent in the # future better then double key-ing the same value render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe, trim_blocks=True) diff --git a/src/migration-scripts/interfaces/11-to-12 b/src/migration-scripts/interfaces/11-to-12 new file mode 100755 index 000000000..0dad24642 --- /dev/null +++ b/src/migration-scripts/interfaces/11-to-12 @@ -0,0 +1,58 @@ +#!/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 . + +# - 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 sys import exit, argv +from vyos.configtree import ConfigTree + +if __name__ == '__main__': + if (len(argv) < 1): + print("Must specify file name!") + exit(1) + + file_name = argv[1] + with open(file_name, 'r') as f: + config_file = f.read() + + config = ConfigTree(config_file) + + 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) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) -- cgit v1.2.3 From 5a5974d5a00b482cabd3dee92bc365d3c9f399bc Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 22 Aug 2020 23:29:18 +0200 Subject: ifconfig: T2653: drop unused vyos.ifconfig.pppoe --- python/vyos/ifconfig/__init__.py | 1 - python/vyos/ifconfig/pppoe.py | 41 ---------------------------------------- 2 files changed, 42 deletions(-) delete mode 100644 python/vyos/ifconfig/pppoe.py diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index 9cd8d44c1..f4b504ebd 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -30,7 +30,6 @@ 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 GREIf from vyos.ifconfig.tunnel import GRETapIf from vyos.ifconfig.tunnel import IP6GREIf diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py deleted file mode 100644 index 787245696..000000000 --- a/python/vyos/ifconfig/pppoe.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2020 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 . - - -from vyos.ifconfig.interface import Interface - - -@Interface.register -class PPPoEIf(Interface): - default = { - 'type': 'pppoe', - } - definition = { - **Interface.definition, - **{ - 'section': 'pppoe', - 'prefixes': ['pppoe', ], - }, - } - - # stub this interface is created in the configure script - - 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 -- cgit v1.2.3 From ec1cf7dd1508e4a84d99818c7e34d093242b3331 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 00:04:09 +0200 Subject: dhcpv6-pd: T2821: support dhcpv6-pd without "address dhcpv6" Currently DHCPv6-PD requires an interface address configured to dhcpv6 on the CLI. This is not required also sometimes there is either no dhcpv6 interface addressing available (PPPoE) or wanted. This limitation was artificial due to the old interface code. Change the implementation to spawn the DHCPv6 client and request a prefix even when there is no address request configured. --- data/templates/dhcp-client/ipv6.tmpl | 12 ++++++++---- python/vyos/ifconfig/interface.py | 17 +++++++++++++---- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl index e9285d86b..85841fe94 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -2,13 +2,15 @@ # man https://www.unix.com/man-page/debian/5/dhcp6c.conf/ interface {{ ifname }} { +{% if address is defined and 'dhcpv6' in address %} request domain-name-servers; request domain-name; -{% if dhcpv6_options is defined and dhcpv6_options.parameters_only is defined %} +{% if dhcpv6_options is defined and dhcpv6_options.parameters_only is defined %} information-only; -{% endif %} -{% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} +{% endif %} +{% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} send ia-na 0; # non-temporary address +{% endif %} {% endif %} {% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} {% for pd in dhcpv6_options.pd %} @@ -17,10 +19,12 @@ interface {{ ifname }} { {% endif %} }; -{% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} +{% if address is defined and 'dhcpv6' in address %} +{% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} id-assoc na 0 { # Identity association for non temporary address }; +{% endif %} {% endif %} {% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 892495dec..537c4bc2d 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -901,10 +901,15 @@ class Interface(Control): if isinstance(new_addr, str): new_addr = [new_addr] - # ensure DHCP/DHCPv6 is stopped (when not configured explicitly) - for proto in ['dhcp', 'dhcpv6']: - if proto not in new_addr: - self.del_addr(proto) + # 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 = jmespath.search('dhcpv6_options.pd', config) + if 'dhcpv6' not in new_addr or dhcpv6pd == None: + 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 @@ -915,6 +920,10 @@ class Interface(Control): for addr in new_addr: self.add_addr(addr) + # start DHCPv6 client when only PD was configured + if dhcpv6pd != None: + self.set_dhcpv6(True) + # There are some items in the configuration which can only be applied # if this instance is not bound to a bridge. This should be checked # by the caller but better save then sorry! -- cgit v1.2.3 From a574a01ab5c02aeb90c6d099dce482c1bf8be096 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 00:16:45 +0200 Subject: dhcpv6-pd: T2677: add support for rapid-commit option When rapid-commit is specified, dhcp6c will include a rapid-commit option in solicit messages and wait for an immediate reply instead of advertisements. --- data/templates/dhcp-client/ipv6.tmpl | 3 +++ interface-definitions/include/dhcpv6-options.xml.i | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl index 85841fe94..68f668117 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -11,6 +11,9 @@ interface {{ ifname }} { {% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} send ia-na 0; # non-temporary address {% endif %} +{% if dhcpv6_options is defined and dhcpv6_options.rapid_commit is defined %} + send rapid-commit; # wait for immediate reply instead of advertisements +{% endif %} {% endif %} {% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} {% for pd in dhcpv6_options.pd %} diff --git a/interface-definitions/include/dhcpv6-options.xml.i b/interface-definitions/include/dhcpv6-options.xml.i index 5d088b83d..b0a806806 100644 --- a/interface-definitions/include/dhcpv6-options.xml.i +++ b/interface-definitions/include/dhcpv6-options.xml.i @@ -70,9 +70,15 @@ + + + Wait for immediate reply instead of advertisements + + + - IPv6 "temporary" address + IPv6 temporary address -- cgit v1.2.3 From 87693ce61819b82b6496cdde3d59835aa81cf6a9 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 01:16:03 +0200 Subject: pppoe: sync to new dhcpv6-pd cli --- scripts/cli/test_interfaces_pppoe.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/cli/test_interfaces_pppoe.py b/scripts/cli/test_interfaces_pppoe.py index d2f6c9b16..7e26b7dac 100755 --- a/scripts/cli/test_interfaces_pppoe.py +++ b/scripts/cli/test_interfaces_pppoe.py @@ -111,11 +111,10 @@ class PPPoEInterfaceTest(unittest.TestCase): self.session.set(base_path + [interface, 'ipv6', 'enable']) # prefix delegation stuff - dhcpv6_pd_base = base_path + [interface, 'dhcpv6-options', 'prefix-delegation'] + dhcpv6_pd_base = base_path + [interface, 'dhcpv6-options', 'pd', '0'] self.session.set(dhcpv6_pd_base + ['length', '56']) self.session.set(dhcpv6_pd_base + ['interface', self._source_interface, 'address', address]) self.session.set(dhcpv6_pd_base + ['interface', self._source_interface, 'sla-id', sla_id]) - self.session.set(dhcpv6_pd_base + ['interface', self._source_interface, 'sla-len', sla_len]) # commit changes self.session.commit() -- cgit v1.2.3 From c8ea23cc3d3cab6e384b143d7d22030a9085f1a2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 11:47:30 +0200 Subject: Revert "ifconfig: T2653: drop unused vyos.ifconfig.pppoe" This reverts commit 5a5974d5a00b482cabd3dee92bc365d3c9f399bc. Required for operational mode "show interfaces" command. --- python/vyos/ifconfig/__init__.py | 1 + python/vyos/ifconfig/pppoe.py | 41 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 python/vyos/ifconfig/pppoe.py diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index f4b504ebd..9cd8d44c1 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -30,6 +30,7 @@ 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 GREIf from vyos.ifconfig.tunnel import GRETapIf from vyos.ifconfig.tunnel import IP6GREIf diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py new file mode 100644 index 000000000..787245696 --- /dev/null +++ b/python/vyos/ifconfig/pppoe.py @@ -0,0 +1,41 @@ +# Copyright 2020 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 . + + +from vyos.ifconfig.interface import Interface + + +@Interface.register +class PPPoEIf(Interface): + default = { + 'type': 'pppoe', + } + definition = { + **Interface.definition, + **{ + 'section': 'pppoe', + 'prefixes': ['pppoe', ], + }, + } + + # stub this interface is created in the configure script + + 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 -- cgit v1.2.3 From 52a78e3d76672a66e27c29b70102c844757e2055 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 11:50:41 +0200 Subject: dhcpv6-pd: pppoe: T2677: fix dhcpc6 startup DHCPv6 client was not started as it used the old dict keys. --- data/templates/pppoe/ip-down.script.tmpl | 2 +- data/templates/pppoe/ipv6-up.script.tmpl | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/data/templates/pppoe/ip-down.script.tmpl b/data/templates/pppoe/ip-down.script.tmpl index 7b1952a80..c2d0cd09a 100644 --- a/data/templates/pppoe/ip-down.script.tmpl +++ b/data/templates/pppoe/ip-down.script.tmpl @@ -30,7 +30,7 @@ vtysh -c "conf t" ${VRF_NAME} -c "no ipv6 route ::/0 {{ ifname }} ${VRF_NAME}" {% endif %} {% endif %} -{% if dhcpv6_options is defined and dhcpv6_options.prefix_delegation is defined %} +{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} # Stop wide dhcpv6 client systemctl stop dhcp6c@{{ ifname }}.service {% endif %} diff --git a/data/templates/pppoe/ipv6-up.script.tmpl b/data/templates/pppoe/ipv6-up.script.tmpl index 3dee3d011..d0a62478c 100644 --- a/data/templates/pppoe/ipv6-up.script.tmpl +++ b/data/templates/pppoe/ipv6-up.script.tmpl @@ -40,12 +40,11 @@ echo 2 > /proc/sys/net/ipv6/conf/{{ ifname }}/accept_ra echo 1 > /proc/sys/net/ipv6/conf/{{ ifname }}/autoconf {% endif %} -{% if dhcpv6_options is defined and dhcpv6_options.prefix_delegation is defined %} +{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} # Start wide dhcpv6 client systemctl start dhcp6c@{{ ifname }}.service {% endif %} - {% if default_route != 'none' -%} # See https://phabricator.vyos.net/T2248 & T2220. Determine if we are enslaved # to a VRF, this is needed to properly insert the default route. -- cgit v1.2.3 From 1748ce9ca2d50e931c94d50116198365f59e5c8b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 12:14:12 +0200 Subject: ifconfig: vxlan: geneve: T2823: properly set interface state --- python/vyos/ifconfig/geneve.py | 19 +++++++++++++++++++ python/vyos/ifconfig/vxlan.py | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/python/vyos/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py index 145dc268c..dd0658668 100644 --- a/python/vyos/ifconfig/geneve.py +++ b/python/vyos/ifconfig/geneve.py @@ -64,3 +64,22 @@ class GeneveIf(Interface): >> dict = GeneveIf().get_config() """ return deepcopy(cls.default) + + 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 first + super().update(config) + + # 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) diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index 0dddab7b7..18a500336 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -109,3 +109,22 @@ class VXLANIf(Interface): >> dict = VXLANIf().get_config() """ return deepcopy(cls.default) + + 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 first + super().update(config) + + # 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) -- cgit v1.2.3 From 9fd15ce5c60d6c5074d3c374a4049a194b8c17ca Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 12:25:16 +0200 Subject: hostapd: T2564: bugfix on daemon startup Commit b082a6fb21 ("lcd: T2564: flatten CLI interface") by accident replaced the template for hostapd which an LCD configuration during implementation of T2564. This has been corrected by restoring the content of the service configuration from commit 8efb8ba1e (one commit earlier). --- src/etc/systemd/system/hostapd@.service.d/override.conf | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/etc/systemd/system/hostapd@.service.d/override.conf b/src/etc/systemd/system/hostapd@.service.d/override.conf index b03dbc299..bb8e81d7a 100644 --- a/src/etc/systemd/system/hostapd@.service.d/override.conf +++ b/src/etc/systemd/system/hostapd@.service.d/override.conf @@ -3,7 +3,8 @@ After= After=vyos-router.service [Service] -WorkingDirectory=/run/LCDd +WorkingDirectory=/run/hostapd +EnvironmentFile= ExecStart= -ExecStart=/usr/sbin/LCDd -s 1 -f -c /run/LCDd/LCDd.conf - +ExecStart=/usr/sbin/hostapd -B -P /run/hostapd/%i.pid /run/hostapd/%i.conf +PIDFile=/run/hostapd/%i.pid -- cgit v1.2.3 From 86ed6b5ebfd43f3baac3becd0c0524577812d11d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 12:34:13 +0200 Subject: wireless: T2057: ensure interface state is properly set --- python/vyos/ifconfig/wireless.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py index 3122ac0a3..a50346ffa 100644 --- a/python/vyos/ifconfig/wireless.py +++ b/python/vyos/ifconfig/wireless.py @@ -71,6 +71,25 @@ class WiFiIf(Interface): return config + 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 first + super().update(config) + + # 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) + @Interface.register class WiFiModemIf(WiFiIf): -- cgit v1.2.3 From 02e995265a4548aac7a990ae3e176f993b2f5a17 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 13:05:03 +0200 Subject: vyos.util: T2755: add vyos_dict_search() to traverse a dictionary This is faster implementation then using jmespath.search('foo.bar', dict). --- python/vyos/util.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/python/vyos/util.py b/python/vyos/util.py index d27a8a3e7..4cc25764b 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -672,3 +672,17 @@ def find_device_file(device): return os.path.join(root, basename) return None + +def vyos_dict_search(dict, path): + """ Traverse Python dictionary (dict) delimited by dot (.). + Return value of key if found, None otherwise. + + This is faster implementation then jmespath.search('foo.bar', dict)""" + parts = path.split('.') + inside = parts[:-1] + if not inside: + return dict[path] + c = dict + for p in parts[:-1]: + c = c.get(p, {}) + return c.get(parts[-1], None) -- cgit v1.2.3 From 20ef1aab793504cf4956dedeeadaf528933b7ccf Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 13:06:37 +0200 Subject: vyos.configverify: T2677: extend verify_dhcpv6() for non duplicate sla-ids --- python/vyos/configverify.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index d1519b0ac..264dd1c30 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -91,9 +91,26 @@ def verify_dhcpv6(config): Common helper function used by interface implementations to perform recurring validation of DHCPv6 options which are mutually exclusive. """ - if {'parameters_only', 'temporary'} <= set(config.get('dhcpv6_options', {})): - raise ConfigError('DHCPv6 temporary and parameters-only options ' - 'are mutually exclusive!') + if 'dhcpv6_options' in config: + from vyos.util import vyos_dict_search + 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 vyos_dict_search(config, 'dhcpv6_options.pd'): + sla_ids = [] + for interface in vyos_dict_search(config, f'dhcpv6_options.pd.{pd}.interface'): + sla_id = vyos_dict_search(config, + f'dhcpv6_options.pd.{pd}.interface.{interface}.sla_id') + sla_ids.append(sla_id) + + # 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): """ -- cgit v1.2.3 From bd7f30dcd55866de125af012ac63b96dc226a1f1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 13:26:19 +0200 Subject: wireless: validate hostapd/wpa_supplicant is running --- scripts/cli/test_interfaces_wireless.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/scripts/cli/test_interfaces_wireless.py b/scripts/cli/test_interfaces_wireless.py index ff293ac84..fae233244 100755 --- a/scripts/cli/test_interfaces_wireless.py +++ b/scripts/cli/test_interfaces_wireless.py @@ -18,6 +18,7 @@ import os import unittest from base_interfaces_test import BasicInterfaceTest +from psutil import process_iter from vyos.util import check_kmod class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): @@ -38,15 +39,19 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): self._interfaces = list(self._options) self.session.set(['system', 'wifi-regulatory-domain', 'SE']) - def test_wifi_client(self): - """ test creation of a wireless station """ - for intf in self._interfaces: - # prepare interfaces - for option in self._options.get(intf, []): - self.session.set(self._base_path + [intf] + option.split()) + def test_add_address_single(self): + """ derived method to check if member interfaces are enslaved properly """ + super().test_add_address_single() - # commit changes - self.session.commit() + for option, option_value in self._options.items(): + if 'type access-point' in option_value: + # Check for running process + self.assertIn('hostapd', (p.name() for p in process_iter())) + elif 'type station' in option_value: + # Check for running process + self.assertIn('wpa_supplicant', (p.name() for p in process_iter())) + else: + self.assertTrue(False) if __name__ == '__main__': check_kmod('mac80211_hwsim') -- cgit v1.2.3 From aa25690c83c4812c92490d29b564dd0330b24d34 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 14:22:41 +0200 Subject: T2755: convert jmespath.search() to vyos_dict_search() for performance --- python/vyos/configdict.py | 6 +++--- python/vyos/configverify.py | 9 +++++---- python/vyos/ifconfig/bond.py | 6 +++--- python/vyos/ifconfig/bridge.py | 9 ++++----- python/vyos/ifconfig/ethernet.py | 12 ++++++------ python/vyos/ifconfig/interface.py | 33 +++++++++++++++++---------------- python/vyos/util.py | 2 +- 7 files changed, 39 insertions(+), 38 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index c1e93955e..bd8624ced 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -17,7 +17,6 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion. """ import os -import jmespath from enum import Enum from copy import deepcopy @@ -226,8 +225,9 @@ def get_interface_dict(config, base, ifname=''): Will return a dictionary with the necessary interface configuration """ - from vyos.xml import defaults + from vyos.util import vyos_dict_search from vyos.validate import is_member + from vyos.xml import defaults if not ifname: # determine tagNode instance @@ -273,7 +273,7 @@ def get_interface_dict(config, base, ifname=''): # XXX: T2636 workaround: convert string to a list with one element if isinstance(eui64, str): eui64 = [eui64] - tmp = jmespath.search('ipv6.address', dict) + tmp = vyos_dict_search('ipv6.address', dict) if not tmp: dict.update({'ipv6': {'address': {'eui64_old': eui64}}}) else: diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 264dd1c30..7e1930878 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -93,17 +93,18 @@ def verify_dhcpv6(config): """ if 'dhcpv6_options' in config: from vyos.util import vyos_dict_search + 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 vyos_dict_search(config, 'dhcpv6_options.pd'): + for pd in vyos_dict_search('dhcpv6_options.pd', config): sla_ids = [] - for interface in vyos_dict_search(config, f'dhcpv6_options.pd.{pd}.interface'): - sla_id = vyos_dict_search(config, - f'dhcpv6_options.pd.{pd}.interface.{interface}.sla_id') + for interface in vyos_dict_search(f'dhcpv6_options.pd.{pd}.interface', config): + sla_id = vyos_dict_search( + f'dhcpv6_options.pd.{pd}.interface.{interface}.sla_id', config) sla_ids.append(sla_id) # Check for duplicates diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 193cea321..64407401b 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -14,12 +14,12 @@ # License along with this library. If not, see . import os -import jmespath from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN from vyos.util import cmd +from vyos.util import vyos_dict_search from vyos.validate import assert_list from vyos.validate import assert_positive @@ -336,7 +336,7 @@ class BondIf(Interface): self.set_arp_ip_target('-' + addr) # Add configured ARP target addresses - value = jmespath.search('arp_monitor.target', config) + value = vyos_dict_search('arp_monitor.target', config) if isinstance(value, str): value = [value] if value: @@ -359,7 +359,7 @@ class BondIf(Interface): if value: self.set_mode(value) # Add (enslave) interfaces to bond - value = jmespath.search('member.interface', config) + value = vyos_dict_search('member.interface', config) if value: for interface in value: # if we've come here we already verified the interface diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 466e6b682..4c76fe996 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -13,13 +13,12 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . -import jmespath - from vyos.ifconfig.interface import Interface from vyos.ifconfig.stp import STP from vyos.validate import assert_boolean from vyos.validate import assert_positive from vyos.util import cmd +from vyos.util import vyos_dict_search @Interface.register class BridgeIf(Interface): @@ -223,18 +222,18 @@ class BridgeIf(Interface): self.set_stp(value) # enable or disable IGMP querier - tmp = jmespath.search('igmp.querier', config) + tmp = vyos_dict_search('igmp.querier', config) value = '1' if (tmp != None) else '0' self.set_multicast_querier(value) # remove interface from bridge - tmp = jmespath.search('member.interface_remove', config) + tmp = vyos_dict_search('member.interface_remove', config) if tmp: for member in tmp: self.del_port(member) STPBridgeIf = STP.enable(BridgeIf) - tmp = jmespath.search('member.interface', config) + tmp = vyos_dict_search('member.interface', config) if tmp: for interface, interface_config in tmp.items(): # if we've come here we already verified the interface diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index b2f701e00..17c1bd64d 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -15,12 +15,12 @@ import os import re -import jmespath from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN from vyos.validate import assert_list from vyos.util import run +from vyos.util import vyos_dict_search @Interface.register @VLAN.enable @@ -268,27 +268,27 @@ class EthernetIf(Interface): self.set_flow_control(value) # GRO (generic receive offload) - tmp = jmespath.search('offload_options.generic_receive', config) + tmp = vyos_dict_search('offload_options.generic_receive', config) value = tmp if (tmp != None) else 'off' self.set_gro(value) # GSO (generic segmentation offload) - tmp = jmespath.search('offload_options.generic_segmentation', config) + tmp = vyos_dict_search('offload_options.generic_segmentation', config) value = tmp if (tmp != None) else 'off' self.set_gso(value) # scatter-gather option - tmp = jmespath.search('offload_options.scatter_gather', config) + tmp = vyos_dict_search('offload_options.scatter_gather', config) value = tmp if (tmp != None) else 'off' self.set_sg(value) # TSO (TCP segmentation offloading) - tmp = jmespath.search('offload_options.udp_fragmentation', config) + tmp = vyos_dict_search('offload_options.udp_fragmentation', config) value = tmp if (tmp != None) else 'off' self.set_tso(value) # UDP fragmentation offloading - tmp = jmespath.search('offload_options.udp_fragmentation', config) + tmp = vyos_dict_search('offload_options.udp_fragmentation', config) value = tmp if (tmp != None) else 'off' self.set_ufo(value) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 537c4bc2d..67ba973c4 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -34,6 +34,7 @@ from vyos.configdict import list_diff from vyos.configdict import dict_merge from vyos.template import render from vyos.util import mac2eui64 +from vyos.util import vyos_dict_search from vyos.validate import is_ipv4 from vyos.validate import is_ipv6 from vyos.validate import is_intf_addr_assigned @@ -824,7 +825,7 @@ class Interface(Control): lease_file = f'{config_base}_{ifname}.leases' if enable and 'disable' not in self._config: - if jmespath.search('dhcp_options.host_name', self._config) == None: + if vyos_dict_search('dhcp_options.host_name', self._config) == None: # read configured system hostname. # maybe change to vyos hostd client ??? hostname = 'vyos' @@ -907,7 +908,7 @@ class Interface(Control): # always ensure DHCPv6 client is stopped (when not configured as client # for IPv6 address or prefix delegation - dhcpv6pd = jmespath.search('dhcpv6_options.pd', config) + dhcpv6pd = vyos_dict_search('dhcpv6_options.pd', config) if 'dhcpv6' not in new_addr or dhcpv6pd == None: self.del_addr('dhcpv6') @@ -935,59 +936,59 @@ class Interface(Control): self.set_vrf(config.get('vrf', '')) # Configure ARP cache timeout in milliseconds - has default value - tmp = jmespath.search('ip.arp_cache_timeout', config) + tmp = vyos_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 = jmespath.search('ip.disable_arp_filter', config) + tmp = vyos_dict_search('ip.disable_arp_filter', config) value = '0' if (tmp != None) else '1' self.set_arp_filter(value) # Configure ARP accept - tmp = jmespath.search('ip.enable_arp_accept', config) + tmp = vyos_dict_search('ip.enable_arp_accept', config) value = '1' if (tmp != None) else '0' self.set_arp_accept(value) # Configure ARP announce - tmp = jmespath.search('ip.enable_arp_announce', config) + tmp = vyos_dict_search('ip.enable_arp_announce', config) value = '1' if (tmp != None) else '0' self.set_arp_announce(value) # Configure ARP ignore - tmp = jmespath.search('ip.enable_arp_ignore', config) + tmp = vyos_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 = jmespath.search('ip.enable_proxy_arp', config) + tmp = vyos_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 = jmespath.search('ip.proxy_arp_pvlan', config) + tmp = vyos_dict_search('ip.proxy_arp_pvlan', config) value = '1' if (tmp != None) else '0' self.set_proxy_arp_pvlan(value) # IPv6 forwarding - tmp = jmespath.search('ipv6.disable_forwarding', config) + tmp = vyos_dict_search('ipv6.disable_forwarding', config) value = '0' if (tmp != None) else '1' self.set_ipv6_forwarding(value) # IPv6 router advertisements - tmp = jmespath.search('ipv6.address.autoconf', config) + tmp = vyos_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 = jmespath.search('ipv6.address.autoconf', config) + tmp = vyos_dict_search('ipv6.address.autoconf', config) value = '1' if (tmp != None) else '0' self.set_ipv6_autoconf(value) # IPv6 Duplicate Address Detection (DAD) tries - tmp = jmespath.search('ipv6.dup_addr_detect_transmits', config) + tmp = vyos_dict_search('ipv6.dup_addr_detect_transmits', config) value = tmp if (tmp != None) else '1' self.set_ipv6_dad_messages(value) @@ -996,7 +997,7 @@ class Interface(Control): self.set_mtu(config.get('mtu')) # Delete old IPv6 EUI64 addresses before changing MAC - tmp = jmespath.search('ipv6.address.eui64_old', config) + tmp = vyos_dict_search('ipv6.address.eui64_old', config) if tmp: for addr in tmp: self.del_ipv6_eui64_address(addr) @@ -1011,7 +1012,7 @@ class Interface(Control): self.set_mac(mac) # Manage IPv6 link-local addresses - tmp = jmespath.search('ipv6.address.no_default_link_local', config) + tmp = vyos_dict_search('ipv6.address.no_default_link_local', config) # we must check explicitly for None type as if the key is set we will # get an empty dict () if tmp is not None: @@ -1020,7 +1021,7 @@ class Interface(Control): self.add_ipv6_eui64_address('fe80::/64') # Add IPv6 EUI-based addresses - tmp = jmespath.search('ipv6.address.eui64', config) + tmp = vyos_dict_search('ipv6.address.eui64', config) if tmp: # XXX: T2636 workaround: convert string to a list with one element if isinstance(tmp, str): diff --git a/python/vyos/util.py b/python/vyos/util.py index 4cc25764b..84aa16791 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -673,7 +673,7 @@ def find_device_file(device): return None -def vyos_dict_search(dict, path): +def vyos_dict_search(path, dict): """ Traverse Python dictionary (dict) delimited by dot (.). Return value of key if found, None otherwise. -- cgit v1.2.3 From 11da3f091a98436044e157892cdefd41a2358e42 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 14:45:15 +0200 Subject: pppoe: validate dhcpv6 client will be started --- scripts/cli/test_interfaces_pppoe.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/cli/test_interfaces_pppoe.py b/scripts/cli/test_interfaces_pppoe.py index 7e26b7dac..822f05de6 100755 --- a/scripts/cli/test_interfaces_pppoe.py +++ b/scripts/cli/test_interfaces_pppoe.py @@ -152,7 +152,11 @@ class PPPoEInterfaceTest(unittest.TestCase): self.assertTrue(running) # We can not check if wide-dhcpv6 process is running as it is started - # after the PPP interface gets a link to the ISP + # after the PPP interface gets a link to the ISP - but we can see if + # it would be started by the scripts + tmp = read_file(f'/etc/ppp/ipv6-up.d/1000-vyos-pppoe-{interface}') + tmp = re.findall(f'systemctl start dhcp6c@{interface}.service', tmp) + self.assertTrue(tmp) if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 049a87ed9940d22e8bfd39947ba1a92dee82b04a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 20:26:20 +0200 Subject: pppoe-server: initial smoketest with local and radius auth --- scripts/cli/test_service_pppoe-server.py | 154 +++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100755 scripts/cli/test_service_pppoe-server.py diff --git a/scripts/cli/test_service_pppoe-server.py b/scripts/cli/test_service_pppoe-server.py new file mode 100755 index 000000000..318b6530a --- /dev/null +++ b/scripts/cli/test_service_pppoe-server.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import re +import os +import unittest + +from configparser import ConfigParser +from psutil import process_iter +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError + +base_path = ['service', 'pppoe-server'] +local_if = ['interfaces', 'dummy', 'dum667'] +pppoe_conf = '/run/accel-pppd/pppoe.conf' + +ac_name = 'ACN' +subnet = '172.18.0.0/24' +gateway = '192.0.2.1' +nameserver = '9.9.9.9' +mtu = '1492' +interface = 'eth0' + +class TestServicePPPoEServer(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + self.session.delete(base_path) + + def tearDown(self): + self.session.delete(base_path) + self.session.delete(local_if) + self.session.commit() + del self.session + + def verify(self, conf): + # validate some common values in the configuration + for tmp in ['log_syslog', 'pppoe', 'chap-secrets', 'ippool', 'ipv6pool', + 'ipv6_nd', 'ipv6_dhcp', '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 configured subnet + self.assertEqual(conf['ip-pool'][subnet], None) + self.assertEqual(conf['ip-pool']['gw-ip-address'], gateway) + + # check ppp + self.assertTrue(conf['ppp'].getboolean('verbose')) + self.assertTrue(conf['ppp'].getboolean('check-ip')) + self.assertFalse(conf['ppp'].getboolean('ccp')) + self.assertEqual(conf['ppp']['min-mtu'], mtu) + self.assertEqual(conf['ppp']['mtu'], mtu) + self.assertEqual(conf['ppp']['mppe'], 'prefer') + self.assertEqual(conf['ppp']['lcp-echo-interval'], '30') + self.assertEqual(conf['ppp']['lcp-echo-timeout'], '0') + self.assertEqual(conf['ppp']['lcp-echo-failure'], '3') + + def test_local_auth(self): + """ Test configuration of local authentication for PPPoE server """ + self.session.set(local_if + ['address', '192.0.2.1/32']) + self.session.set(base_path + ['access-concentrator', ac_name]) + self.session.set(base_path + ['authentication', 'local-users', 'username', 'vyos', 'password', 'vyos']) + self.session.set(base_path + ['authentication', 'mode', 'local']) + self.session.set(base_path + ['client-ip-pool', 'subnet', subnet]) + self.session.set(base_path + ['name-server', nameserver]) + self.session.set(base_path + ['interface', interface]) + self.session.set(base_path + ['local-ip', gateway]) + + # commit changes + self.session.commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True) + conf.read(pppoe_conf) + + # basic verification + self.verify(conf) + + # check auth + self.assertEqual(conf['chap-secrets']['chap-secrets'], '/run/accel-pppd/pppoe.chap-secrets') + self.assertEqual(conf['chap-secrets']['gw-ip-address'], gateway) + + # Check for running process + self.assertTrue('accel-pppd' in (p.name() for p in process_iter())) + + def test_radius_auth(self): + """ Test configuration of RADIUS authentication for PPPoE server """ + radius_server = '192.0.2.22' + radius_key = 'secretVyOS' + radius_port = '2000' + radius_port_acc = '3000' + + self.session.set(local_if + ['address', '192.0.2.1/32']) + self.session.set(base_path + ['access-concentrator', ac_name]) + self.session.set(base_path + ['authentication', 'radius', 'server', radius_server, 'key', radius_key]) + self.session.set(base_path + ['authentication', 'radius', 'server', radius_server, 'port', radius_port]) + self.session.set(base_path + ['authentication', 'radius', 'server', radius_server, 'acct-port', radius_port_acc]) + + self.session.set(base_path + ['authentication', 'mode', 'radius']) + self.session.set(base_path + ['client-ip-pool', 'subnet', subnet]) + self.session.set(base_path + ['name-server', nameserver]) + self.session.set(base_path + ['interface', interface]) + self.session.set(base_path + ['local-ip', gateway]) + + # commit changes + self.session.commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True) + conf.read(pppoe_conf) + + # basic verification + self.verify(conf) + + # check auth + self.assertTrue(conf['radius'].getboolean('verbose')) + self.assertTrue(conf['radius']['acct-timeout'], '3') + self.assertTrue(conf['radius']['timeout'], '3') + self.assertTrue(conf['radius']['max-try'], '3') + self.assertTrue(conf['radius']['gw-ip-address'], gateway) + + 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]) + + # Check for running process + self.assertTrue('accel-pppd' in (p.name() for p in process_iter())) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From f7e3e7fe417e665f42af0025bccbb4f2ba56fbe4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 20:34:32 +0200 Subject: lcd: T2564: make hello and goodbye message oneline ... required for smoketest and ConfigParser --- data/templates/lcd/LCDd.conf.tmpl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/data/templates/lcd/LCDd.conf.tmpl b/data/templates/lcd/LCDd.conf.tmpl index bde177b6a..f8ad62ff4 100644 --- a/data/templates/lcd/LCDd.conf.tmpl +++ b/data/templates/lcd/LCDd.conf.tmpl @@ -78,12 +78,10 @@ User=nobody Foreground=yes # Hello message: each entry represents a display line; default: builtin -Hello="Starting VyOS" -Hello=" ... " +Hello="Starting VyOS..." # GoodBye message: each entry represents a display line; default: builtin -GoodBye=" VyOS shutting" -GoodBye=" down... " +GoodBye="VyOS shutdown..." # Sets the interval in microseconds for updating the display. # [default: 125000 meaning 8Hz] -- cgit v1.2.3 From ffb3e2dc9df93c53abbd7ff0742ea75090d8c33d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 20:35:29 +0200 Subject: lcd: T2564: limit LCD display update to 2 updates per second ... this reduces CPU load. --- data/templates/lcd/LCDd.conf.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/lcd/LCDd.conf.tmpl b/data/templates/lcd/LCDd.conf.tmpl index f8ad62ff4..6cf6a440f 100644 --- a/data/templates/lcd/LCDd.conf.tmpl +++ b/data/templates/lcd/LCDd.conf.tmpl @@ -85,7 +85,7 @@ GoodBye="VyOS shutdown..." # Sets the interval in microseconds for updating the display. # [default: 125000 meaning 8Hz] -FrameInterval=250000 # 4 updates per second +FrameInterval=500000 # 2 updates per second # Sets the default time in seconds to displays a screen. [default: 4] WaitTime=1 -- cgit v1.2.3 From 71a2e3fad3bd773391196f84171c606cad2e3516 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 20:47:20 +0200 Subject: lcd: adapt test to new CLI design --- scripts/cli/test_system_display.py | 135 ------------------------------------- scripts/cli/test_system_lcd.py | 135 +++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 135 deletions(-) delete mode 100755 scripts/cli/test_system_display.py create mode 100755 scripts/cli/test_system_lcd.py diff --git a/scripts/cli/test_system_display.py b/scripts/cli/test_system_display.py deleted file mode 100755 index f9fb00aa5..000000000 --- a/scripts/cli/test_system_display.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/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 . - -import re -import os -import unittest -import configparser - -from psutil import process_iter -from vyos.configsession import ConfigSession, ConfigSessionError -from vyos.util import read_file - -base_path = ['system', 'display'] - -""" - system display model (sdec|ezio) - system display show host (cpu|cpu-all|cpu-hist|disk|load-hist|memory|proc|uptime) - network interface alias - units (bps|Bps|pps) - clock (big|mini|date-time) - title - - system display time - system display hello - system display bye - system display disabled -""" - -class SystemDisplayTest(unittest.TestCase): - def setUp(self): - self.session = ConfigSession(os.getpid()) - - def tearDown(self): - self.session.delete(base_path) - self.session.commit() - del self.session - - def test_system_display(self): - # configure some system display - self.session.set(base_path + ['hello', 'Welcome to VyOS']) - self.session.set(base_path + ['bye', 'Bye from VyOS']) - self.session.set(base_path + ['time', '30']) - - # check validate() - a model and a show are required - with self.assertRaises(ConfigSessionError): - self.session.commit() - - self.session.set(base_path + ['model', 'ezio']) - - # check validate() - a show required - with self.assertRaises(ConfigSessionError): - self.session.commit() - - self.session.set(base_path + ['show', 'clock', 'big']) - - self.session.set(base_path + ['show', 'network', 'units', 'pps']) - self.session.set(base_path + ['show', 'network', 'interface', 'eth0', 'alias', 'WAN']) - self.session.set(base_path + ['show', 'network', 'interface', 'eth1', 'alias', 'LAN']) - self.session.set(base_path + ['show', 'network', 'interface', 'eth2', 'alias', 'WIFI']) - # One too many - self.session.set(base_path + ['show', 'network', 'interface', 'eth3']) - - # check validate() - more then 3 interfaces - with self.assertRaises(ConfigSessionError): - self.session.commit() - - self.session.delete(base_path + ['show', 'network', 'interface', 'eth3']) - - # commit changes - self.session.commit() - - # load up ini-styled LCDd.conf - LCDd_conf = configparser.ConfigParser() - LCDd_conf.read('/run/LCDd/LCDd.lo.conf') - - # Check settings made it into LCDd.conf - self.assertTrue(LCDd_conf['server']['Driver'] == 'hd44780') - self.assertTrue(LCDd_conf['server']['Hello'] == '"Welcome to VyOS"') - self.assertTrue(LCDd_conf['server']['GoodBye'] == '"Bye from VyOS"') - self.assertTrue(LCDd_conf['server']['WaitTime'] == '30') - - self.assertTrue(LCDd_conf['hd44780']['ConnectionType'] == 'ezio') - self.assertTrue(LCDd_conf['hd44780']['Keypad'] == 'yes') - self.assertTrue(LCDd_conf['hd44780']['Size'] == '16x2') - self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_1'] == 'Enter') - self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_2'] == 'Up') - self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_3'] == 'Down') - self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_4'] == 'Escape') - #self.assertTrue(LCDd_conf['hd44780']['Device'] == '/dev/ttyS1') - - # load up ini-styled lcdproc.conf configuration file - lcdproc_conf = configparser.ConfigParser() - - lcdproc_conf.read('/run/lcdproc/lcdproc.lo.conf') - # clock - self.assertTrue(lcdproc_conf['TimeDate']['Active'] == 'false') - self.assertTrue(lcdproc_conf['BigClock']['Active'] == 'true') - self.assertTrue(lcdproc_conf['MiniClock']['Active'] == 'false') - # host - self.assertTrue(lcdproc_conf['CPU']['Active'] == 'false') - self.assertTrue(lcdproc_conf['Memory']['Active'] == 'false') - self.assertTrue(lcdproc_conf['Load']['Active'] == 'false') - # network - self.assertTrue(lcdproc_conf['Iface']['Active'] == 'true') - self.assertTrue(lcdproc_conf['Iface']['Interface0'] == 'eth0') - self.assertTrue(lcdproc_conf['Iface']['Alias0'] == 'WAN') - self.assertTrue(lcdproc_conf['Iface']['Interface1'] == 'eth1') - self.assertTrue(lcdproc_conf['Iface']['Alias1'] == 'LAN') - self.assertTrue(lcdproc_conf['Iface']['Interface2'] == 'eth2') - self.assertTrue(lcdproc_conf['Iface']['Alias2'] == 'WIFI') - self.assertTrue(lcdproc_conf['Iface']['unit'] == 'packet') - # Check if LCdd and lcdproc are running - running = 0 - for p in process_iter(): - if p.name() in ['lcdproc', 'LCDd']: - running += 1 - - # both processes running - self.assertTrue(running == 2) - -if __name__ == '__main__': - unittest.main() diff --git a/scripts/cli/test_system_lcd.py b/scripts/cli/test_system_lcd.py new file mode 100755 index 000000000..f9fb00aa5 --- /dev/null +++ b/scripts/cli/test_system_lcd.py @@ -0,0 +1,135 @@ +#!/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 . + +import re +import os +import unittest +import configparser + +from psutil import process_iter +from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.util import read_file + +base_path = ['system', 'display'] + +""" + system display model (sdec|ezio) + system display show host (cpu|cpu-all|cpu-hist|disk|load-hist|memory|proc|uptime) + network interface alias + units (bps|Bps|pps) + clock (big|mini|date-time) + title + + system display time + system display hello + system display bye + system display disabled +""" + +class SystemDisplayTest(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + + def tearDown(self): + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_system_display(self): + # configure some system display + self.session.set(base_path + ['hello', 'Welcome to VyOS']) + self.session.set(base_path + ['bye', 'Bye from VyOS']) + self.session.set(base_path + ['time', '30']) + + # check validate() - a model and a show are required + with self.assertRaises(ConfigSessionError): + self.session.commit() + + self.session.set(base_path + ['model', 'ezio']) + + # check validate() - a show required + with self.assertRaises(ConfigSessionError): + self.session.commit() + + self.session.set(base_path + ['show', 'clock', 'big']) + + self.session.set(base_path + ['show', 'network', 'units', 'pps']) + self.session.set(base_path + ['show', 'network', 'interface', 'eth0', 'alias', 'WAN']) + self.session.set(base_path + ['show', 'network', 'interface', 'eth1', 'alias', 'LAN']) + self.session.set(base_path + ['show', 'network', 'interface', 'eth2', 'alias', 'WIFI']) + # One too many + self.session.set(base_path + ['show', 'network', 'interface', 'eth3']) + + # check validate() - more then 3 interfaces + with self.assertRaises(ConfigSessionError): + self.session.commit() + + self.session.delete(base_path + ['show', 'network', 'interface', 'eth3']) + + # commit changes + self.session.commit() + + # load up ini-styled LCDd.conf + LCDd_conf = configparser.ConfigParser() + LCDd_conf.read('/run/LCDd/LCDd.lo.conf') + + # Check settings made it into LCDd.conf + self.assertTrue(LCDd_conf['server']['Driver'] == 'hd44780') + self.assertTrue(LCDd_conf['server']['Hello'] == '"Welcome to VyOS"') + self.assertTrue(LCDd_conf['server']['GoodBye'] == '"Bye from VyOS"') + self.assertTrue(LCDd_conf['server']['WaitTime'] == '30') + + self.assertTrue(LCDd_conf['hd44780']['ConnectionType'] == 'ezio') + self.assertTrue(LCDd_conf['hd44780']['Keypad'] == 'yes') + self.assertTrue(LCDd_conf['hd44780']['Size'] == '16x2') + self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_1'] == 'Enter') + self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_2'] == 'Up') + self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_3'] == 'Down') + self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_4'] == 'Escape') + #self.assertTrue(LCDd_conf['hd44780']['Device'] == '/dev/ttyS1') + + # load up ini-styled lcdproc.conf configuration file + lcdproc_conf = configparser.ConfigParser() + + lcdproc_conf.read('/run/lcdproc/lcdproc.lo.conf') + # clock + self.assertTrue(lcdproc_conf['TimeDate']['Active'] == 'false') + self.assertTrue(lcdproc_conf['BigClock']['Active'] == 'true') + self.assertTrue(lcdproc_conf['MiniClock']['Active'] == 'false') + # host + self.assertTrue(lcdproc_conf['CPU']['Active'] == 'false') + self.assertTrue(lcdproc_conf['Memory']['Active'] == 'false') + self.assertTrue(lcdproc_conf['Load']['Active'] == 'false') + # network + self.assertTrue(lcdproc_conf['Iface']['Active'] == 'true') + self.assertTrue(lcdproc_conf['Iface']['Interface0'] == 'eth0') + self.assertTrue(lcdproc_conf['Iface']['Alias0'] == 'WAN') + self.assertTrue(lcdproc_conf['Iface']['Interface1'] == 'eth1') + self.assertTrue(lcdproc_conf['Iface']['Alias1'] == 'LAN') + self.assertTrue(lcdproc_conf['Iface']['Interface2'] == 'eth2') + self.assertTrue(lcdproc_conf['Iface']['Alias2'] == 'WIFI') + self.assertTrue(lcdproc_conf['Iface']['unit'] == 'packet') + # Check if LCdd and lcdproc are running + running = 0 + for p in process_iter(): + if p.name() in ['lcdproc', 'LCDd']: + running += 1 + + # both processes running + self.assertTrue(running == 2) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From a4b064ea73f0d6255f868ad3dec620cad8060f9a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 22:12:00 +0200 Subject: lcd: adjust to cli changes --- scripts/cli/test_system_lcd.py | 103 +++++------------------------------------ 1 file changed, 11 insertions(+), 92 deletions(-) diff --git a/scripts/cli/test_system_lcd.py b/scripts/cli/test_system_lcd.py index f9fb00aa5..931a91c53 100755 --- a/scripts/cli/test_system_lcd.py +++ b/scripts/cli/test_system_lcd.py @@ -14,32 +14,16 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import re import os import unittest -import configparser +from configparser import ConfigParser from psutil import process_iter -from vyos.configsession import ConfigSession, ConfigSessionError -from vyos.util import read_file +from vyos.configsession import ConfigSession -base_path = ['system', 'display'] +base_path = ['system', 'lcd'] -""" - system display model (sdec|ezio) - system display show host (cpu|cpu-all|cpu-hist|disk|load-hist|memory|proc|uptime) - network interface alias - units (bps|Bps|pps) - clock (big|mini|date-time) - title - - system display time - system display hello - system display bye - system display disabled -""" - -class SystemDisplayTest(unittest.TestCase): +class TestSystemLCD(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) @@ -50,86 +34,21 @@ class SystemDisplayTest(unittest.TestCase): def test_system_display(self): # configure some system display - self.session.set(base_path + ['hello', 'Welcome to VyOS']) - self.session.set(base_path + ['bye', 'Bye from VyOS']) - self.session.set(base_path + ['time', '30']) - - # check validate() - a model and a show are required - with self.assertRaises(ConfigSessionError): - self.session.commit() - - self.session.set(base_path + ['model', 'ezio']) - - # check validate() - a show required - with self.assertRaises(ConfigSessionError): - self.session.commit() - - self.session.set(base_path + ['show', 'clock', 'big']) - - self.session.set(base_path + ['show', 'network', 'units', 'pps']) - self.session.set(base_path + ['show', 'network', 'interface', 'eth0', 'alias', 'WAN']) - self.session.set(base_path + ['show', 'network', 'interface', 'eth1', 'alias', 'LAN']) - self.session.set(base_path + ['show', 'network', 'interface', 'eth2', 'alias', 'WIFI']) - # One too many - self.session.set(base_path + ['show', 'network', 'interface', 'eth3']) - - # check validate() - more then 3 interfaces - with self.assertRaises(ConfigSessionError): - self.session.commit() - - self.session.delete(base_path + ['show', 'network', 'interface', 'eth3']) + self.session.set(base_path + ['device', 'ttyS1']) + self.session.set(base_path + ['model', 'cfa-533']) # commit changes self.session.commit() # load up ini-styled LCDd.conf - LCDd_conf = configparser.ConfigParser() - LCDd_conf.read('/run/LCDd/LCDd.lo.conf') - - # Check settings made it into LCDd.conf - self.assertTrue(LCDd_conf['server']['Driver'] == 'hd44780') - self.assertTrue(LCDd_conf['server']['Hello'] == '"Welcome to VyOS"') - self.assertTrue(LCDd_conf['server']['GoodBye'] == '"Bye from VyOS"') - self.assertTrue(LCDd_conf['server']['WaitTime'] == '30') - - self.assertTrue(LCDd_conf['hd44780']['ConnectionType'] == 'ezio') - self.assertTrue(LCDd_conf['hd44780']['Keypad'] == 'yes') - self.assertTrue(LCDd_conf['hd44780']['Size'] == '16x2') - self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_1'] == 'Enter') - self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_2'] == 'Up') - self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_3'] == 'Down') - self.assertTrue(LCDd_conf['hd44780']['KeyMatrix_4_4'] == 'Escape') - #self.assertTrue(LCDd_conf['hd44780']['Device'] == '/dev/ttyS1') - - # load up ini-styled lcdproc.conf configuration file - lcdproc_conf = configparser.ConfigParser() + conf = ConfigParser() + conf.read('/run/LCDd/LCDd.conf') - lcdproc_conf.read('/run/lcdproc/lcdproc.lo.conf') - # clock - self.assertTrue(lcdproc_conf['TimeDate']['Active'] == 'false') - self.assertTrue(lcdproc_conf['BigClock']['Active'] == 'true') - self.assertTrue(lcdproc_conf['MiniClock']['Active'] == 'false') - # host - self.assertTrue(lcdproc_conf['CPU']['Active'] == 'false') - self.assertTrue(lcdproc_conf['Memory']['Active'] == 'false') - self.assertTrue(lcdproc_conf['Load']['Active'] == 'false') - # network - self.assertTrue(lcdproc_conf['Iface']['Active'] == 'true') - self.assertTrue(lcdproc_conf['Iface']['Interface0'] == 'eth0') - self.assertTrue(lcdproc_conf['Iface']['Alias0'] == 'WAN') - self.assertTrue(lcdproc_conf['Iface']['Interface1'] == 'eth1') - self.assertTrue(lcdproc_conf['Iface']['Alias1'] == 'LAN') - self.assertTrue(lcdproc_conf['Iface']['Interface2'] == 'eth2') - self.assertTrue(lcdproc_conf['Iface']['Alias2'] == 'WIFI') - self.assertTrue(lcdproc_conf['Iface']['unit'] == 'packet') - # Check if LCdd and lcdproc are running - running = 0 - for p in process_iter(): - if p.name() in ['lcdproc', 'LCDd']: - running += 1 + self.assertEqual(conf['CFontzPacket']['Model'], '533') + self.assertEqual(conf['CFontzPacket']['Device'], '/dev/ttyS1') # both processes running - self.assertTrue(running == 2) + self.assertTrue('LCDd' in (p.name() for p in process_iter())) if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From bfae3e1a439972cf17e0c8193a91e14e388367d8 Mon Sep 17 00:00:00 2001 From: Runar Borge Date: Tue, 25 Aug 2020 20:05:39 +0200 Subject: T2826: frr: frr python lib error in replace_section because of a bug in frr.py the default before_re will not be working. it is by default without a group, but will be used in a match that needs a group. The whole string could be matched in the group, so the fix is easy to implement. --- python/vyos/frr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/vyos/frr.py b/python/vyos/frr.py index e39b6a914..3fc75bbdf 100644 --- a/python/vyos/frr.py +++ b/python/vyos/frr.py @@ -281,7 +281,7 @@ def replace_section(config, replacement, from_re, to_re=r'!', before_re=r'line v startline and endline tags will be automatically added to the resulting from_re/to_re and before_re regex'es """ - return _replace_section(config, replacement, replace_re=rf'^{from_re}$.*?^{to_re}$', before_re=rf'^{before_re}$') + 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='!'): -- cgit v1.2.3 From 12f566f4566c8ef115b15f0c11280cb3ea9a7673 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 26 Aug 2020 20:00:14 +0200 Subject: pppoe-server: bugfix MPPE node definition PPPoE server supports setting the MPPE preferences, it is implemented as node with leafNodes for require, prefer and deny. This will render a wrong AccelPPP configuration as it will be "None" if specified. pppoe-server { ppp-options { mppe { deny require } } } instead it must be: pppoe-server { ppp-options { mppe require } } --- interface-definitions/service_pppoe-server.xml.in | 42 +++++++++++------------ 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in index c7ba2617a..605f47b37 100644 --- a/interface-definitions/service_pppoe-server.xml.in +++ b/interface-definitions/service_pppoe-server.xml.in @@ -311,31 +311,29 @@ - + Specifies MPPE negotiation preference. (default prefer mppe) + + deny prefer require + + + deny + Deny MPPE + + + prefer + Ask client for MPPE - do not fail on reject + + + require + Ask client for MPPE - drop connection on reject + + + ^(deny|prefer|require)$ + - - - - Ask client for MPPE, if it rejects then drop the connection - - - - - - Ask client for MPPE, if it rejects do not fail - - - - - - Deny MPPE - - - - - + LCP echo-requests/sec -- cgit v1.2.3 From 756c9153e683be83ab1f2786a72e6ed1e65dd3b5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 26 Aug 2020 20:03:54 +0200 Subject: pppoe-server: test some more values --- scripts/cli/test_service_pppoe-server.py | 37 +++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/scripts/cli/test_service_pppoe-server.py b/scripts/cli/test_service_pppoe-server.py index 318b6530a..901ca792d 100755 --- a/scripts/cli/test_service_pppoe-server.py +++ b/scripts/cli/test_service_pppoe-server.py @@ -67,25 +67,33 @@ class TestServicePPPoEServer(unittest.TestCase): # check ppp self.assertTrue(conf['ppp'].getboolean('verbose')) self.assertTrue(conf['ppp'].getboolean('check-ip')) - self.assertFalse(conf['ppp'].getboolean('ccp')) self.assertEqual(conf['ppp']['min-mtu'], mtu) self.assertEqual(conf['ppp']['mtu'], mtu) - self.assertEqual(conf['ppp']['mppe'], 'prefer') self.assertEqual(conf['ppp']['lcp-echo-interval'], '30') self.assertEqual(conf['ppp']['lcp-echo-timeout'], '0') self.assertEqual(conf['ppp']['lcp-echo-failure'], '3') - def test_local_auth(self): - """ Test configuration of local authentication for PPPoE server """ + def basic_config(self): self.session.set(local_if + ['address', '192.0.2.1/32']) + self.session.set(base_path + ['access-concentrator', ac_name]) - self.session.set(base_path + ['authentication', 'local-users', 'username', 'vyos', 'password', 'vyos']) self.session.set(base_path + ['authentication', 'mode', 'local']) self.session.set(base_path + ['client-ip-pool', 'subnet', subnet]) self.session.set(base_path + ['name-server', nameserver]) self.session.set(base_path + ['interface', interface]) self.session.set(base_path + ['local-ip', gateway]) + def test_local_auth(self): + """ Test configuration of local authentication for PPPoE server """ + self.basic_config() + # authentication + self.session.set(base_path + ['authentication', 'local-users', 'username', 'vyos', 'password', 'vyos']) + self.session.set(base_path + ['authentication', 'mode', 'local']) + # other settings + self.session.set(base_path + ['ppp-options', 'ccp']) + self.session.set(base_path + ['ppp-options', 'mppe', 'require']) + self.session.set(base_path + ['limits', 'connection-limit', '20/min']) + # commit changes self.session.commit() @@ -100,6 +108,13 @@ class TestServicePPPoEServer(unittest.TestCase): self.assertEqual(conf['chap-secrets']['chap-secrets'], '/run/accel-pppd/pppoe.chap-secrets') self.assertEqual(conf['chap-secrets']['gw-ip-address'], gateway) + # check pado + self.assertEqual(conf['ppp']['mppe'], 'require') + self.assertTrue(conf['ppp'].getboolean('ccp')) + + # check other settings + self.assertEqual(conf['connlimit']['limit'], '20/min') + # Check for running process self.assertTrue('accel-pppd' in (p.name() for p in process_iter())) @@ -110,17 +125,11 @@ class TestServicePPPoEServer(unittest.TestCase): radius_port = '2000' radius_port_acc = '3000' - self.session.set(local_if + ['address', '192.0.2.1/32']) - self.session.set(base_path + ['access-concentrator', ac_name]) + self.basic_config() self.session.set(base_path + ['authentication', 'radius', 'server', radius_server, 'key', radius_key]) self.session.set(base_path + ['authentication', 'radius', 'server', radius_server, 'port', radius_port]) self.session.set(base_path + ['authentication', 'radius', 'server', radius_server, 'acct-port', radius_port_acc]) - self.session.set(base_path + ['authentication', 'mode', 'radius']) - self.session.set(base_path + ['client-ip-pool', 'subnet', subnet]) - self.session.set(base_path + ['name-server', nameserver]) - self.session.set(base_path + ['interface', interface]) - self.session.set(base_path + ['local-ip', gateway]) # commit changes self.session.commit() @@ -147,6 +156,10 @@ class TestServicePPPoEServer(unittest.TestCase): self.assertEqual(f'req-limit=0', server[4]) self.assertEqual(f'fail-time=0', server[5]) + # check defaults + self.assertEqual(conf['ppp']['mppe'], 'prefer') + self.assertFalse(conf['ppp'].getboolean('ccp')) + # Check for running process self.assertTrue('accel-pppd' in (p.name() for p in process_iter())) -- cgit v1.2.3 From a7ab5c9f57d216818cf896ef163a314f07cd7c93 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 26 Aug 2020 13:11:48 -0500 Subject: https: T2830: update to use get_config_dict --- data/templates/https/nginx.default.tmpl | 2 +- src/conf_mode/https.py | 97 +++++++++++++++++++++------------ 2 files changed, 62 insertions(+), 37 deletions(-) diff --git a/data/templates/https/nginx.default.tmpl b/data/templates/https/nginx.default.tmpl index aaa652e09..a20be45ae 100644 --- a/data/templates/https/nginx.default.tmpl +++ b/data/templates/https/nginx.default.tmpl @@ -52,7 +52,7 @@ server { error_page 501 502 503 =200 @50*_json; -{% if api_somewhere %} +{% if api_set %} location @50*_json { default_type application/json; return 200 '{"error": "service https api unavailable at this proxy address: set service https api-restrict virtual-host"}'; diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index 3dae0fddb..a13f131ab 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -14,9 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os +import sys -from sys import exit from copy import deepcopy import vyos.defaults @@ -33,6 +32,11 @@ airbag.enable() config_file = '/etc/nginx/sites-available/default' certbot_dir = vyos.defaults.directories['certbot'] +# https config needs to coordinate several subsystems: api, certbot, +# self-signed certificate, as well as the virtual hosts defined within the +# https config definition itself. Consequently, one needs a general dict, +# encompassing the https and other configs, and a list of such virtual hosts +# (server blocks in nginx terminology) to pass to the jinja2 template. default_server_block = { 'id' : '', 'address' : '*', @@ -44,44 +48,57 @@ default_server_block = { } def get_config(): - server_block_list = [] conf = Config() if not conf.exists('service https'): return None - else: - conf.set_level('service https') - if not conf.exists('virtual-host'): + server_block_list = [] + https_dict = conf.get_config_dict('service https', get_first_key=True) + + # organize by vhosts + + vhost_dict = https_dict.get('virtual-host', {}) + + if not vhost_dict: + # no specified virtual hosts (server blocks); use default server_block_list.append(default_server_block) else: - for vhost in conf.list_nodes('virtual-host'): + for vhost in list(vhost_dict): server_block = deepcopy(default_server_block) server_block['id'] = vhost - if conf.exists(f'virtual-host {vhost} listen-address'): - addr = conf.return_value(f'virtual-host {vhost} listen-address') - server_block['address'] = addr - if conf.exists(f'virtual-host {vhost} listen-port'): - port = conf.return_value(f'virtual-host {vhost} listen-port') - server_block['port'] = port - if conf.exists(f'virtual-host {vhost} server-name'): - names = conf.return_values(f'virtual-host {vhost} server-name') - server_block['name'] = names[:] + data = vhost_dict.get(vhost, {}) + server_block['address'] = data.get('listen-address', '*') + server_block['port'] = data.get('listen-port', '443') + name = data.get('server-name', ['_']) + # XXX: T2636 workaround: convert string to a list with one element + if not isinstance(name, list): + name = [name] + server_block['name'] = name server_block_list.append(server_block) + # get certificate data + + cert_dict = https_dict.get('certificates', {}) + + # self-signed certificate + vyos_cert_data = {} - if conf.exists('certificates system-generated-certificate'): + if 'system-generated-certificate' in list(cert_dict): vyos_cert_data = vyos.defaults.vyos_cert_data if vyos_cert_data: for block in server_block_list: block['vyos_cert'] = vyos_cert_data + # letsencrypt certificate using certbot + certbot = False - certbot_domains = [] - if conf.exists('certificates certbot domain-name'): - certbot_domains = conf.return_values('certificates certbot domain-name') - if certbot_domains: + cert_domains = cert_dict.get('certbot', {}).get('domain-name', []) + if cert_domains: + # XXX: T2636 workaround: convert string to a list with one element + if not isinstance(cert_domains, list): + cert_domains = [cert_domains] certbot = True - for domain in certbot_domains: + for domain in cert_domains: sub_list = vyos.certbot_util.choose_server_block(server_block_list, domain) if sub_list: @@ -89,25 +106,30 @@ def get_config(): sb['certbot'] = True sb['certbot_dir'] = certbot_dir # certbot organizes certificates by first domain - sb['certbot_domain_dir'] = certbot_domains[0] + sb['certbot_domain_dir'] = cert_domains[0] - api_somewhere = False + # get api data + + api_set = False api_data = {} - if conf.exists('api'): - api_somewhere = True + if 'api' in list(https_dict): + api_set = True api_data = vyos.defaults.api_data - if conf.exists('api port'): - port = conf.return_value('api port') + api_settings = https_dict.get('api', {}) + if api_settings: + port = api_settings.get('port', '') + if port: api_data['port'] = port - if conf.exists('api-restrict virtual-host'): - vhosts = conf.return_values('api-restrict virtual-host') + vhosts = https_dict.get('api-restrict', {}).get('virtual-host', []) + # XXX: T2636 workaround: convert string to a list with one element + if not isinstance(vhosts, list): + vhosts = [vhosts] + if vhosts: api_data['vhost'] = vhosts[:] if api_data: - # we do not want to include 'vhost' key as part of - # vyos.defaults.api_data, so check for key existence - vhost_list = api_data.get('vhost') - if vhost_list is None: + vhost_list = api_data.get('vhost', []) + if not vhost_list: for block in server_block_list: block['api'] = api_data else: @@ -115,9 +137,12 @@ def get_config(): if block['id'] in vhost_list: block['api'] = api_data + # return dict for use in template + https = {'server_block_list' : server_block_list, - 'api_somewhere': api_somewhere, + 'api_set': api_set, 'certbot': certbot} + return https def verify(https): @@ -157,4 +182,4 @@ if __name__ == '__main__': apply(c) except ConfigError as e: print(e) - exit(1) + sys.exit(1) -- cgit v1.2.3 From efeac80f82810c4d752b9633861d691e36de4385 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 26 Aug 2020 20:19:56 +0200 Subject: pppoe-server: T2829: migrate 'ppp-options mppe' to leafNode --- src/migration-scripts/pppoe-server/2-to-3 | 1 - src/migration-scripts/pppoe-server/3-to-4 | 54 +++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100755 src/migration-scripts/pppoe-server/3-to-4 diff --git a/src/migration-scripts/pppoe-server/2-to-3 b/src/migration-scripts/pppoe-server/2-to-3 index fa6ef02da..5f9730a41 100755 --- a/src/migration-scripts/pppoe-server/2-to-3 +++ b/src/migration-scripts/pppoe-server/2-to-3 @@ -17,7 +17,6 @@ # - remove primary/secondary identifier from nameserver import os -import sys from sys import argv, exit from vyos.configtree import ConfigTree diff --git a/src/migration-scripts/pppoe-server/3-to-4 b/src/migration-scripts/pppoe-server/3-to-4 new file mode 100755 index 000000000..ed5a01625 --- /dev/null +++ b/src/migration-scripts/pppoe-server/3-to-4 @@ -0,0 +1,54 @@ +#!/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 . + +# change mppe node to a leaf node with value prefer + +import os + +from sys import argv, exit +from vyos.configtree import ConfigTree + +if (len(argv) < 1): + print("Must specify file name!") + exit(1) + +file_name = argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) +base = ['service', 'pppoe-server'] +if not config.exists(base): + # Nothing to do + exit(0) +else: + mppe_base = base + ['ppp-options', 'mppe'] + if config.exists(mppe_base): + # drop node first ... + config.delete(mppe_base) + # ... and set new default + config.set(mppe_base, value='prefer') + + print(config.to_string()) + exit(1) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) -- cgit v1.2.3 From 778a6d590b6a53015fb2bbc3c4a7e1088547eb2a Mon Sep 17 00:00:00 2001 From: sever-sever Date: Thu, 27 Aug 2020 09:23:52 +0000 Subject: rip: T2833: Fix distribute-list filter --- src/conf_mode/protocols_rip.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index 4f8816d61..95e8ce901 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -97,7 +97,7 @@ def get_config(): # Get distribute list interface old_rip for dist_iface in conf.list_effective_nodes('distribute-list interface'): # Set level 'distribute-list interface ethX' - conf.set_level((str(base)) + ' distribute-list interface ' + dist_iface) + conf.set_level(base + ['distribute-list', 'interface', dist_iface]) rip_conf['rip']['distribute'].update({ dist_iface : { 'iface_access_list_in': conf.return_effective_value('access-list in'.format(dist_iface)), @@ -125,7 +125,7 @@ def get_config(): # Get distribute list interface for dist_iface in conf.list_nodes('distribute-list interface'): # Set level 'distribute-list interface ethX' - conf.set_level((str(base)) + ' distribute-list interface ' + dist_iface) + conf.set_level(base + ['distribute-list', 'interface', dist_iface]) rip_conf['rip']['distribute'].update({ dist_iface : { 'iface_access_list_in': conf.return_value('access-list in'.format(dist_iface)), @@ -148,7 +148,7 @@ def get_config(): if conf.exists('prefix-list out'.format(dist_iface)): rip_conf['rip']['iface_prefix_list_out'] = conf.return_value('prefix-list out'.format(dist_iface)) - conf.set_level((str(base)) + ' distribute-list') + conf.set_level(base + ['distribute-list']) # Get distribute list, access-list in if conf.exists_effective('access-list in'): -- cgit v1.2.3 From da9ec80f80b15d88d3bc2bc1ee13e7136ee14c97 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 27 Aug 2020 21:13:27 +0200 Subject: Debian: set proper changelog version --- debian/changelog | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index fba9d77d0..2b65b22c6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -vyos-1x (1.0.0) unstable; urgency=medium +vyos-1x (1.3dev0) unstable; urgency=medium * Dummy changelog entry for vyos-1x repository This is a internal VyOS package and the VyOS package process does not use @@ -7,4 +7,4 @@ vyos-1x (1.0.0) unstable; urgency=medium The correct verion number of this package is auto-generated by GIT on build-time - -- Runar Borge Sat, 9 May 2020 22:00:00 +0100 + -- VyOS maintainers and contributors Wed, 26 Aug 2020 19:07:24 +0000 -- cgit v1.2.3 From c29ed43a720f0205dbafa26a0048076bba9d7333 Mon Sep 17 00:00:00 2001 From: hagbard Date: Fri, 28 Aug 2020 12:01:29 -0700 Subject: T2836: show system integrity broken --- op-mode-definitions/show-system.xml | 2 +- src/op_mode/show_system_integrity.py | 70 ++++++++++++++++++++++++++++++++++++ src/op_mode/system_integrity.py | 70 ------------------------------------ 3 files changed, 71 insertions(+), 71 deletions(-) create mode 100755 src/op_mode/show_system_integrity.py delete mode 100755 src/op_mode/system_integrity.py diff --git a/op-mode-definitions/show-system.xml b/op-mode-definitions/show-system.xml index 1b98b559b..0623e3b62 100644 --- a/op-mode-definitions/show-system.xml +++ b/op-mode-definitions/show-system.xml @@ -59,7 +59,7 @@ Checks overall system integrity - sudo ${vyos_op_scripts_dir}/system_integrity.py + sudo ${vyos_op_scripts_dir}/show_system_integrity.py diff --git a/src/op_mode/show_system_integrity.py b/src/op_mode/show_system_integrity.py new file mode 100755 index 000000000..c34d41e80 --- /dev/null +++ b/src/op_mode/show_system_integrity.py @@ -0,0 +1,70 @@ +#!/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 . +# +# + +import sys +import os +import re +import json +from datetime import datetime, timedelta + +version_file = r'/usr/share/vyos/version.json' + + +def _get_sys_build_version(): + if not os.path.exists(version_file): + return None + buf = open(version_file, 'r').read() + j = json.loads(buf) + if not 'built_on' in j: + return None + return datetime.strptime(j['built_on'], '%a %d %b %Y %H:%M %Z') + + +def _check_pkgs(build_stamp): + pkg_diffs = { + 'buildtime': str(build_stamp), + 'pkg': {} + } + + pkg_info = os.listdir('/var/lib/dpkg/info/') + for file in pkg_info: + if re.search('\.list$', file): + fts = os.stat('/var/lib/dpkg/info/' + file).st_mtime + dt_str = (datetime.utcfromtimestamp( + fts).strftime('%Y-%m-%d %H:%M:%S')) + fdt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S') + if fdt > build_stamp: + pkg_diffs['pkg'].update( + {str(re.sub('\.list', '', file)): str(fdt)}) + + if len(pkg_diffs['pkg']) != 0: + return pkg_diffs + else: + return None + + +if __name__ == '__main__': + built_date = _get_sys_build_version() + if not built_date: + sys.exit(1) + pkgs = _check_pkgs(built_date) + if pkgs: + print ( + "The following packages don\'t fit the image creation time\nbuild time:\t" + pkgs['buildtime']) + for k, v in pkgs['pkg'].items(): + print ("installed: " + v + '\t' + k) diff --git a/src/op_mode/system_integrity.py b/src/op_mode/system_integrity.py deleted file mode 100755 index c0e3d1095..000000000 --- a/src/op_mode/system_integrity.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/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 . -# -# - -import sys -import os -import re -import itertools -from datetime import datetime, timedelta - -from vyos.util import cmd - -verf = r'/usr/libexec/vyos/op_mode/version.py' - -def get_sys_build_version(): - if not os.path.exists(verf): - return None - - a = cmd('/usr/libexec/vyos/op_mode/version.py') - if re.search('^Built on:.+',a, re.M) == None: - return None - - dt = ( re.sub('Built on: +','', re.search('^Built on:.+',a, re.M).group(0)) ) - return datetime.strptime(dt,'%a %d %b %Y %H:%M %Z') - -def check_pkgs(dt): - pkg_diffs = { - 'buildtime' : str(dt), - 'pkg' : {} - } - - pkg_info = os.listdir('/var/lib/dpkg/info/') - for file in pkg_info: - if re.search('\.list$', file): - fts = os.stat('/var/lib/dpkg/info/' + file).st_mtime - dt_str = (datetime.utcfromtimestamp(fts).strftime('%Y-%m-%d %H:%M:%S')) - fdt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S') - if fdt > dt: - pkg_diffs['pkg'].update( { str(re.sub('\.list','',file)) : str(fdt)}) - - if len(pkg_diffs['pkg']) != 0: - return pkg_diffs - else: - return None - -def main(): - dt = get_sys_build_version() - pkgs = check_pkgs(dt) - if pkgs != None: - print ("The following packages don\'t fit the image creation time\nbuild time:\t" + pkgs['buildtime']) - for k, v in pkgs['pkg'].items(): - print ("installed: " + v + '\t' + k) - -if __name__ == '__main__': - sys.exit( main() ) - -- cgit v1.2.3 From 0831c666891506d26cf6b4730c88c2e900121d6a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 28 Aug 2020 21:14:00 +0200 Subject: nat: T2813: translation address is mandatory if rule is not excluded --- smoketest/scripts/cli/test_nat.py | 19 +++++++++++++++---- src/conf_mode/nat.py | 5 +++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py index 416810e40..b06fa239d 100755 --- a/smoketest/scripts/cli/test_nat.py +++ b/smoketest/scripts/cli/test_nat.py @@ -23,6 +23,8 @@ from vyos.configsession import ConfigSession, ConfigSessionError from vyos.util import cmd base_path = ['nat'] +source_path = base_path + ['source'] + snat_pattern = 'nftables[?rule].rule[?chain].{chain: chain, comment: comment, address: { network: expr[].match.right.prefix.addr | [0], prefix: expr[].match.right.prefix.len | [0]}}' class TestNAT(unittest.TestCase): @@ -39,16 +41,15 @@ class TestNAT(unittest.TestCase): def test_source_nat(self): """ Configure and validate source NAT rule(s) """ - path = base_path + ['source'] network = '192.168.0.0/16' - self.session.set(path + ['rule', '1', 'destination', 'address', network]) - self.session.set(path + ['rule', '1', 'exclude']) + self.session.set(source_path + ['rule', '1', 'destination', 'address', network]) + self.session.set(source_path + ['rule', '1', 'exclude']) # check validate() - outbound-interface must be defined with self.assertRaises(ConfigSessionError): self.session.commit() - self.session.set(path + ['rule', '1', 'outbound-interface', 'any']) + self.session.set(source_path + ['rule', '1', 'outbound-interface', 'any']) self.session.commit() tmp = cmd('sudo nft -j list table nat') @@ -59,5 +60,15 @@ class TestNAT(unittest.TestCase): self.assertEqual(condensed_json['address']['network'], network.split('/')[0]) self.assertEqual(str(condensed_json['address']['prefix']), network.split('/')[1]) + + def test_validation(self): + """ T2813: Ensure translation address is specified """ + self.session.set(source_path + ['rule', '100', 'outbound-interface', 'eth0']) + + # check validate() - translation address not specified + with self.assertRaises(ConfigSessionError): + self.session.commit() + + if __name__ == '__main__': unittest.main() diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index dd34dfd66..97cd36223 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -232,6 +232,8 @@ def verify(nat): addr = rule['translation_address'] if addr != 'masquerade' and not is_addr_assigned(addr): print(f'Warning: IP address {addr} does not exist on the system!') + elif not rule['exclude']: + raise ConfigError(f'{err_msg} translation address not specified') # common rule verification verify_rule(rule, err_msg) @@ -246,6 +248,9 @@ def verify(nat): if not rule['interface_in']: raise ConfigError(f'{err_msg} inbound-interface not specified') + if not rule['translation_address'] and not rule['exclude']: + raise ConfigError(f'{err_msg} translation address not specified') + # common rule verification verify_rule(rule, err_msg) -- cgit v1.2.3 From bab1534e0fc1e6da538414b8b032342eb2598f03 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 30 Aug 2020 16:09:37 +0200 Subject: WireGuard: T2842: switch to binary package from buster-backports --- debian/control | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 65808de86..a628fa9c4 100644 --- a/debian/control +++ b/debian/control @@ -108,7 +108,8 @@ Depends: usbutils, vyos-utils, wide-dhcpv6-client, - wireguard, + wireguard-tools, + wireguard-modules, wireless-regdb, wpasupplicant (>= 0.6.7) Description: VyOS configuration scripts and data -- cgit v1.2.3 From b40c52682a25664f7018ab8b4e5ba6f467f10d17 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 30 Aug 2020 08:22:32 -0500 Subject: config: T2636: get_config_dict() returns a list on multi node by default Unless no_multi_convert is True, a single valued multi node will be returned as a list by get_config_dict(). Modification of Thomas Mangin's version. --- python/vyos/config.py | 61 ++++++++++++++++++++++++++----------------- python/vyos/configdiff.py | 4 +-- python/vyos/xml/__init__.py | 4 +++ python/vyos/xml/definition.py | 17 ++++++++++++ 4 files changed, 60 insertions(+), 26 deletions(-) diff --git a/python/vyos/config.py b/python/vyos/config.py index 884d6d947..de79a3654 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -67,6 +67,7 @@ import re import json from copy import deepcopy +import vyos.xml import vyos.util import vyos.configtree from vyos.configsource import ConfigSource, ConfigSourceSession @@ -193,50 +194,62 @@ class Config(object): """ return self._config_source.show_config(path, default, effective) - def get_cached_dict(self, effective=False): + def get_cached_root_dict(self, effective=False): cached = self._dict_cache.get(effective, {}) if cached: - config_dict = cached + return cached + + if effective: + config = self._running_config else: - config_dict = {} + config = self._session_config - if effective: - if self._running_config: - config_dict = json.loads((self._running_config).to_json()) - else: - if self._session_config: - config_dict = json.loads((self._session_config).to_json()) + if config: + config_dict = json.loads(config.to_json()) + else: + config_dict = {} - self._dict_cache[effective] = config_dict + self._dict_cache[effective] = config_dict return config_dict - def get_config_dict(self, path=[], effective=False, key_mangling=None, get_first_key=False): + def get_config_dict(self, path=[], effective=False, key_mangling=None, + get_first_key=False, no_multi_convert=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 """ - config_dict = self.get_cached_dict(effective) + lpath = self._make_path(path) + root_dict = self.get_cached_root_dict(effective) + conf_dict = vyos.util.get_sub_dict(root_dict, lpath, get_first_key) - config_dict = vyos.util.get_sub_dict(config_dict, self._make_path(path), get_first_key) + if not key_mangling and no_multi_convert: + return deepcopy(conf_dict) - if 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") - else: - config_dict = vyos.util.mangle_dict_keys(config_dict, key_mangling[0], key_mangling[1]) - else: - config_dict = deepcopy(config_dict) + xmlpath = lpath if get_first_key else lpath[:-1] - return config_dict + if not key_mangling: + conf_dict = vyos.xml.multi_to_list(xmlpath, conf_dict) + return conf_dict + + if no_multi_convert is False: + conf_dict = vyos.xml.multi_to_list(xmlpath, conf_dict) + + 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") + + conf_dict = vyos.util.mangle_dict_keys(conf_dict, key_mangling[0], key_mangling[1]) + + return conf_dict def is_multi(self, path): """ diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py index b79893507..0e41fbe27 100644 --- a/python/vyos/configdiff.py +++ b/python/vyos/configdiff.py @@ -82,8 +82,8 @@ class ConfigDiff(object): """ def __init__(self, config, key_mangling=None): self._level = config.get_level() - self._session_config_dict = config.get_cached_dict() - self._effective_config_dict = config.get_cached_dict(effective=True) + 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 # mirrored from Config; allow path arguments relative to level diff --git a/python/vyos/xml/__init__.py b/python/vyos/xml/__init__.py index 0f914fed2..0ef0c85ce 100644 --- a/python/vyos/xml/__init__.py +++ b/python/vyos/xml/__init__.py @@ -51,6 +51,10 @@ def defaults(lpath, flat=False): return load_configuration().defaults(lpath, flat) +def multi_to_list(lpath, conf): + return load_configuration().multi_to_list(lpath, conf) + + if __name__ == '__main__': print(defaults(['service'], flat=True)) print(defaults(['service'], flat=False)) diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py index 098e64f7e..6d6fcb5c7 100644 --- a/python/vyos/xml/definition.py +++ b/python/vyos/xml/definition.py @@ -281,6 +281,23 @@ class XML(dict): return _flatten(lpath, len(lpath), d) + def multi_to_list(self, lpath, conf): + r = {} + for k in conf: + # key mangling could also be done here + # it would prevent two parsing of the config tree + # under = k.replace('-','_') + under = k + fpath = lpath + [k] + if isinstance(conf[k],dict): + r[under] = self.multi_to_list(fpath, conf[k]) + continue + value = conf[k] + if self.is_multi(fpath) and not isinstance(value, list): + value = [value] + r[under] = value + return r + # from functools import lru_cache # @lru_cache(maxsize=100) # XXX: need to use cachetool instead - for later -- cgit v1.2.3 From be49f336550f5b6dabd1b8d068237f541f37148a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 30 Aug 2020 20:08:12 +0200 Subject: nat: T2814: nftables module for NAT has been renamed Depending on the underlaying Kernel version load the corresponding Kernel module. --- src/conf_mode/nat.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 97cd36223..f79f0f42b 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -19,6 +19,8 @@ import json import os from copy import deepcopy +from distutils.version import LooseVersion +from platform import release as kernel_version from sys import exit from netifaces import interfaces @@ -33,7 +35,10 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -k_mod = ['nft_nat', 'nft_chain_nat_ipv4'] +if LooseVersion(kernel_version()) > LooseVersion('5.1'): + k_mod = ['nft_nat', 'nft_chain_nat'] +else: + k_mod = ['nft_nat', 'nft_chain_nat_ipv4'] default_config_data = { 'deleted': False, -- cgit v1.2.3 From 1e632d7d01f6d418754ee62d3b154ee50ec842f2 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 30 Aug 2020 13:05:36 -0500 Subject: https: add simple smoketest to check nginx config integrity --- smoketest/scripts/cli/test_service_https.py | 61 +++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100755 smoketest/scripts/cli/test_service_https.py diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py new file mode 100755 index 000000000..5f073b6d2 --- /dev/null +++ b/smoketest/scripts/cli/test_service_https.py @@ -0,0 +1,61 @@ +#!/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 . + +import os +import unittest + +from vyos.configsession import ConfigSession +from vyos.util import run + +base_path = ['service', 'https'] + +class TestHTTPSService(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + self.session.delete(base_path) + + def tearDown(self): + self.session.delete(base_path) + self.session.commit() + + def test_default(self): + self.session.set(base_path) + self.session.commit() + + ret = run('sudo /usr/sbin/nginx -t') + self.assertEqual(ret, 0) + + def test_server_block(self): + vhost_id = 'example' + address = '0.0.0.0' + port = '8443' + name = 'example.org' + + test_path = base_path + ['virtual-host', vhost_id] + + self.session.set(test_path + ['listen-address', address]) + self.session.set(test_path + ['listen-port', port]) + self.session.set(test_path + ['server-name', name]) + + self.session.commit() + + ret = run('sudo /usr/sbin/nginx -t') + self.assertEqual(ret, 0) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From bd076f694a763991a0b0d3a7bb0fa5d194d56d7c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 30 Aug 2020 21:41:27 +0200 Subject: Debian: T2843: drop wireguard-modules dependency with kernel 5.8 --- debian/control | 1 - 1 file changed, 1 deletion(-) diff --git a/debian/control b/debian/control index a628fa9c4..65f3be228 100644 --- a/debian/control +++ b/debian/control @@ -109,7 +109,6 @@ Depends: vyos-utils, wide-dhcpv6-client, wireguard-tools, - wireguard-modules, wireless-regdb, wpasupplicant (>= 0.6.7) Description: VyOS configuration scripts and data -- cgit v1.2.3 From 88985dad133d5e85aca559dbfce53207a2292e0a Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 28 Aug 2020 15:49:55 -0500 Subject: configd: T2582: add config daemon and supporting files --- data/configd-include.json | 1 + data/vyos-configd-env-set | 2 + data/vyos-configd-env-unset | 2 + debian/control | 1 + debian/rules | 1 + src/services/vyos-configd | 224 +++++++++++++++++++++++++++++++++++++++ src/systemd/vyos-configd.service | 27 +++++ 7 files changed, 258 insertions(+) create mode 100644 data/configd-include.json create mode 100644 data/vyos-configd-env-set create mode 100644 data/vyos-configd-env-unset create mode 100755 src/services/vyos-configd create mode 100644 src/systemd/vyos-configd.service diff --git a/data/configd-include.json b/data/configd-include.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/data/configd-include.json @@ -0,0 +1 @@ +[] diff --git a/data/vyos-configd-env-set b/data/vyos-configd-env-set new file mode 100644 index 000000000..d6d421eba --- /dev/null +++ b/data/vyos-configd-env-set @@ -0,0 +1,2 @@ +# +export vyshim=/usr/sbin/vyshim diff --git a/data/vyos-configd-env-unset b/data/vyos-configd-env-unset new file mode 100644 index 000000000..9616f9858 --- /dev/null +++ b/data/vyos-configd-env-unset @@ -0,0 +1,2 @@ +# +unset vyshim diff --git a/debian/control b/debian/control index 65f3be228..d9663d07b 100644 --- a/debian/control +++ b/debian/control @@ -6,6 +6,7 @@ Build-Depends: debhelper (>= 9), fakeroot, libvyosconfig0 (>= 0.0.7), + libzmq3-dev, python3, python3-coverage, python3-lxml, diff --git a/debian/rules b/debian/rules index 58bafd333..6b982fd8e 100755 --- a/debian/rules +++ b/debian/rules @@ -33,6 +33,7 @@ override_dh_auto_install: 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 diff --git a/src/services/vyos-configd b/src/services/vyos-configd new file mode 100755 index 000000000..75f84d3df --- /dev/null +++ b/src/services/vyos-configd @@ -0,0 +1,224 @@ +#!/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 . +# +# + +import os +import sys +import grp +import re +import json +import logging +import signal +import importlib.util +import zmq + +from vyos.defaults import directories +from vyos.configsource import ConfigSourceString +from vyos.config import Config +from vyos import ConfigError + +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) + +SOCKET_PATH = "ipc:///run/vyos-configd.sock" + +# 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' + +active_string = '' +session_string = '' + +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 run_script(script, config) -> int: + config.set_level([]) + try: + c = script.get_config(config) + script.verify(c) + script.generate(c) + script.apply(c) + except ConfigError as e: + logger.critical(e) + return R_ERROR_COMMIT + except Exception: + return R_ERROR_DAEMON + + return R_SUCCESS + +def initialization(socket): + # Reset config strings: + active_string = '' + session_string = '' + # zmq synchronous for ipc from single client: + active_string = socket.recv().decode() + resp = "active" + socket.send(resp.encode()) + session_string = socket.recv().decode() + resp = "session" + socket.send(resp.encode()) + + configsource = ConfigSourceString(running_config_text=active_string, + session_config_text=session_string) + + config = Config(config_source=configsource) + + return config + +def process_node_data(config, data) -> int: + if not config: + logger.critical(f"Empty config") + return R_ERROR_DAEMON + + script_name = None + + res = re.match(r'^.+\/([^/].+).py(VYOS_TAGNODE_VALUE=.+)?', data) + if res.group(1): + script_name = res.group(1) + if res.group(2): + env = res.group(2).split('=') + os.environ[env[0]] = env[1] + + if not script_name: + logger.critical(f"Missing script_name") + return R_ERROR_DAEMON + + if script_name in exclude_set: + return R_PASS + + result = run_script(conf_mode_scripts[script_name], config) + + return result + +def remove_if_file(f: str): + try: + os.remove(f) + except FileNotFoundError: + pass + except OSError: + raise + +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['SUDO_USER'] = 'vyos' + os.environ['SUDO_GID'] = str(cfg_group.gr_gid) + + def sig_handler(signum, frame): + 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 = process_node_data(config, message["data"]) + response = res.to_bytes(1, byteorder=sys.byteorder) + logger.debug(f"Sending response {res}") + socket.send(response) + else: + logger.critical(f"Unexpected message: {message}") diff --git a/src/systemd/vyos-configd.service b/src/systemd/vyos-configd.service new file mode 100644 index 000000000..274ccc787 --- /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 -- cgit v1.2.3 From 163047aedabc2d43249239b0c0bdf92b8e664131 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 28 Aug 2020 15:50:12 -0500 Subject: configd: T2582: add mkjson for use by shim (https://github.com/Jacajack/mkjson.git) --- src/shim/mkjson/LICENSE | 21 ++++ src/shim/mkjson/makefile | 30 +++++ src/shim/mkjson/mkjson.c | 307 +++++++++++++++++++++++++++++++++++++++++++++++ src/shim/mkjson/mkjson.h | 50 ++++++++ 4 files changed, 408 insertions(+) create mode 100644 src/shim/mkjson/LICENSE create mode 100644 src/shim/mkjson/makefile create mode 100644 src/shim/mkjson/mkjson.c create mode 100644 src/shim/mkjson/mkjson.h diff --git a/src/shim/mkjson/LICENSE b/src/shim/mkjson/LICENSE new file mode 100644 index 000000000..8c4284c91 --- /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 000000000..ba75399d2 --- /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 000000000..1172664fb --- /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 +#include +#include +#include +#include + +// 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 000000000..38cc07b26 --- /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 -- cgit v1.2.3 From d469e2f56e299a9d767d7db1f00646741f7ce367 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 28 Aug 2020 15:50:19 -0500 Subject: configd: T2582: add shim as config daemon client --- Makefile | 10 +- debian/vyos-1x.install | 1 + src/shim/Makefile | 20 ++++ src/shim/vyshim.c | 287 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 src/shim/Makefile create mode 100644 src/shim/vyshim.c diff --git a/Makefile b/Makefile index 5b7e4da63..e85835eec 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,9 @@ TMPL_DIR := templates-cfg OP_TMPL_DIR := templates-op BUILD_DIR := build DATA_DIR := data +SHIM_DIR := src/shim +CC := gcc +LIBS := -lzmq CFLAGS := src = $(wildcard interface-definitions/*.xml.in) @@ -114,14 +117,19 @@ op_mode_definitions: component_versions: $(BUILD_DIR) $(obj) $(CURDIR)/scripts/build-component-versions $(BUILD_DIR)/interface-definitions $(DATA_DIR) +.PHONY: vyshim +vyshim: + $(MAKE) -C $(SHIM_DIR) + .PHONY: all -all: clean interface_definitions op_mode_definitions component_versions +all: clean interface_definitions op_mode_definitions component_versions vyshim .PHONY: clean clean: rm -rf $(BUILD_DIR) rm -rf $(TMPL_DIR) rm -rf $(OP_TMPL_DIR) + $(MAKE) -C $(SHIM_DIR) clean .PHONY: test test: diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install index 6cd678442..6d5026e91 100644 --- a/debian/vyos-1x.install +++ b/debian/vyos-1x.install @@ -7,6 +7,7 @@ etc/udev etc/vyos lib/ opt/ +usr/sbin/vyshim usr/bin/initial-setup usr/bin/vyos-config-file-query usr/bin/vyos-config-to-commands diff --git a/src/shim/Makefile b/src/shim/Makefile new file mode 100644 index 000000000..c8487e3c2 --- /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/vyshim.c b/src/shim/vyshim.c new file mode 100644 index 000000000..8b6feab99 --- /dev/null +++ b/src/shim/vyshim.c @@ -0,0 +1,287 @@ +/* + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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" + +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); + +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 init_timeout = 0; + + debug_print("Connecting to vyos-configd ...\n"); + zmq_connect(requester, SOCKET_PATH); + + if (access(COMMIT_MARKER, F_OK) != -1) { + init_timeout = initialization(requester); + if (!init_timeout) remove(COMMIT_MARKER); + } + + int end = argc > 3 ? 2 : argc - 1; + + // if initial communication failed, pass through execution of script + if (init_timeout) { + int ret = pass_through(argv, end); + return ret; + } + + for (int i = end; i > 0 ; i--) { + strncat(&string_node_data[0], argv[i], 127); + } + + char error_code[1]; + debug_print("Sending node data ...\n"); + char *string_node_data_msg = mkjson(MKJSON_OBJ, 2, + MKJSON_STRING, "type", "node", + 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"); + + int err = (int)error_code[0]; + + free(string_node_data_msg); + + zmq_close(requester); + zmq_ctx_destroy(context); + + if (err & PASS) { + debug_print("Received PASS\n"); + int ret = pass_through(argv, end); + return ret; + } + + if (err & ERROR_DAEMON) { + debug_print("Received ERROR_DAEMON\n"); + int ret = pass_through(argv, end); + return ret; + } + + if (err & ERROR_COMMIT) { + debug_print("Received ERROR_COMMIT\n"); + return -1; + } + + return 0; +} + +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; + + 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); + + return 0; +} + +int pass_through(char **argv, int end) +{ + char *newargv[] = { NULL, NULL }; + pid_t child_pid; + + newargv[0] = argv[end]; + if (end > 1) { + putenv(argv[end - 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[end], newargv)) { + debug_print("pass_through execve failed %s: %s\n", + argv[end], 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 -- cgit v1.2.3 From eeeab0197c6e54eda5c2ec59a91f93403618a58b Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 28 Aug 2020 15:50:25 -0500 Subject: configd: T2582: inject shim env variable into configsession ConfigSession needs to have required env vars manully injected; add the shim definition env var to the list, if config daemon is active. --- python/vyos/configsession.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 0994fd974..6e4214360 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -17,6 +17,8 @@ import re import sys import subprocess +from vyos.util import call + CLI_SHELL_API = '/bin/cli-shell-api' SET = '/opt/vyatta/sbin/my_set' DELETE = '/opt/vyatta/sbin/my_delete' @@ -69,6 +71,11 @@ def inject_vyos_env(env): 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 + ret = call('systemctl is-active --quiet vyos-configd.service') + if not ret: + env['vyshim'] = '/usr/sbin/vyshim' + return env -- cgit v1.2.3 From 87d60d5bb08326de8f7d11ba199ee44b6ce34c76 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 28 Aug 2020 15:50:31 -0500 Subject: configd: T2582: add shim var to node.def --- scripts/build-command-templates | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/build-command-templates b/scripts/build-command-templates index 457adbec2..d6585b0cc 100755 --- a/scripts/build-command-templates +++ b/scripts/build-command-templates @@ -225,11 +225,13 @@ def make_node_def(props): 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 \"VYOS_TAGNODE_VALUE='$VAR(@)' {0}\"\n".format(props["owner"]) + node_def += "end: sudo sh -c \"{1} VYOS_TAGNODE_VALUE='$VAR(@)' {0}\"\n".format(props["owner"], shim) else: - node_def += "end: sudo sh -c \"{0}\"\n".format(props["owner"]) + 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) -- cgit v1.2.3 From dba455c24206a33be54fc293d16061bb735af5cc Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 28 Aug 2020 15:50:37 -0500 Subject: configd: T2582: add utility to safely add/remove items from include file --- scripts/update-configd-include-file | 298 ++++++++++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100755 scripts/update-configd-include-file diff --git a/scripts/update-configd-include-file b/scripts/update-configd-include-file new file mode 100755 index 000000000..6615e21ff --- /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.util 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) -- cgit v1.2.3 From 050f16e5c92ebb341913942ebedc6fa0c2c677bf Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 28 Aug 2020 15:50:44 -0500 Subject: configd: T2808: add smoketest to ensure script consistency with daemon --- smoketest/scripts/cli/test_configd_inspect.py | 107 ++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100755 smoketest/scripts/cli/test_configd_inspect.py diff --git a/smoketest/scripts/cli/test_configd_inspect.py b/smoketest/scripts/cli/test_configd_inspect.py new file mode 100755 index 000000000..5181187e9 --- /dev/null +++ b/smoketest/scripts/cli/test_configd_inspect.py @@ -0,0 +1,107 @@ +#!/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 . + +import os +import json +import unittest +import warnings +import importlib.util +from inspect import signature, getsource +from functools import wraps + +from vyos.defaults import directories + +INC_FILE = '/usr/share/vyos/configd-include.json' +CONF_DIR = directories['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 TestConfigdInclude(unittest.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) + if not f: + continue + 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) + n = str_f.count('Config()') + 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) + n = str_m.count('Config()') + 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") + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From ad69fb36201ee0930b76d80f0869284e26846991 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 28 Aug 2020 15:50:50 -0500 Subject: configd: T2582: add scripts to include list for daemon --- data/configd-include.json | 65 ++++++++++++++++++++++++++++- src/conf_mode/bcast_relay.py | 7 +++- src/conf_mode/dhcp_relay.py | 7 +++- src/conf_mode/dhcp_server.py | 7 +++- src/conf_mode/dhcpv6_relay.py | 7 +++- src/conf_mode/dhcpv6_server.py | 7 +++- src/conf_mode/dynamic_dns.py | 7 +++- src/conf_mode/firewall_options.py | 7 +++- src/conf_mode/host_name.py | 7 +++- src/conf_mode/http-api.py | 8 +++- src/conf_mode/https.py | 8 +++- src/conf_mode/igmp_proxy.py | 7 +++- src/conf_mode/intel_qat.py | 7 +++- src/conf_mode/interfaces-bonding.py | 7 +++- src/conf_mode/interfaces-bridge.py | 7 +++- src/conf_mode/interfaces-dummy.py | 7 +++- src/conf_mode/interfaces-ethernet.py | 7 +++- src/conf_mode/interfaces-geneve.py | 7 +++- src/conf_mode/interfaces-l2tpv3.py | 7 +++- src/conf_mode/interfaces-loopback.py | 7 +++- src/conf_mode/interfaces-macsec.py | 7 +++- src/conf_mode/interfaces-openvpn.py | 7 +++- src/conf_mode/interfaces-pppoe.py | 7 +++- src/conf_mode/interfaces-pseudo-ethernet.py | 7 +++- src/conf_mode/interfaces-tunnel.py | 8 +++- src/conf_mode/interfaces-vxlan.py | 7 +++- src/conf_mode/interfaces-wireguard.py | 7 +++- src/conf_mode/interfaces-wireless.py | 7 +++- src/conf_mode/interfaces-wirelessmodem.py | 7 +++- src/conf_mode/ipsec-settings.py | 7 +++- src/conf_mode/lldp.py | 7 +++- src/conf_mode/nat.py | 7 +++- src/conf_mode/ntp.py | 7 +++- src/conf_mode/protocols_igmp.py | 7 +++- src/conf_mode/protocols_mpls.py | 7 +++- src/conf_mode/protocols_pim.py | 7 +++- src/conf_mode/protocols_rip.py | 7 +++- src/conf_mode/protocols_static_multicast.py | 7 +++- src/conf_mode/salt-minion.py | 7 +++- src/conf_mode/service_console-server.py | 7 +++- src/conf_mode/service_ids_fastnetmon.py | 7 +++- src/conf_mode/service_ipoe-server.py | 7 +++- src/conf_mode/service_mdns-repeater.py | 7 +++- src/conf_mode/service_pppoe-server.py | 7 +++- src/conf_mode/service_router-advert.py | 7 +++- src/conf_mode/ssh.py | 7 +++- src/conf_mode/system-ip.py | 7 +++- src/conf_mode/system-ipv6.py | 7 +++- src/conf_mode/system-login-banner.py | 7 +++- src/conf_mode/system-login.py | 7 +++- src/conf_mode/system-options.py | 7 +++- src/conf_mode/system-syslog.py | 7 +++- src/conf_mode/system-timezone.py | 7 +++- src/conf_mode/system-wifi-regdom.py | 7 +++- src/conf_mode/system_console.py | 7 +++- src/conf_mode/system_lcd.py | 7 +++- src/conf_mode/task_scheduler.py | 7 +++- src/conf_mode/tftp_server.py | 7 +++- src/conf_mode/vpn_l2tp.py | 7 +++- src/conf_mode/vpn_pptp.py | 7 +++- src/conf_mode/vpn_sstp.py | 7 +++- src/conf_mode/vrf.py | 7 +++- src/conf_mode/vrrp.py | 7 +++- src/conf_mode/vyos_cert.py | 7 +++- 64 files changed, 382 insertions(+), 127 deletions(-) diff --git a/data/configd-include.json b/data/configd-include.json index fe51488c7..11d550f59 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -1 +1,64 @@ -[] +[ +"bcast_relay.py", +"dhcp_relay.py", +"dhcp_server.py", +"dhcpv6_relay.py", +"dhcpv6_server.py", +"dynamic_dns.py", +"firewall_options.py", +"host_name.py", +"http-api.py", +"https.py", +"igmp_proxy.py", +"intel_qat.py", +"interfaces-bonding.py", +"interfaces-bridge.py", +"interfaces-dummy.py", +"interfaces-ethernet.py", +"interfaces-geneve.py", +"interfaces-l2tpv3.py", +"interfaces-loopback.py", +"interfaces-macsec.py", +"interfaces-openvpn.py", +"interfaces-pppoe.py", +"interfaces-pseudo-ethernet.py", +"interfaces-tunnel.py", +"interfaces-vxlan.py", +"interfaces-wireguard.py", +"interfaces-wireless.py", +"interfaces-wirelessmodem.py", +"ipsec-settings.py", +"lldp.py", +"nat.py", +"ntp.py", +"protocols_igmp.py", +"protocols_mpls.py", +"protocols_pim.py", +"protocols_rip.py", +"protocols_static_multicast.py", +"salt-minion.py", +"service_console-server.py", +"service_ids_fastnetmon.py", +"service_ipoe-server.py", +"service_mdns-repeater.py", +"service_pppoe-server.py", +"service_router-advert.py", +"ssh.py", +"system-ip.py", +"system-ipv6.py", +"system-login-banner.py", +"system-options.py", +"system-syslog.py", +"system-timezone.py", +"system-wifi-regdom.py", +"system_console.py", +"system_lcd.py", +"task_scheduler.py", +"tftp_server.py", +"vpn_l2tp.py", +"vpn_pptp.py", +"vpn_sstp.py", +"vrf.py", +"vrrp.py", +"vyos_cert.py" +] \ No newline at end of file diff --git a/src/conf_mode/bcast_relay.py b/src/conf_mode/bcast_relay.py index a3e141a00..4a47b9246 100755 --- a/src/conf_mode/bcast_relay.py +++ b/src/conf_mode/bcast_relay.py @@ -29,8 +29,11 @@ airbag.enable() config_file_base = r'/etc/default/udp-broadcast-relay' -def get_config(): - conf = Config() +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) diff --git a/src/conf_mode/dhcp_relay.py b/src/conf_mode/dhcp_relay.py index f093a005e..352865b9d 100755 --- a/src/conf_mode/dhcp_relay.py +++ b/src/conf_mode/dhcp_relay.py @@ -36,9 +36,12 @@ default_config_data = { 'relay_agent_packets': 'forward' } -def get_config(): +def get_config(config=None): relay = default_config_data - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists(['service', 'dhcp-relay']): return None else: diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index 0eaa14c5b..fd4e2ec61 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -126,9 +126,12 @@ def dhcp_static_route(static_subnet, static_router): return string -def get_config(): +def get_config(config=None): dhcp = default_config_data - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists('service dhcp-server'): return None else: diff --git a/src/conf_mode/dhcpv6_relay.py b/src/conf_mode/dhcpv6_relay.py index 6ef290bf0..d4212b8be 100755 --- a/src/conf_mode/dhcpv6_relay.py +++ b/src/conf_mode/dhcpv6_relay.py @@ -35,9 +35,12 @@ default_config_data = { 'options': [], } -def get_config(): +def get_config(config=None): relay = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists('service dhcpv6-relay'): return None else: diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index 53c8358a5..4ce4cada1 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -37,9 +37,12 @@ default_config_data = { 'shared_network': [] } -def get_config(): +def get_config(config=None): dhcpv6 = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['service', 'dhcpv6-server'] if not conf.exists(base): return None diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py index 5b1883c03..57c910a68 100755 --- a/src/conf_mode/dynamic_dns.py +++ b/src/conf_mode/dynamic_dns.py @@ -50,9 +50,12 @@ default_config_data = { 'deleted': False } -def get_config(): +def get_config(config=None): dyndns = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() base_level = ['service', 'dns', 'dynamic'] if not conf.exists(base_level): diff --git a/src/conf_mode/firewall_options.py b/src/conf_mode/firewall_options.py index 71b2a98b3..67bf5d0e2 100755 --- a/src/conf_mode/firewall_options.py +++ b/src/conf_mode/firewall_options.py @@ -32,9 +32,12 @@ default_config_data = { 'new_chain6': False } -def get_config(): +def get_config(config=None): opts = copy.deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists('firewall options'): # bail out early return opts diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 9d66bd434..f4c75c257 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -43,8 +43,11 @@ default_config_data = { hostsd_tag = 'system' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() hosts = copy.deepcopy(default_config_data) diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py index b8a084a40..472eb77e4 100755 --- a/src/conf_mode/http-api.py +++ b/src/conf_mode/http-api.py @@ -39,7 +39,7 @@ dependencies = [ 'https.py', ] -def get_config(): +def get_config(config=None): http_api = deepcopy(vyos.defaults.api_data) x = http_api.get('api_keys') if x is None: @@ -48,7 +48,11 @@ def get_config(): default_key = x[0] keys_added = False - conf = Config() + if config: + conf = config + else: + conf = Config() + if not conf.exists('service https api'): return None else: diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index a13f131ab..dc51cb117 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -47,8 +47,12 @@ default_server_block = { 'certbot' : False } -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + if not conf.exists('service https'): return None diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py index 49aea9b7f..754f46566 100755 --- a/src/conf_mode/igmp_proxy.py +++ b/src/conf_mode/igmp_proxy.py @@ -36,9 +36,12 @@ default_config_data = { 'interfaces': [], } -def get_config(): +def get_config(config=None): igmp_proxy = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['protocols', 'igmp-proxy'] if not conf.exists(base): return None diff --git a/src/conf_mode/intel_qat.py b/src/conf_mode/intel_qat.py index 742f09a54..1e5101a9f 100755 --- a/src/conf_mode/intel_qat.py +++ b/src/conf_mode/intel_qat.py @@ -30,8 +30,11 @@ airbag.enable() # Define for recovering gl_ipsec_conf = None -def get_config(): - c = Config() +def get_config(config=None): + if config: + c = config + else: + c = Config() config_data = { 'qat_conf' : None, 'ipsec_conf' : None, diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 3b238f1ea..16e6e4f6e 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -53,12 +53,15 @@ def get_bond_mode(mode): else: raise ConfigError(f'invalid bond mode "{mode}"') -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'bonding'] bond = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index ee8e85e73..47c8c05f9 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -34,12 +34,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'bridge'] bridge = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 8df86c8ea..44fc9cb9e 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -28,12 +28,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'dummy'] dummy = get_interface_dict(conf, base) return dummy diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 10758e35a..a8df64cce 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -30,12 +30,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'ethernet'] ethernet = get_interface_dict(conf, base) return ethernet diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 1104bd3c0..cc2cf025a 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -30,12 +30,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'geneve'] geneve = get_interface_dict(conf, base) return geneve diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 0978df5b6..8250a3df8 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -35,12 +35,15 @@ airbag.enable() k_mod = ['l2tp_eth', 'l2tp_netlink', 'l2tp_ip', 'l2tp_ip6'] -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'l2tpv3'] l2tpv3 = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-loopback.py b/src/conf_mode/interfaces-loopback.py index 0398cd591..30a27abb4 100755 --- a/src/conf_mode/interfaces-loopback.py +++ b/src/conf_mode/interfaces-loopback.py @@ -25,12 +25,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'loopback'] loopback = get_interface_dict(conf, base) return loopback diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index ca15212d4..2866ccc0a 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -35,12 +35,15 @@ airbag.enable() # XXX: wpa_supplicant works on the source interface wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf' -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'macsec'] macsec = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 1420b4116..958b305dd 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -192,9 +192,12 @@ def getDefaultServer(network, topology, devtype): return server -def get_config(): +def get_config(config=None): openvpn = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() # determine tagNode instance if 'VYOS_TAGNODE_VALUE' not in os.environ: diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 901ea769c..1b4b9e4ee 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -30,12 +30,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'pppoe'] pppoe = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index fe2d7b1be..59edca1cc 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -34,12 +34,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'pseudo-ethernet'] peth = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index ea15a7fb7..11d8d6edc 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -397,12 +397,16 @@ def ip_proto (afi): return 6 if afi == IP6 else 4 -def get_config(): +def get_config(config=None): ifname = os.environ.get('VYOS_TAGNODE_VALUE','') if not ifname: raise ConfigError('Interface not specified') - config = Config() + if config: + config = config + else: + config = Config() + conf = ConfigurationState(config, ['interfaces', 'tunnel ', ifname], default_config_data) options = conf.options changes = conf.changes diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 47c0bdcb8..bea3aa25b 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -30,12 +30,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'vxlan'] vxlan = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 8b64cde4d..e7c22da1a 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -33,12 +33,15 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'wireguard'] wireguard = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index b6f247952..9861f72db 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -64,12 +64,15 @@ def find_other_stations(conf, base, ifname): conf.set_level(old_level) return dict -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'wireless'] wifi = get_interface_dict(conf, base) diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index 6d168d918..7d8110096 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -31,12 +31,15 @@ airbag.enable() k_mod = ['option', 'usb_wwan', 'usbserial'] -def get_config(): +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 """ - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['interfaces', 'wirelessmodem'] wwan = get_interface_dict(conf, base) return wwan diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py index 015d1a480..11a5b7aaa 100755 --- a/src/conf_mode/ipsec-settings.py +++ b/src/conf_mode/ipsec-settings.py @@ -41,8 +41,11 @@ delim_ipsec_l2tp_begin = "### VyOS L2TP VPN Begin ###" delim_ipsec_l2tp_end = "### VyOS L2TP VPN End ###" charon_pidfile = "/var/run/charon.pid" -def get_config(): - config = Config() +def get_config(config=None): + if config: + config = config + else: + config = Config() data = {"install_routes": "yes"} if config.exists("vpn ipsec options disable-route-autoinstall"): diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py index 1b539887a..6b645857a 100755 --- a/src/conf_mode/lldp.py +++ b/src/conf_mode/lldp.py @@ -146,9 +146,12 @@ def get_location(config): return intfs_location -def get_config(): +def get_config(config=None): lldp = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists(base): return None else: diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index f79f0f42b..eb634fd78 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -167,9 +167,12 @@ def parse_configuration(conf, source_dest): return ruleset -def get_config(): +def get_config(config=None): nat = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() # read in current nftable (once) for further processing tmp = cmd('nft -j list table raw') diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index bba8f87a4..d6453ec83 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -27,8 +27,11 @@ airbag.enable() config_file = r'/etc/ntp.conf' systemd_override = r'/etc/systemd/system/ntp.service.d/override.conf' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['system', 'ntp'] ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py index ca148fd6a..6f4fc784d 100755 --- a/src/conf_mode/protocols_igmp.py +++ b/src/conf_mode/protocols_igmp.py @@ -29,8 +29,11 @@ airbag.enable() config_file = r'/tmp/igmp.frr' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() igmp_conf = { 'igmp_conf' : False, 'old_ifaces' : {}, diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index bcb16fa04..e515490d0 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -29,8 +29,11 @@ config_file = r'/tmp/ldpd.frr' def sysctl(name, value): call('sysctl -wq {}={}'.format(name, value)) -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() mpls_conf = { 'router_id' : None, 'mpls_ldp' : False, diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 8aa324bac..6d333e19a 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -29,8 +29,11 @@ airbag.enable() config_file = r'/tmp/pimd.frr' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() pim_conf = { 'pim_conf' : False, 'old_pim' : { diff --git a/src/conf_mode/protocols_rip.py b/src/conf_mode/protocols_rip.py index 95e8ce901..8ddd705f2 100755 --- a/src/conf_mode/protocols_rip.py +++ b/src/conf_mode/protocols_rip.py @@ -28,8 +28,11 @@ airbag.enable() config_file = r'/tmp/ripd.frr' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['protocols', 'rip'] rip_conf = { 'rip_conf' : False, diff --git a/src/conf_mode/protocols_static_multicast.py b/src/conf_mode/protocols_static_multicast.py index 232d1e181..99157835a 100755 --- a/src/conf_mode/protocols_static_multicast.py +++ b/src/conf_mode/protocols_static_multicast.py @@ -30,8 +30,11 @@ airbag.enable() config_file = r'/tmp/static_mcast.frr' # Get configuration for static multicast route -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() mroute = { 'old_mroute' : {}, 'mroute' : {} diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py index 3343d1247..841bf6a39 100755 --- a/src/conf_mode/salt-minion.py +++ b/src/conf_mode/salt-minion.py @@ -44,9 +44,12 @@ default_config_data = { 'master_key': '' } -def get_config(): +def get_config(config=None): salt = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['service', 'salt-minion'] if not conf.exists(base): diff --git a/src/conf_mode/service_console-server.py b/src/conf_mode/service_console-server.py index 613ec6879..0e5fc75b0 100755 --- a/src/conf_mode/service_console-server.py +++ b/src/conf_mode/service_console-server.py @@ -27,8 +27,11 @@ from vyos import ConfigError config_file = r'/run/conserver/conserver.cf' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['service', 'console-server'] # Retrieve CLI representation as dictionary diff --git a/src/conf_mode/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py index d46f9578e..27d0ee60c 100755 --- a/src/conf_mode/service_ids_fastnetmon.py +++ b/src/conf_mode/service_ids_fastnetmon.py @@ -28,8 +28,11 @@ airbag.enable() config_file = r'/etc/fastnetmon.conf' networks_list = r'/etc/networks_list' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['service', 'ids', 'ddos-protection'] fastnetmon = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) return fastnetmon diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index 553cc2e97..96cf932d1 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -55,8 +55,11 @@ default_config_data = { 'thread_cnt': get_half_cpus() } -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base_path = ['service', 'ipoe-server'] if not conf.exists(base_path): return None diff --git a/src/conf_mode/service_mdns-repeater.py b/src/conf_mode/service_mdns-repeater.py index 1a6b2c328..729518c96 100755 --- a/src/conf_mode/service_mdns-repeater.py +++ b/src/conf_mode/service_mdns-repeater.py @@ -28,8 +28,11 @@ airbag.enable() config_file = r'/etc/default/mdns-repeater' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['service', 'mdns', 'repeater'] mdns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) return mdns diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 39d34a7e2..45d3806d5 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -85,8 +85,11 @@ default_config_data = { 'thread_cnt': get_half_cpus() } -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base_path = ['service', 'pppoe-server'] if not conf.exists(base_path): return None diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index 4e1c432ab..687d7068f 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -29,8 +29,11 @@ airbag.enable() config_file = r'/run/radvd/radvd.conf' -def get_config(): - conf = Config() +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) diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index 7b262565a..a19fa72d8 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -31,8 +31,11 @@ airbag.enable() config_file = r'/run/ssh/sshd_config' systemd_override = r'/etc/systemd/system/ssh.service.d/override.conf' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['service', 'ssh'] if not conf.exists(base): return None diff --git a/src/conf_mode/system-ip.py b/src/conf_mode/system-ip.py index 85f1e3771..64c9e6d05 100755 --- a/src/conf_mode/system-ip.py +++ b/src/conf_mode/system-ip.py @@ -35,9 +35,12 @@ default_config_data = { def sysctl(name, value): call('sysctl -wq {}={}'.format(name, value)) -def get_config(): +def get_config(config=None): ip_opt = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() conf.set_level('system ip') if conf.exists(''): if conf.exists('arp table-size'): diff --git a/src/conf_mode/system-ipv6.py b/src/conf_mode/system-ipv6.py index 3417c609d..f70ec2631 100755 --- a/src/conf_mode/system-ipv6.py +++ b/src/conf_mode/system-ipv6.py @@ -41,9 +41,12 @@ default_config_data = { def sysctl(name, value): call('sysctl -wq {}={}'.format(name, value)) -def get_config(): +def get_config(config=None): ip_opt = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() conf.set_level('system ipv6') if conf.exists(''): ip_opt['disable_addr_assignment'] = conf.exists('disable') diff --git a/src/conf_mode/system-login-banner.py b/src/conf_mode/system-login-banner.py index 5c0adc921..569010735 100755 --- a/src/conf_mode/system-login-banner.py +++ b/src/conf_mode/system-login-banner.py @@ -41,9 +41,12 @@ default_config_data = { 'motd': motd } -def get_config(): +def get_config(config=None): banner = default_config_data - conf = Config() + if config: + conf = config + else: + conf = Config() base_level = ['system', 'login', 'banner'] if not conf.exists(base_level): diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index b1dd583b5..2aca199f9 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -56,9 +56,12 @@ def get_local_users(): return local_users -def get_config(): +def get_config(config=None): login = default_config_data - conf = Config() + if config: + conf = config + else: + conf = Config() base_level = ['system', 'login'] # We do not need to check if the nodes exist or not and bail out early diff --git a/src/conf_mode/system-options.py b/src/conf_mode/system-options.py index 0aacd19d8..6ac35a4ab 100755 --- a/src/conf_mode/system-options.py +++ b/src/conf_mode/system-options.py @@ -31,8 +31,11 @@ curlrc_config = r'/etc/curlrc' ssh_config = r'/etc/ssh/ssh_config' systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['system', 'options'] options = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) return options diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py index cfc1ca55f..d29109c41 100755 --- a/src/conf_mode/system-syslog.py +++ b/src/conf_mode/system-syslog.py @@ -27,8 +27,11 @@ from vyos.template import render from vyos import airbag airbag.enable() -def get_config(): - c = Config() +def get_config(config=None): + if config: + c = config + else: + c = Config() if not c.exists('system syslog'): return None c.set_level('system syslog') diff --git a/src/conf_mode/system-timezone.py b/src/conf_mode/system-timezone.py index 0f4513122..4d9f017a6 100755 --- a/src/conf_mode/system-timezone.py +++ b/src/conf_mode/system-timezone.py @@ -29,9 +29,12 @@ default_config_data = { 'name': 'UTC' } -def get_config(): +def get_config(config=None): tz = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() if conf.exists('system time-zone'): tz['name'] = conf.return_value('system time-zone') diff --git a/src/conf_mode/system-wifi-regdom.py b/src/conf_mode/system-wifi-regdom.py index 30ea89098..874f93923 100755 --- a/src/conf_mode/system-wifi-regdom.py +++ b/src/conf_mode/system-wifi-regdom.py @@ -34,9 +34,12 @@ default_config_data = { 'deleted' : False } -def get_config(): +def get_config(config=None): regdom = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['system', 'wifi-regulatory-domain'] # Check if interface has been removed diff --git a/src/conf_mode/system_console.py b/src/conf_mode/system_console.py index 6f83335f3..b17818797 100755 --- a/src/conf_mode/system_console.py +++ b/src/conf_mode/system_console.py @@ -26,8 +26,11 @@ airbag.enable() by_bus_dir = '/dev/serial/by-bus' -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['system', 'console'] # retrieve configuration at once diff --git a/src/conf_mode/system_lcd.py b/src/conf_mode/system_lcd.py index 31a09252d..a540d1b9e 100755 --- a/src/conf_mode/system_lcd.py +++ b/src/conf_mode/system_lcd.py @@ -29,8 +29,11 @@ airbag.enable() lcdd_conf = '/run/LCDd/LCDd.conf' lcdproc_conf = '/run/lcdproc/lcdproc.conf' -def get_config(): - conf = Config() +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) diff --git a/src/conf_mode/task_scheduler.py b/src/conf_mode/task_scheduler.py index 51d8684cb..129be5d3c 100755 --- a/src/conf_mode/task_scheduler.py +++ b/src/conf_mode/task_scheduler.py @@ -53,8 +53,11 @@ def make_command(executable, arguments): else: return("sg vyattacfg \"{0}\"".format(executable)) -def get_config(): - conf = Config() +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 = [] diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index d31851bef..ad5ee9c33 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -40,9 +40,12 @@ default_config_data = { 'listen': [] } -def get_config(): +def get_config(config=None): tftpd = deepcopy(default_config_data) - conf = Config() + if config: + conf = config + else: + conf = Config() base = ['service', 'tftp-server'] if not conf.exists(base): return None diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index 26ad1af84..13831dcd8 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -70,8 +70,11 @@ default_config_data = { 'thread_cnt': get_half_cpus() } -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base_path = ['vpn', 'l2tp', 'remote-access'] if not conf.exists(base_path): return None diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py index 32cbadd74..9f3b40534 100755 --- a/src/conf_mode/vpn_pptp.py +++ b/src/conf_mode/vpn_pptp.py @@ -56,8 +56,11 @@ default_pptp = { 'thread_cnt': get_half_cpus() } -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base_path = ['vpn', 'pptp', 'remote-access'] if not conf.exists(base_path): return None diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index ddb499bf4..7fc370f99 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -65,10 +65,13 @@ default_config_data = { 'thread_cnt' : get_half_cpus() } -def get_config(): +def get_config(config=None): sstp = deepcopy(default_config_data) base_path = ['vpn', 'sstp'] - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists(base_path): return None diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 56ca813ff..2f4da0240 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -76,8 +76,11 @@ def vrf_routing(c, match): return matched -def get_config(): - conf = Config() +def get_config(config=None): + if config: + conf = config + else: + conf = Config() vrf_config = deepcopy(default_config_data) cfg_base = ['vrf'] diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index 292eb0c78..f1ceb261b 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -32,11 +32,14 @@ from vyos.ifconfig.vrrp import VRRP from vyos import airbag airbag.enable() -def get_config(): +def get_config(config=None): vrrp_groups = [] sync_groups = [] - config = vyos.config.Config() + if config: + config = config + else: + config = vyos.config.Config() # Get the VRRP groups for group_name in config.list_nodes("high-availability vrrp group"): diff --git a/src/conf_mode/vyos_cert.py b/src/conf_mode/vyos_cert.py index fb4644d5a..dc7c64684 100755 --- a/src/conf_mode/vyos_cert.py +++ b/src/conf_mode/vyos_cert.py @@ -103,10 +103,13 @@ def generate_self_signed(cert_data): if san_config: san_config.close() -def get_config(): +def get_config(config=None): vyos_cert = vyos.defaults.vyos_cert_data - conf = Config() + if config: + conf = config + else: + conf = Config() if not conf.exists('service https certificates system-generated-certificate'): return None else: -- cgit v1.2.3 From 9c63731d6683f59ea784c08852ed38e3ac22794b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 31 Aug 2020 19:57:06 +0200 Subject: T2636: remove workarounds for get_config_dict() Now that b40c52682a256 ("config: T2636: get_config_dict() returns a list on multi node by default") is implemented the workarounds can be removed. --- data/templates/ntp/ntp.conf.tmpl | 16 ++++------------ data/templates/ssh/sshd_config.tmpl | 16 ++++------------ data/templates/wifi/hostapd.conf.tmpl | 12 ------------ python/vyos/configdict.py | 3 --- python/vyos/ifconfig/interface.py | 7 ------- python/vyos/ifconfig/loopback.py | 5 ----- src/conf_mode/https.py | 9 --------- 7 files changed, 8 insertions(+), 60 deletions(-) diff --git a/data/templates/ntp/ntp.conf.tmpl b/data/templates/ntp/ntp.conf.tmpl index 6ef0c0f2c..df8157a41 100644 --- a/data/templates/ntp/ntp.conf.tmpl +++ b/data/templates/ntp/ntp.conf.tmpl @@ -25,23 +25,15 @@ server {{ srv | replace('_', '-') }} iburst {{ options }} {% if allow_clients is defined and allow_clients.address is defined %} # Allowed clients configuration -{% if allow_clients.address is string %} -restrict {{ allow_clients.address|address_from_cidr }} mask {{ allow_clients.address|netmask_from_cidr }} nomodify notrap nopeer -{% else %} -{% for address in allow_clients.address %} +{% for address in allow_clients.address %} restrict {{ address|address_from_cidr }} mask {{ address|netmask_from_cidr }} nomodify notrap nopeer -{% endfor %} -{% endif %} +{% endfor %} {% endif %} {% if listen_address %} # NTP should listen on configured addresses only interface ignore wildcard -{% if listen_address is string %} -interface listen {{ listen_address }} -{% else %} -{% for address in listen_address %} +{% for address in listen_address %} interface listen {{ address }} -{% endfor %} -{% endif %} +{% endfor %} {% endif %} diff --git a/data/templates/ssh/sshd_config.tmpl b/data/templates/ssh/sshd_config.tmpl index 4fde24255..52d537aca 100644 --- a/data/templates/ssh/sshd_config.tmpl +++ b/data/templates/ssh/sshd_config.tmpl @@ -37,13 +37,9 @@ PermitRootLogin no UseDNS {{ "no" if disable_host_validation is defined else "yes" }} # Specifies the port number that sshd(8) listens on -{% if port is string %} -Port {{ port }} -{% else %} -{% for value in port %} +{% for value in port %} Port {{ value }} -{% endfor %} -{% endif %} +{% endfor %} # Gives the verbosity level that is used when logging messages from sshd LogLevel {{ loglevel | upper }} @@ -53,13 +49,9 @@ PasswordAuthentication {{ "no" if disable_password_authentication is defined els {% if listen_address %} # Specifies the local addresses sshd should listen on -{% if listen_address is string %} -ListenAddress {{ listen_address }} -{% else %} -{% for address in listen_address %} +{% for address in listen_address %} ListenAddress {{ address }} -{% endfor %} -{% endif %} +{% endfor %} {% endif %} {% if ciphers %} diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl index 765668c57..a7efee6d5 100644 --- a/data/templates/wifi/hostapd.conf.tmpl +++ b/data/templates/wifi/hostapd.conf.tmpl @@ -500,18 +500,10 @@ wpa=1 {% if security.wpa.mode is defined and security.wpa.mode == 'wpa2' %} # Pairwise cipher for RSN/WPA2 (default: use wpa_pairwise value) -{% if security.wpa.cipher is string %} -rsn_pairwise={{ security.wpa.cipher }} -{% else %} rsn_pairwise={{ security.wpa.cipher | join(" ") }} -{% endif %} {% else %} # Pairwise cipher for WPA (v1) (default: TKIP) -{% if security.wpa.cipher is string %} -wpa_pairwise={{ security.wpa.cipher }} -{% else %} wpa_pairwise={{ security.wpa.cipher | join(" ") }} -{% endif %} {% endif %} {% endif %} @@ -522,11 +514,7 @@ wpa_pairwise={{ security.wpa.cipher | join(" ") }} # overriding the group cipher with an unexpected value can result in # interoperability issues and in general, this parameter is mainly used for # testing purposes. -{% if security.wpa.group_cipher is string %} -group_cipher={{ security.wpa.group_cipher }} -{% else %} group_cipher={{ security.wpa.group_cipher | join(" ") }} -{% endif %} {% endif %} {% if security.wpa.passphrase is defined %} diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index bd8624ced..e8c0aa5b3 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -270,9 +270,6 @@ def get_interface_dict(config, base, ifname=''): eui64 = leaf_node_changed(config, ['ipv6', 'address', 'eui64']) if eui64: - # XXX: T2636 workaround: convert string to a list with one element - if isinstance(eui64, str): - eui64 = [eui64] tmp = vyos_dict_search('ipv6.address', dict) if not tmp: dict.update({'ipv6': {'address': {'eui64_old': eui64}}}) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 67ba973c4..ef2336c17 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -898,10 +898,6 @@ class Interface(Control): # configured addresses will be removed first new_addr = config.get('address', []) - # XXX: T2636 workaround: convert string to a list with one element - if isinstance(new_addr, str): - new_addr = [new_addr] - # always ensure DHCP client is stopped (when not configured explicitly) if 'dhcp' not in new_addr: self.del_addr('dhcp') @@ -1023,9 +1019,6 @@ class Interface(Control): # Add IPv6 EUI-based addresses tmp = vyos_dict_search('ipv6.address.eui64', config) if tmp: - # XXX: T2636 workaround: convert string to a list with one element - if isinstance(tmp, str): - tmp = [tmp] for addr in tmp: self.add_ipv6_eui64_address(addr) diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py index 2b4ebfdcc..c70e1773f 100644 --- a/python/vyos/ifconfig/loopback.py +++ b/python/vyos/ifconfig/loopback.py @@ -64,11 +64,6 @@ class LoopbackIf(Interface): on any interface. """ addr = config.get('address', []) - # XXX workaround for T2636, convert IP address string to a list - # with one element - if isinstance(addr, str): - addr = [addr] - # We must ensure that the loopback addresses are never deleted from the system addr += self._persistent_addresses diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index dc51cb117..de228f0f8 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -74,9 +74,6 @@ def get_config(config=None): server_block['address'] = data.get('listen-address', '*') server_block['port'] = data.get('listen-port', '443') name = data.get('server-name', ['_']) - # XXX: T2636 workaround: convert string to a list with one element - if not isinstance(name, list): - name = [name] server_block['name'] = name server_block_list.append(server_block) @@ -98,9 +95,6 @@ def get_config(config=None): certbot = False cert_domains = cert_dict.get('certbot', {}).get('domain-name', []) if cert_domains: - # XXX: T2636 workaround: convert string to a list with one element - if not isinstance(cert_domains, list): - cert_domains = [cert_domains] certbot = True for domain in cert_domains: sub_list = vyos.certbot_util.choose_server_block(server_block_list, @@ -125,9 +119,6 @@ def get_config(config=None): if port: api_data['port'] = port vhosts = https_dict.get('api-restrict', {}).get('virtual-host', []) - # XXX: T2636 workaround: convert string to a list with one element - if not isinstance(vhosts, list): - vhosts = [vhosts] if vhosts: api_data['vhost'] = vhosts[:] -- cgit v1.2.3 From 2503f720e3c61ba98fdc2fd9296afe79fd5518b8 Mon Sep 17 00:00:00 2001 From: sever-sever Date: Tue, 1 Sep 2020 06:42:36 +0000 Subject: op-mode: T2846: Fix show ip route longer-prefixes --- op-mode-definitions/show-ip-route.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-mode-definitions/show-ip-route.xml b/op-mode-definitions/show-ip-route.xml index d12d132c0..48ebbc74a 100644 --- a/op-mode-definitions/show-ip-route.xml +++ b/op-mode-definitions/show-ip-route.xml @@ -149,7 +149,7 @@ Show longer prefixes of routes for specified IP address or prefix - /usr/bin/vtysh -c "show ip route $4" + /usr/bin/vtysh -c "show ip route $4 longer-prefixes" -- cgit v1.2.3 From 0952b62baf878a9d4abcfc777beaa19c1cf9e47b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 1 Sep 2020 07:35:07 +0200 Subject: T2636: ssh: add smoketest for XML defaultValue node --- smoketest/scripts/cli/test_service_ssh.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py index 3ee498f3d..1038b8775 100755 --- a/smoketest/scripts/cli/test_service_ssh.py +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -30,6 +30,9 @@ def get_config_value(key): tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) return tmp +def is_service_running(): + return 'sshd' in (p.name() for p in process_iter()) + class TestServiceSSH(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) @@ -46,6 +49,21 @@ class TestServiceSSH(unittest.TestCase): self.session.commit() del self.session + def test_ssh_default(self): + """ Check if SSH service runs with default settings - used for checking + behavior of in XML definition """ + self.session.set(base_path) + + # commit changes + self.session.commit() + + # Check configured port + port = get_config_value('Port')[0] + self.assertEqual('22', port) + + # Check for running process + self.assertTrue(is_service_running()) + def test_ssh_single(self): """ Check if SSH service can be configured and runs """ self.session.set(base_path + ['port', '1234']) @@ -83,7 +101,7 @@ class TestServiceSSH(unittest.TestCase): self.assertTrue("100" in keepalive) # Check for running process - self.assertTrue("sshd" in (p.name() for p in process_iter())) + self.assertTrue(is_service_running()) def test_ssh_multi(self): """ Check if SSH service can be configured and runs with multiple @@ -110,7 +128,7 @@ class TestServiceSSH(unittest.TestCase): self.assertIn(address, tmp) # Check for running process - self.assertTrue("sshd" in (p.name() for p in process_iter())) + self.assertTrue(is_service_running()) if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 570886631deae3de8c38ea4ff70710e7a4858cbd Mon Sep 17 00:00:00 2001 From: sever-sever Date: Tue, 1 Sep 2020 16:07:51 +0000 Subject: frr-template: T2850: Add BGP template for FRR --- data/templates/frr/bgp.frr.tmpl | 966 ++++++++++++++++++++++++++++++++++++++++ src/conf_mode/protocols_bgp.py | 69 ++- 2 files changed, 995 insertions(+), 40 deletions(-) diff --git a/data/templates/frr/bgp.frr.tmpl b/data/templates/frr/bgp.frr.tmpl index cdf4cb4fe..d011a1e85 100644 --- a/data/templates/frr/bgp.frr.tmpl +++ b/data/templates/frr/bgp.frr.tmpl @@ -1 +1,967 @@ +{% set conf_bgp = nbgp -%} +{% for asn in nbgp -%} ! +router bgp {{ asn }} + no bgp default ipv4-unicast + +{#- set 'conf_bgp[asn].parameters' as bgp_params #} +{%- set bgp_params = conf_bgp[asn].parameters %} +{%- set bgp_afi = conf_bgp[asn].address_family %} + +{#- START Global ASN address-family section; set protocol bgp xxx address-family #} +{%- if 'address_family' in conf_bgp[asn] %} +{%- for type in bgp_afi %} +{%- if type == "ipv4_unicast" %} + ! + address-family ipv4 unicast +{# need to check #} +{%- if 'aggregate_address' in bgp_afi[type] %} +{%- for ip in bgp_afi[type].aggregate_address %} +{%- if ( ('as_set' and 'summary_only') in bgp_afi[type].aggregate_address[ip] ) %} + aggregate-address {{ ip }} as-set summary-only +{%- elif 'as_set' in bgp_afi[type].aggregate_address[ip] %} + aggregate-address {{ ip }} as-set +{%- elif 'summary_only' in bgp_afi[type].aggregate_address[ip] %} + aggregate-address {{ ip }} summary-only +{%- else %} + aggregate-address {{ ip }} +{%- endif %} +{%- endfor %} +{%- endif %} +{# END aggregate address#} +{#- redistribute #} +{# need to check. dont work. + 'metric' and 'route_map' match also only 'route_map' + 'table' parameter also include in protocol, its not what I want #} +{%- if 'redistribute' in bgp_afi[type] %} +{%- if 'table' in bgp_afi[type].redistribute %} + redistribute table {{bgp_afi[type].redistribute.table}} +{%- endif %} +{%- for protocol in bgp_afi[type].redistribute %} +{%- if ( ('metric' and 'route_map') in bgp_afi[type].redistribute[protocol] ) %} + redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} route-map {{bgp_afi[type].redistribute[protocol].route_map}} +{%- elif 'metric' in bgp_afi[type].redistribute[protocol] %} + redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} +{%- elif 'route_map' in bgp_afi[type].redistribute[protocol] %} + redistribute {{protocol}} route-map {{bgp_afi[type].redistribute[protocol].route_map}} +{%- else %} + redistribute {{protocol}} +{%- endif %} +{%- endfor %} +{%- endif %} +{#- END redistribute #} + +{%- if 'network' in bgp_afi[type] %} +{%- for net in bgp_afi[type].network %} + network {{ net }} +{%- endfor %} +{%- endif %} + exit-address-family + ! +{%- endif %} + +{%- if type == "ipv6_unicast" %} + ! + address-family ipv6 unicast +{%- if 'aggregate_address' in bgp_afi[type] %} +{%- for ip in bgp_afi[type].aggregate_address %} +{%- if ( ('as_set' and 'summary_only') in bgp_afi[type].aggregate_address[ip] ) %} + aggregate-address {{ ip }} as-set summary-only +{%- elif 'as_set' in bgp_afi[type].aggregate_address[ip] %} + aggregate-address {{ ip }} as-set +{%- elif 'summary_only' in bgp_afi[type].aggregate_address[ip] %} + aggregate-address {{ ip }} summary-only +{%- else %} + aggregate-address {{ ip }} +{%- endif %} +{%- endfor %} +{%- endif %} +{# END aggregate address#} + +{#- redistribute #} +{# need to check. doesn't work. 'metric' and 'route_map' match also only 'route_map' #} +{%- if 'redistribute' in bgp_afi[type] %} +{%- if 'table' in bgp_afi[type].redistribute %} + redistribute table {{bgp_afi[type].redistribute.table}} +{%- endif %} +{%- for protocol in bgp_afi[type].redistribute %} +{%- if ( ('metric' and 'route_map') in bgp_afi[type].redistribute[protocol] ) %} + redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} route-map {{bgp_afi[type].redistribute[protocol].route_map}} +{%- elif 'metric' in bgp_afi[type].redistribute[protocol] %} + redistribute {{protocol}} metric {{bgp_afi[type].redistribute[protocol].metric}} +{%- elif 'route_map' in bgp_afi[type].redistribute[protocol] %} + redistribute {{protocol}} route-map {{bgp_afi[type].redistribute[protocol].route_map}} +{%- else %} + redistribute {{protocol}} +{%- endif %} +{%- endfor %} +{%- endif %} +{#- END redistribute #} + +{%- if 'network' in bgp_afi[type] %} +{%- for net in bgp_afi[type].network %} + network {{ net }} +{%- endfor %} +{%- endif %} + exit-address-family +! +{%- endif %} +{%- endfor %} +{%- endif %} +{#- END Global ASN address-family section; set protocols bgp 65001 address-family #} + +{#- set protocols nbgp xxxx maximum-paths ibgp x, Generated by default for afi_4 #} +{#- We don't have this parameter in afi_6. But this is supported in the FRR #} +{%- if 'maximum_paths' in conf_bgp[asn] %} +{%- if 'ebgp' in conf_bgp[asn].maximum_paths %} + ! + address-family ipv4 unicast + maximum-paths {{ conf_bgp[asn].maximum_paths.ebgp }} + exit-address-family + ! +{%- endif %} +{%- if 'ibgp' in conf_bgp[asn].maximum_paths %} + ! + address-family ipv4 unicast + maximum-paths ibgp {{ conf_bgp[asn].maximum_paths.ibgp }} + exit-address-family + ! +{%- endif %} +{%- endif %} + +{#- START peer-group; set protocol bgp xxx peer-group #} +{%- if 'peer_group' in conf_bgp[asn] %} +{%- for pr_group in conf_bgp[asn].peer_group %} +{%- set conf_peer_group = conf_bgp[asn].peer_group[pr_group] %} + neighbor {{pr_group}} peer-group + +{#- First parameter for peer-group - remote-as #} +{%- if 'remote_as' in conf_peer_group %} + neighbor {{ pr_group }} remote-as {{ conf_peer_group.remote_as }} +{%- endif %} + +{%- if 'bfd' in conf_peer_group %} + neighbor {{ pr_group }} bfd +{%- endif %} + +{%- if 'capability' in conf_peer_group %} +{%- if 'dynamic' in conf_peer_group.capability %} + neighbor {{ pr_group }} capability dynamic +{%- endif %} +{%- if 'extended_nexthop' in conf_peer_group.capability %} + neighbor {{ pr_group }} capability extended-nexthop +{%- endif %} +{%- endif %} + +{%- if 'description' in conf_peer_group %} + neighbor {{ pr_group }} description {{ conf_peer_group.description }} +{%- endif %} + +{%- if 'disable_capability_negotiation' in conf_peer_group %} + neighbor {{ pr_group }} disable-capability-negotiation +{%- endif %} + +{#- https://phabricator.vyos.net/T2844. 'disable-send-community' only for afi #} +{%- if 'disable_send_community' in conf_peer_group %} + ! +{%- endif %} + +{%- if 'ebgp_multihop' in conf_peer_group %} + neighbor {{ pr_group }} ebgp-multihop {{conf_peer_group.ebgp_multihop}} +{%- endif %} + +{%- if 'local_as' in conf_peer_group %} +{%- for loc_asn in conf_peer_group.local_as %} +{%- if 'no_prepend' in conf_peer_group.local_as[loc_asn] %} + neighbor {{ pr_group }} local-as {{loc_asn}} no-prepend +{%- else %} + neighbor {{ pr_group }} local-as {{loc_asn}} +{%- endif %} +{%- endfor %} +{%- endif %} + +{%- if 'override_capability' in conf_peer_group %} + neighbor {{ pr_group }} override-capability +{%- endif %} + +{%- if 'passive' in conf_peer_group %} + neighbor {{ pr_group }} passive +{%- endif %} + +{%- if 'password' in conf_peer_group %} + neighbor {{ pr_group }} password {{ conf_peer_group.password }} +{%- endif %} + +{%- if 'shutdown' in conf_peer_group %} + neighbor {{ pr_group }} shutdown +{%- endif %} + +{%- if 'ttl_security' in conf_peer_group %} +{%- if 'hops' in conf_peer_group.ttl_security %} + neighbor {{ pr_group }} ttl-security hops {{conf_peer_group.ttl_security.hops}} +{%- endif %} +{%- endif %} + +{%- if 'update_source' in conf_peer_group %} + neighbor {{ pr_group }} update-source {{ conf_peer_group.update_source }} +{%- endif %} + +{# START peer-group afi; set protocols bgp xxx peer-group FOO address-family #} +{%- if 'address_family' in conf_peer_group %} +{%- for afi in conf_peer_group.address_family %} +{%- if afi == "ipv4_unicast" %} + ! + address-family ipv4 unicast + +{%- if 'allowas_in' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'number' in conf_peer_group.address_family.ipv4_unicast.allowas_in %} + neighbor {{ pr_group }} allowas-in {{ conf_peer_group.address_family.ipv4_unicast.allowas_in.number }} +{%- else %} + neighbor {{ pr_group }} allowas-in +{%- endif %} +{%- endif %} + +{#- START Single Params for peer-group; set protocols bgp xxx peer-group FOO address-family ipv4-unicast #} + +{%- if 'remove_private_as' in conf_peer_group.address_family.ipv4_unicast %} + neighbor {{ pr_group }} remove-private-AS +{%- endif %} + +{%- if 'route_reflector_client' in conf_peer_group.address_family.ipv4_unicast %} + neighbor {{ pr_group }} route-reflector-client +{%- endif %} + +{%- if 'weight' in conf_peer_group.address_family.ipv4_unicast %} + neighbor {{ pr_group }} weight {{ conf_peer_group.address_family.ipv4_unicast.weight }} +{%- endif %} +{#- END single params for peer-group #} + +{#- Checks need to be done as-path|med|next-hop #} +{%- if 'attribute_unchanged' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'as_path' in conf_peer_group.address_family.ipv4_unicast.attribute_unchanged %} + neighbor {{ pr_group }} attribute-unchanged as-path +{%- else %} + neighbor {{ pr_group }} attribute-unchanged as-path next-hop med +{%- endif %} +{%- endif %} +{#- END attribute-unchanged #} + +{%- if 'capability' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'receive' in conf_peer_group.address_family.ipv4_unicast.capability.orf.prefix_list %} + neighbor {{ pr_group }} capability orf prefix-list receive +{%- endif %} +{%- if 'send' in conf_peer_group.address_family.ipv4_unicast.capability.orf.prefix_list %} + neighbor {{ pr_group }} capability orf prefix-list send +{%- endif %} +{%- endif %} + +{%- if 'default_originate' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'route_map' in conf_peer_group.address_family.ipv4_unicast.default_originate %} + neighbor {{ pr_group }} default-originate route-map {{ conf_peer_group.address_family.ipv4_unicast.default_originate.route_map }} +{%- else %} + neighbor {{ pr_group }} default-originate +{%- endif %} +{%- endif %} + +{%- if 'distribute_list' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv4_unicast.distribute_list %} + neighbor {{ pr_group }} distribute-list {{conf_peer_group.address_family.ipv4_unicast.distribute_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv4_unicast.distribute_list %} + neighbor {{ pr_group }} distribute-list {{conf_peer_group.address_family.ipv4_unicast.distribute_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'filter_list' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv4_unicast.filter_list %} + neighbor {{ pr_group }} filter-list {{conf_peer_group.address_family.ipv4_unicast.filter_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv4_unicast.filter_list %} + neighbor {{ pr_group }} filter-list {{conf_peer_group.address_family.ipv4_unicast.filter_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'maximum_prefix' in conf_peer_group.address_family.ipv4_unicast %} + neighbor {{ pr_group }} maximum-prefix {{ conf_peer_group.address_family.ipv4_unicast.maximum_prefix }} +{%- endif %} + +{#- https://phabricator.vyos.net/T1817 #} +{%- if 'nexthop_self' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'force' in conf_peer_group.address_family.ipv4_unicast.nexthop_self %} + neighbor {{ pr_group }} next-hop-self force + neighbor {{ pr_group }} next-hop-self +{%- else %} + neighbor {{ pr_group }} next-hop-self +{%- endif %} +{%- endif %} + +{%- if 'route_server_client' in conf_peer_group.address_family.ipv4_unicast %} + neighbor {{ pr_group }} route-server-client +{%- endif %} + +{%- if 'route_map' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv4_unicast.route_map %} + neighbor {{ pr_group }} route-map {{conf_peer_group.address_family.ipv4_unicast.route_map.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv4_unicast.route_map %} + neighbor {{ pr_group }} route-map {{conf_peer_group.address_family.ipv4_unicast.route_map.import}} in +{%- endif %} +{%- endif %} +{%- if 'prefix_list' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv4_unicast.prefix_list %} + neighbor {{ pr_group }} prefix-list {{conf_peer_group.address_family.ipv4_unicast.prefix_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv4_unicast.prefix_list %} + neighbor {{ pr_group }} prefix-list {{conf_peer_group.address_family.ipv4_unicast.prefix_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'soft_reconfiguration' in conf_peer_group.address_family.ipv4_unicast %} +{%- if 'inbound' is defined %} + neighbor {{ pr_group }} soft-reconfiguration inbound +{%- endif %} +{%- endif %} + +{#- Need to check. https://phabricator.vyos.net/T2387#73900 #} +{%- if 'unsuppress_map' in conf_peer_group.address_family.ipv4_unicast %} + neighbor {{ pr_group }} unsuppress-map {{conf_peer_group.address_family.ipv4_unicast.unsuppress_map}} +{%- endif %} + neighbor {{ pr_group }} activate + exit-address-family + ! +{%- endif %} + +{%- if afi == "ipv6_unicast" %} + ! + address-family ipv6 unicast + +{%- if 'allowas_in' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'number' in conf_peer_group.address_family.ipv6_unicast.allowas_in %} + neighbor {{ pr_group }} allowas-in {{ conf_peer_group.address_family.ipv6_unicast.allowas_in.number }} +{%- else %} + neighbor {{ pr_group }} allowas-in +{%- endif %} +{%- endif %} + +{#- START Single Params for peer-group afi6; set protocols bgp xxx peer-group FOO address-family ipv6-unicast #} +{%- if 'remove_private_as' in conf_peer_group.address_family.ipv6_unicast %} + neighbor {{ pr_group }} remove-private-AS +{%- endif %} + +{%- if 'route_reflector_client' in conf_peer_group.address_family.ipv6_unicast %} + neighbor {{ pr_group }} route-reflector-client +{%- endif %} + +{%- if 'weight' in conf_peer_group.address_family.ipv6_unicast %} + neighbor {{ pr_group }} weight {{ conf_peer_group.address_family.ipv6_unicast.weight }} +{%- endif %} +{#- END single params for peer-group afi6 #} + +{#- Checks need to be done as-path|med|next-hop #} +{%- if 'attribute_unchanged' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'as_path' in conf_peer_group.address_family.ipv6_unicast.attribute_unchanged %} + neighbor {{ pr_group }} attribute-unchanged as-path +{%- else %} + neighbor {{ pr_group }} attribute-unchanged as-path next-hop med +{%- endif %} +{%- endif %} + +{%- if 'capability' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'receive' in conf_peer_group.address_family.ipv6_unicast.capability.orf.prefix_list %} + neighbor {{ pr_group }} capability orf prefix-list receive +{%- endif %} +{%- if 'send' in conf_peer_group.address_family.ipv6_unicast.capability.orf.prefix_list %} + neighbor {{ pr_group }} capability orf prefix-list send +{%- endif %} +{%- endif %} + +{%- if 'default_originate' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'route_map' in conf_peer_group.address_family.ipv6_unicast.default_originate %} + neighbor {{ pr_group }} default-originate route-map {{ conf_peer_group.address_family.ipv6_unicast.default_originate.route_map }} +{%- else %} + neighbor {{ pr_group }} default-originate +{%- endif %} +{%- endif %} + +{%- if 'distribute_list' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv6_unicast.distribute_list %} + neighbor {{ pr_group }} distribute-list {{conf_peer_group.address_family.ipv6_unicast.distribute_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv6_unicast.distribute_list %} + neighbor {{ pr_group }} distribute-list {{conf_peer_group.address_family.ipv6_unicast.distribute_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'filter_list' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv6_unicast.filter_list %} + neighbor {{ pr_group }} filter-list {{conf_peer_group.address_family.ipv6_unicast.filter_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv6_unicast.filter_list %} + neighbor {{ pr_group }} filter-list {{conf_peer_group.address_family.ipv6_unicast.filter_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'maximum_prefix' in conf_peer_group.address_family.ipv6_unicast %} + neighbor {{ pr_group }} maximum-prefix {{ conf_peer_group.address_family.ipv6_unicast.maximum_prefix }} +{%- endif %} + +{#- https://phabricator.vyos.net/T1817 #} +{%- if 'nexthop_self' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'force' in conf_peer_group.address_family.ipv6_unicast.nexthop_self %} + neighbor {{ pr_group }} next-hop-self force + neighbor {{ pr_group }} next-hop-self +{%- else %} + neighbor {{ pr_group }} next-hop-self +{%- endif %} +{%- endif %} + +{%- if 'route_server_client' in conf_peer_group.address_family.ipv6_unicast %} + neighbor {{ pr_group }} route-server-client +{%- endif %} + +{%- if 'route_map' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv6_unicast.route_map %} + neighbor {{ pr_group }} route-map {{conf_peer_group.address_family.ipv6_unicast.route_map.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv6_unicast.route_map %} + neighbor {{ pr_group }} route-map {{conf_peer_group.address_family.ipv6_unicast.route_map.import}} in +{%- endif %} +{%- endif %} +{%- if 'prefix_list' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer_group.address_family.ipv6_unicast.prefix_list %} + neighbor {{ pr_group }} prefix-list {{conf_peer_group.address_family.ipv6_unicast.prefix_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer_group.address_family.ipv6_unicast.prefix_list %} + neighbor {{ pr_group }} prefix-list {{conf_peer_group.address_family.ipv6_unicast.prefix_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'soft_reconfiguration' in conf_peer_group.address_family.ipv6_unicast %} +{%- if 'inbound' is defined %} + neighbor {{ pr_group }} soft-reconfiguration inbound +{%- endif %} +{%- endif %} + +{#- Checks need to be done. https://phabricator.vyos.net/T2387#73900 #} +{%- if 'unsuppress_map' in conf_peer_group.address_family.ipv6_unicast %} + neighbor {{ pr_group }} unsuppress-map {{conf_peer_group.address_family.ipv6_unicast.unsuppress_map}} +{%- endif %} + neighbor {{ pr_group }} activate + exit-address-family + ! +{%- endif %} + +{%- endfor %} +{%- endif %} +{# END peer-group afi; set protocols bgp xxx peer-group FOO address-family #} + +{%- endfor %} +{%- endif %} +{#- END peer-group; set protocol bgp xxx peer-group #} + +{#- START peer section; set protocol bgp xxx neighbor #} +{%- for peer in conf_bgp[asn].neighbor %} +{#- set peer-group as conf_peer #} +{%- set conf_peer = conf_bgp[asn].neighbor[peer] %} + +{#- First parameter for peer-group - remote-as #} +{%- if 'remote_as' in conf_peer %} + neighbor {{ peer }} remote-as {{ conf_peer.remote_as }} +{%- endif %} + +{%- if 'advertisement_interval' in conf_peer %} + neighbor {{ peer }} advertisement-interval {{ conf_peer.advertisement_interval }} +{%- endif %} + +{%- if 'bfd' in conf_peer %} +{%- if 'check_control_plane_failure' in conf_peer.bfd %} + neighbor {{ peer }} bfd + neighbor {{ peer }} bfd check-control-plane-failure +{%- else %} + neighbor {{ peer }} bfd +{%- endif %} +{%- endif %} + +{%- if 'capability' in conf_peer %} +{%- if 'dynamic' in conf_peer.capability %} + neighbor {{ peer }} capability dynamic +{%- endif %} +{%- if 'extended_nexthop' in conf_peer.capability %} + neighbor {{ peer }} capability extended-nexthop +{%- endif %} +{%- endif %} + +{%- if 'description' in conf_peer %} + neighbor {{ peer }} description {{ conf_peer.description }} +{%- endif %} + +{%- if 'disable_capability_negotiation' in conf_peer %} + neighbor {{ peer }} disable-capability-negotiation +{%- endif %} + +{#- https://phabricator.vyos.net/T2844. 'disable-send-community' only for afi #} +{%- if 'disable_send_community' in conf_peer %} + ! +{%- endif %} + +{%- if 'ebgp_multihop' in conf_peer %} + neighbor {{ peer }} ebgp-multihop {{conf_peer.ebgp_multihop}} +{%- endif %} + +{#- Need to check. 'Peer-group' needs to define before this section #} +{%- if 'interface' in conf_peer %} +{%- if 'peer_group' in conf_peer.interface %} + neighbor {{ peer }} interface peer-group {{conf_peer.interface.peer_group}} +{%- endif %} +{%- if 'remote_as' in conf_peer.interface %} + neighbor {{ peer }} interface remote-as {{conf_peer.interface.remote_as}} +{%- endif %} +{%- if 'v6only' in conf_peer.interface %} +{%- if 'peer_group' in conf_peer.interface.v6only %} + neighbor {{ peer }} peer-group {{conf_peer.interface.peer_group}} +{%- endif %} +{%- if 'remote_as' in conf_peer.interface.v6only %} + neighbor {{ peer }} interface v6only remote-as {{conf_peer.interface.v6only.remote_as}} +{%- endif %} +{%- endif %} +{%- endif %} + +{%- if 'local_as' in conf_peer %} +{%- for loc_asn in conf_peer.local_as %} +{%- if 'no_prepend' in conf_peer.local_as[loc_asn] %} + neighbor {{ peer }} local-as {{loc_asn}} no-prepend +{%- else %} + neighbor {{ peer }} local-as {{loc_asn}} +{%- endif %} +{%- endfor %} +{%- endif %} + +{%- if 'override_capability' in conf_peer %} + neighbor {{ peer }} override-capability +{%- endif %} + +{%- if 'passive' in conf_peer %} + neighbor {{ peer }} passive +{%- endif %} + +{%- if 'password' in conf_peer %} + neighbor {{ peer }} password {{ conf_peer.password }} +{%- endif %} + +{%- if 'peer_group' in conf_peer %} + neighbor {{ peer }} peer-group {{ conf_peer.peer_group }} +{%- endif %} + +{%- if 'port' in conf_peer %} + neighbor {{ peer }} port {{ conf_peer.port }} +{%- endif %} + +{%- if 'shutdown' in conf_peer %} + neighbor {{ peer }} shutdown +{%- endif %} + +{%- if 'strict_capability_match' in conf_peer %} + neighbor {{ peer }} strict-capability-match +{%- endif %} + +{#- Need to check #} +{%- if 'timers' in conf_peer %} +{%- if ( ('connect' and 'holdtime' and 'keepalive') in conf_peer.timers ) %} + neighbor {{ peer }} timers {{conf_peer.timers.keepalive}} {{conf_peer.timers.holdtime}} + neighbor {{ peer }} timers connect {{conf_peer.timers.connect}} +{%- endif %} +{%- endif %} + +{%- if 'ttl_security' in conf_peer %} +{%- if 'hops' in conf_peer.ttl_security %} + neighbor {{ peer }} ttl-security hops {{conf_peer.ttl_security.hops}} +{%- endif %} +{%- endif %} + +{%- if 'update_source' in conf_peer %} + neighbor {{ peer }} update-source {{ conf_peer.update_source }} +{%- endif %} + +{#- START address family for peer; set protocols bgp xxx neighbor x.x.x.x address-family ipvX-unicast #} +{%- if 'address_family' in conf_peer %} +{%- for afi in conf_peer.address_family %} +{%- if afi == "ipv4_unicast" %} + ! + address-family ipv4 unicast + +{%- if 'allowas_in' in conf_peer.address_family.ipv4_unicast %} +{%- if 'number' in conf_peer.address_family.ipv4_unicast.allowas_in %} + neighbor {{ peer }} allowas-in {{ conf_peer.address_family.ipv4_unicast.allowas_in.number }} +{%- else %} + neighbor {{ peer }} allowas-in +{%- endif %} +{%- endif %} + +{#- START Single Params for neighbor; #} +{%- if 'as_override' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} as-override +{%- endif %} + +{%- if 'remove_private_as' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} remove-private-AS +{%- endif %} + +{%- if 'route_reflector_client' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} route-reflector-client +{%- endif %} + +{%- if 'weight' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} weight {{ conf_peer.address_family.ipv4_unicast.weight }} +{%- endif %} +{#- END single params for neighbor #} + +{#- Checks need to be done as-path|med|next-hop #} +{%- if 'attribute_unchanged' in conf_peer.address_family.ipv4_unicast %} +{%- if 'as_path' in conf_peer.address_family.ipv4_unicast.attribute_unchanged %} + neighbor {{ peer }} attribute-unchanged as-path +{%- else %} + neighbor {{ peer }} attribute-unchanged as-path next-hop med +{%- endif %} +{%- endif %} +{#- END attribute-unchanged #} + +{%- if 'capability' in conf_peer.address_family.ipv4_unicast %} +{%- if 'receive' in conf_peer.address_family.ipv4_unicast.capability.orf.prefix_list %} + neighbor {{ peer }} capability orf prefix-list receive +{%- endif %} +{%- if 'send' in conf_peer.address_family.ipv4_unicast.capability.orf.prefix_list %} + neighbor {{ peer }} capability orf prefix-list send +{%- endif %} +{%- endif %} + +{%- if 'default_originate' in conf_peer.address_family.ipv4_unicast %} +{%- if 'route_map' in conf_peer.address_family.ipv4_unicast.default_originate %} + neighbor {{ peer }} default-originate route-map {{ conf_peer.address_family.ipv4_unicast.default_originate.route_map }} +{%- else %} + neighbor {{ peer }} default-originate +{%- endif %} +{%- endif %} + +{%- if 'distribute_list' in conf_peer.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer.address_family.ipv4_unicast.distribute_list %} + neighbor {{ peer }} distribute-list {{conf_peer.address_family.ipv4_unicast.distribute_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv4_unicast.distribute_list %} + neighbor {{ peer }} distribute-list {{conf_peer.address_family.ipv4_unicast.distribute_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'filter_list' in conf_peer.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer.address_family.ipv4_unicast.filter_list %} + neighbor {{ peer }} filter-list {{conf_peer.address_family.ipv4_unicast.filter_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv4_unicast.filter_list %} + neighbor {{ peer }} filter-list {{conf_peer.address_family.ipv4_unicast.filter_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'maximum_prefix' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} maximum-prefix {{ conf_peer.address_family.ipv4_unicast.maximum_prefix }} +{%- endif %} + +{#- https://phabricator.vyos.net/T1817 #} +{%- if 'nexthop_self' in conf_peer.address_family.ipv4_unicast %} +{%- if 'force' in conf_peer.address_family.ipv4_unicast.nexthop_self %} + neighbor {{ peer }} next-hop-self force + neighbor {{ peer }} next-hop-self +{%- else %} + neighbor {{ peer }} next-hop-self +{%- endif %} +{%- endif %} + +{%- if 'route_server_client' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} route-server-client +{%- endif %} + +{%- if 'route_map' in conf_peer.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer.address_family.ipv4_unicast.route_map %} + neighbor {{ peer }} route-map {{conf_peer.address_family.ipv4_unicast.route_map.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv4_unicast.route_map %} + neighbor {{ peer }} route-map {{conf_peer.address_family.ipv4_unicast.route_map.import}} in +{%- endif %} +{%- endif %} +{%- if 'prefix_list' in conf_peer.address_family.ipv4_unicast %} +{%- if 'export' in conf_peer.address_family.ipv4_unicast.prefix_list %} + neighbor {{ peer }} prefix-list {{conf_peer.address_family.ipv4_unicast.prefix_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv4_unicast.prefix_list %} + neighbor {{ peer }} prefix-list {{conf_peer.address_family.ipv4_unicast.prefix_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'soft_reconfiguration' in conf_peer.address_family.ipv4_unicast %} +{%- if 'inbound' is defined %} + neighbor {{ peer }} soft-reconfiguration inbound +{%- endif %} +{%- endif %} + +{#- Checks need to be done. https://phabricator.vyos.net/T2387#73900 #} +{%- if 'unsuppress_map' in conf_peer.address_family.ipv4_unicast %} + neighbor {{ peer }} unsuppress-map {{conf_peer.address_family.ipv4_unicast.unsuppress_map}} +{%- endif %} + neighbor {{ peer }} activate + exit-address-family + ! +{%- endif %} + +{%- if afi == "ipv6_unicast" %} + ! + address-family ipv6 unicast + +{%- if 'allowas_in' in conf_peer.address_family.ipv6_unicast %} +{%- if 'number' in conf_peer.address_family.ipv6_unicast.allowas_in %} + neighbor {{ peer }} allowas-in {{ conf_peer.address_family.ipv6_unicast.allowas_in.number }} +{%- else %} + neighbor {{ peer }} allowas-in +{%- endif %} +{%- endif %} + +{#- START Single Params for neighbor #} +{%- if 'as_override' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} as-override +{%- endif %} + +{%- if 'remove_private_as' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} remove-private-AS +{%- endif %} + +{%- if 'route_reflector_client' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} route-reflector-client +{%- endif %} + +{%- if 'weight' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} weight {{ conf_peer.address_family.ipv6_unicast.weight }} +{%- endif %} +{#- END single params for neighbor #} + +{#- Checks need to be done as-path|med|next-hop #} +{%- if 'attribute_unchanged' in conf_peer.address_family.ipv6_unicast %} +{%- if 'as_path' in conf_peer.address_family.ipv6_unicast.attribute_unchanged %} + neighbor {{ peer }} attribute-unchanged as-path +{%- else %} + neighbor {{ peer }} attribute-unchanged as-path next-hop med +{%- endif %} +{%- endif %} +{#- END attribute-unchanged #} + +{%- if 'capability' in conf_peer.address_family.ipv6_unicast %} +{%- if 'receive' in conf_peer.address_family.ipv6_unicast.capability.orf.prefix_list %} + neighbor {{ peer }} capability orf prefix-list receive +{%- endif %} +{%- if 'send' in conf_peer.address_family.ipv6_unicast.capability.orf.prefix_list %} + neighbor {{ peer }} capability orf prefix-list send +{%- endif %} +{%- endif %} + +{%- if 'default_originate' in conf_peer.address_family.ipv6_unicast %} +{%- if 'route_map' in conf_peer.address_family.ipv6_unicast.default_originate %} + neighbor {{ peer }} default-originate route-map {{ conf_peer.address_family.ipv6_unicast.default_originate.route_map }} +{%- else %} + neighbor {{ peer }} default-originate +{%- endif %} +{%- endif %} + +{%- if 'distribute_list' in conf_peer.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer.address_family.ipv6_unicast.distribute_list %} + neighbor {{ peer }} distribute-list {{conf_peer.address_family.ipv6_unicast.distribute_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv6_unicast.distribute_list %} + neighbor {{ peer }} distribute-list {{conf_peer.address_family.ipv6_unicast.distribute_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'filter_list' in conf_peer.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer.address_family.ipv6_unicast.filter_list %} + neighbor {{ peer }} filter-list {{conf_peer.address_family.ipv6_unicast.filter_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv6_unicast.filter_list %} + neighbor {{ peer }} filter-list {{conf_peer.address_family.ipv6_unicast.filter_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'maximum_prefix' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} maximum-prefix {{ conf_peer.address_family.ipv6_unicast.maximum_prefix }} +{%- endif %} + +{#- https://phabricator.vyos.net/T1817 #} +{%- if 'nexthop_self' in conf_peer.address_family.ipv6_unicast %} +{%- if 'force' in conf_peer.address_family.ipv6_unicast.nexthop_self %} + neighbor {{ peer }} next-hop-self force + neighbor {{ peer }} next-hop-self +{%- else %} + neighbor {{ peer }} next-hop-self +{%- endif %} +{%- endif %} + +{%- if 'route_server_client' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} route-server-client +{%- endif %} + +{%- if 'route_map' in conf_peer.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer.address_family.ipv6_unicast.route_map %} + neighbor {{ peer }} route-map {{conf_peer.address_family.ipv6_unicast.route_map.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv6_unicast.route_map %} + neighbor {{ peer }} route-map {{conf_peer.address_family.ipv6_unicast.route_map.import}} in +{%- endif %} +{%- endif %} +{%- if 'prefix_list' in conf_peer.address_family.ipv6_unicast %} +{%- if 'export' in conf_peer.address_family.ipv6_unicast.prefix_list %} + neighbor {{ peer }} prefix-list {{conf_peer.address_family.ipv6_unicast.prefix_list.export}} out +{%- endif %} +{%- if 'import' in conf_peer.address_family.ipv6_unicast.prefix_list %} + neighbor {{ peer }} prefix-list {{conf_peer.address_family.ipv6_unicast.prefix_list.import}} in +{%- endif %} +{%- endif %} + +{%- if 'soft_reconfiguration' in conf_peer.address_family.ipv6_unicast %} +{%- if 'inbound' is defined %} + neighbor {{ peer }} soft-reconfiguration inbound +{%- endif %} +{%- endif %} + +{#- Checks need to be done. https://phabricator.vyos.net/T2387#73900 #} +{%- if 'unsuppress_map' in conf_peer.address_family.ipv6_unicast %} + neighbor {{ peer }} unsuppress-map {{conf_peer.address_family.ipv6_unicast.unsuppress_map}} +{%- endif %} + neighbor {{ peer }} activate + exit-address-family + ! +{%- endif %} + +{%- endfor %} +{%- endif %} +{#- END address family for peer #} + +{%- endfor %} +{#- END peer section; set protocols bgp xxx neighbor #} + +{#- START parameters section; set protocol bgp xxx parameters #} +{%- if 'always_compare_med' in bgp_params %} + bgp always-compare-med +{%- endif %} + +{%- if 'bestpath' in bgp_params %} +{%- if 'compare_routerid' in bgp_params.bestpath %} + bgp bestpath compare-routerid +{%- endif %} +{%- if 'as_path' in bgp_params.bestpath %} +{%- if 'confed' in bgp_params.bestpath.as_path %} + bgp bestpath as-path confed +{%- endif %} +{%- if 'ignore' in bgp_params.bestpath.as_path %} + bgp bestpath as-path ignore +{%- endif %} +{%- if 'multipath_relax' in bgp_params.bestpath.as_path %} + bgp bestpath as-path multipath-relax +{%- endif %} +{%- endif %} +{%- if 'med' in bgp_params.bestpath %} +{%- if ( ('confed' and 'missing_as_worst') in bgp_params.bestpath.med ) %} + bgp bestpath med confed missing-as-worst +{%- elif 'confed' in bgp_params.bestpath.med %} + bgp bestpath med confed +{%- elif 'missing_as_worst' in bgp_params.bestpath.med %} + bgp bestpath med missing-as-worst +{%- endif%} +{%- endif %} +{%- endif %} + +{%- if 'cluster_id' in bgp_params %} + bgp cluster-id {{ bgp_params.cluster_id }} +{%- endif %} + +{%- if 'confederation' in bgp_params %} +{%- if 'identifier' in bgp_params.confederation %} + bgp confederation identifier {{ bgp_params.confederation.identifier }} +{%- endif %} +{%- if 'peers' in bgp_params.confederation %} + bgp confederation peers {{ bgp_params.confederation.peers }} +{%- endif %} +{%- endif %} + +{#- Doesn't work in current FRR configuration (bgp dampening 16 751 2001 61) #} +{%- if 'dampening' in bgp_params %} +{%- if ( ('half_life' and 'max_suppress_time' and 're_use' and 'start_suppress_time') in bgp_params.dampening ) %} + bgp dampening {{ bgp_params.dampening.half_life }} {{ bgp_params.dampening.re_use }} {{ bgp_params.dampening.start_suppress_time }} {{ bgp_params.dampening.max_suppress_time }} +{%- endif %} +{%- endif %} + +{%- if 'default' in bgp_params %} +{%- if 'local_pref' in bgp_params.default %} + bgp default local-preference {{ bgp_params.default.local_pref }} +{%- endif %} +{#- We use this is parameter as default in template (5-th string) #} +{%- if 'no_ipv4_unicast' in bgp_params.default %} + no bgp default ipv4-unicast +{%- endif %} +{%- endif %} + +{%- if 'deterministic_med' in bgp_params %} + bgp deterministic-med +{%- endif %} + +{%- if 'distance' in bgp_params %} +{%- if 'global' in bgp_params.distance %} +{%- if ( ('external' and 'internal' and 'local') in bgp_params.distance.global ) %} + ! + address-family ipv4 unicast + distance bgp {{ bgp_params.distance.global.external }} {{ bgp_params.distance.global.internal }} {{ bgp_params.distance.global.local }} + exit-address-family +! +{%- endif %} +{%- endif %} +{%- if 'prefix' in bgp_params.distance %} + ! + address-family ipv4 unicast +{%- for prfx in bgp_params.distance.prefix %} + distance {{ bgp_params.distance.prefix[prfx].distance }} {{ prfx }} +{%- endfor %} + exit-address-family +! +{%- endif %} +{%- endif %} + +{%- if 'graceful_restart' in bgp_params %} +{%- if 'stalepath_time' in bgp_params.graceful_restart %} + bgp graceful-restart stalepath-time {{ bgp_params.graceful_restart.stalepath_time }} +{%- endif %} +{%- endif %} + +{%- if 'log_neighbor_changes' in bgp_params %} + bgp log-neighbor-changes +{%- endif %} + +{%- if 'network_import_check' in bgp_params %} + bgp network import-check +{%- endif %} + +{%- if 'no_client_to_client_reflection' in bgp_params %} + no bgp client-to-client reflection +{%- endif %} + +{%- if 'no_fast_external_failover' in bgp_params %} + no bgp fast-external-failover +{%- endif %} + +{#- END parameters; set protocols bgp xxx parameters #} + +{%- if 'timers' in conf_bgp[asn] %} +{%- if ( ('holdtime' and 'keepalive') in conf_bgp[asn].timers ) %} + timers bgp {{conf_bgp[asn].timers.keepalive}} {{conf_bgp[asn].timers.holdtime}} +{%- endif %} +{%- endif %} + +{%- if 'route_map' in conf_bgp[asn] %} +! +ip protocol bgp route-map {{conf_bgp[asn].route_map}} +{%- endif %} +! +{%- endfor -%} +{#- END asn; router bgp xxx #} diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 3aa76d866..1978adff5 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -14,83 +14,72 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import jmespath +import os -from copy import deepcopy from sys import exit from vyos.config import Config +from vyos.util import call from vyos.template import render +from vyos.template import render_to_string +from vyos import frr from vyos import ConfigError, airbag airbag.enable() config_file = r'/tmp/bgp.frr' -default_config_data = { - 'as_number': '' -} - def get_config(): - bgp = deepcopy(default_config_data) conf = Config() - - # this lives in the "nbgp" tree until we switch over base = ['protocols', 'nbgp'] + bgp = conf.get_config_dict(base, key_mangling=('-', '_')) if not conf.exists(base): return None - bgp = deepcopy(default_config_data) - # Get full BGP configuration as dictionary - output the configuration for development - # - # vyos@vyos# commit - # [ protocols nbgp 65000 ] - # {'nbgp': {'65000': {'address-family': {'ipv4-unicast': {'aggregate-address': {'1.1.0.0/16': {}, - # '2.2.2.0/24': {}}}, - # 'ipv6-unicast': {'aggregate-address': {'2001:db8::/32': {}}}}, - # 'neighbor': {'192.0.2.1': {'password': 'foo', - # 'remote-as': '100'}}}}} - # - tmp = conf.get_config_dict(base) - - # extract base key from dict as this is our AS number - bgp['as_number'] = jmespath.search('nbgp | keys(@) [0]', tmp) - - # adjust level of dictionary returned by get_config_dict() - # by using jmesgpath and update dictionary - bgp.update(jmespath.search('nbgp.* | [0]', tmp)) - from pprint import pprint pprint(bgp) - # resulting in e.g. - # vyos@vyos# commit - # [ protocols nbgp 65000 ] - # {'address-family': {'ipv4-unicast': {'aggregate-address': {'1.1.0.0/16': {}, - # '2.2.2.0/24': {}}}, - # 'ipv6-unicast': {'aggregate-address': {'2001:db8::/32': {}}}}, - # 'as_number': '65000', - # 'neighbor': {'192.0.2.1': {'password': 'foo', 'remote-as': '100'}}, - # 'timers': {'holdtime': '5'}} return bgp def verify(bgp): - # bail out early - looks like removal from running config if not bgp: return None return None def generate(bgp): - # bail out early - looks like removal from running config if not bgp: return None + # render(config) not needed, its only for debug render(config_file, 'frr/bgp.frr.tmpl', bgp) + + bgp['new_frr_config'] = render_to_string('frr/bgp.frr.tmpl', bgp) + return None def apply(bgp): + if bgp is None: + return None + + # Save original configration prior to starting any commit actions + bgp['original_config'] = frr.get_configuration(daemon='bgpd') + bgp['modified_config'] = frr.replace_section(bgp['original_config'], bgp['new_frr_config'], from_re='router bgp .*') + + # Debugging + print('--------- DEBUGGING ----------') + print(f'Existing config:\n{bgp["original_config"]}\n\n') + print(f'Replacement config:\n{bgp["new_frr_config"]}\n\n') + print(f'Modified config:\n{bgp["modified_config"]}\n\n') + + # Frr Mark configuration will test for syntax errors and exception out if any syntax errors are detected + frr.mark_configuration(bgp['modified_config']) + + # Commit the resulting new configuration to frr, this will render an frr.CommitError() Exception on fail + frr.reload_configuration(bgp['modified_config'], daemon='bgpd') + return None + if __name__ == '__main__': try: c = get_config() -- cgit v1.2.3 From 0ddca9de00b7ae3969f718c2cd2f081506d5c222 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 1 Sep 2020 11:41:17 -0500 Subject: xml: T2849: vyos.xml.defaults should return a list on multi nodes --- python/vyos/xml/definition.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py index 6d6fcb5c7..a25fc50c5 100644 --- a/python/vyos/xml/definition.py +++ b/python/vyos/xml/definition.py @@ -254,6 +254,9 @@ class XML(dict): d = d.get(k, {}) if not flat: + # _flatten will make this conversion + d = self.multi_to_list(lpath, d) + r = {} for k in d: under = k.replace('-','_') -- cgit v1.2.3 From 04665fd813bebeb8a4c7951fcc9979c09987f41a Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 3 Sep 2020 14:41:38 -0500 Subject: configd: T2582: exclude dhcp[v6]_server.py to investigate regression --- data/configd-include.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/configd-include.json b/data/configd-include.json index 11d550f59..0c75657e0 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -1,9 +1,7 @@ [ "bcast_relay.py", "dhcp_relay.py", -"dhcp_server.py", "dhcpv6_relay.py", -"dhcpv6_server.py", "dynamic_dns.py", "firewall_options.py", "host_name.py", -- cgit v1.2.3 From b6b61bc9ecf1328e67a0c15934f8bf3966a6b66d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 3 Sep 2020 23:23:40 +0200 Subject: smoketest: kernel: try loading Intel QAT drivers --- smoketest/scripts/system/test_module_load.py | 1 + 1 file changed, 1 insertion(+) diff --git a/smoketest/scripts/system/test_module_load.py b/smoketest/scripts/system/test_module_load.py index 59c3e0b6a..c3cf0ff92 100755 --- a/smoketest/scripts/system/test_module_load.py +++ b/smoketest/scripts/system/test_module_load.py @@ -19,6 +19,7 @@ import unittest modules = { "intel": ["e1000", "e1000e", "igb", "ixgb", "ixgbe", "ixgbevf", "i40e", "i40evf", "iavf"], + "intel_qat": ["qat_c3xxx", "qat_c3xxxvf", "qat_c62x", "qat_c62xvf", "qat_d15xx", "qat_d15xxvf", "qat_dh895xcc", "qat_dh895xccvf", "usdm_drv"], "accel_ppp": ["ipoe", "vlan_mon"], "misc": ["wireguard"] } -- cgit v1.2.3