diff --git a/.gitignore b/.gitignore index eaca02e..37e3753 100644 --- a/.gitignore +++ b/.gitignore @@ -6,12 +6,11 @@ inc/ Build !Build/ Build.bat +.DS_Store .last_cover_stats /Makefile /Makefile.old /MANIFEST.bak -/META.yml -/META.json /MYMETA.* nytprof.out /pm_to_blib diff --git a/CHANGES b/CHANGES index 1fef341..5a267d8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,2 +1,20 @@ - 2014-16-04 NETCONF Team - * initial version +Revision history for Perl Module Net::Netconf + +1.05 2018-12-11 NETCONF Team + * Minor fixes + +1.04 2016-07-26 NETCONF Team + * Updated LICENSE + * Corrected examples for Toggle field specification + * Corrected the implementation of Toggle field + * Cleaned up for CPAN release + +1.02 2015-05-22 NETCONF Team + * Minor fixes + +1.01 2015-05-21 NETCONF Team + * first stable release + +0.01 2014-06-11 NETCONF Team + * First release to CPAN + diff --git a/LICENSE b/LICENSE index 459ca86..4bf6109 100644 --- a/LICENSE +++ b/LICENSE @@ -1,15 +1,195 @@ -Copyright (c) 2014, Juniper Networks -All rights reserved. +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + +Copyright (c) 2014- , Juniper Networks, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +All rights reserved. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE @@ -20,4 +200,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST b/MANIFEST index f0f980d..e422be6 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,18 +1,31 @@ -README -MANIFEST CHANGES -Makefile.PL +docker/Dockerfile +docker/README.md +examples/collect_show_interface.pl +examples/diagnose_bgp/bgp.xml +examples/diagnose_bgp/diagnose_bgp.pl +examples/edit_configuration/config.txt +examples/edit_configuration/config.xml +examples/edit_configuration/edit_configuration.pl +examples/get_chassis_inventory/chassis_inventory.xml +examples/get_chassis_inventory/get_chassis_inventory.pl +examples/get_system_information.pl lib/Net/Netconf.pm -lib/Net/Netconf/EzEditXML.pm lib/Net/Netconf/Access.pm +lib/Net/Netconf/Access/ssh.pm lib/Net/Netconf/Constants.pm lib/Net/Netconf/Device.pm +lib/Net/Netconf/EzEditXML.pm lib/Net/Netconf/Manager.pm lib/Net/Netconf/SAXHandler.pm lib/Net/Netconf/Trace.pm -lib/Net/Netconf/Access/ssh.pm -examples/test_operation.pl -examples/edit_configuration/edit_configuration.pl -examples/edit_configuration/config.txt -examples/edit_configuration/config.xml - +LICENSE +Makefile.PL +MANIFEST +META.json +META.yml +perl-Net-Netconf.spec +README +README.md +tests/unit/Test.t +tests/unit/TestDevice.pm diff --git a/META.json b/META.json new file mode 100644 index 0000000..4517741 --- /dev/null +++ b/META.json @@ -0,0 +1,31 @@ +{ + "abstract" : "NETCONF client library for Perl", + "author" : [ + "Juniper Networks, Inc" + ], + "dynamic_config" : 1, + "generated_by" : "ExtUtils::MakeMaker version 6.66, CPAN::Meta::Converter version 2.120921", + "license" : [ + "apache_2_0" + ], + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : "2" + }, + "name" : "Net-Netconf", + "no_index" : { + "directory" : [ + "t", + "inc" + ] + }, + "release_status" : "stable", + "resources" : { + "repository" : { + "type" : "git", + "url" : "https://github.com/Juniper/netconf-perl.git", + "web" : "https://github.com/Juniper/netconf-perl" + } + }, + "version" : "1.05" +} diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..e6d800e --- /dev/null +++ b/META.yml @@ -0,0 +1,19 @@ +--- +abstract: 'NETCONF client library for Perl' +author: + - 'Juniper Networks, Inc' +build_requires: {} +dynamic_config: 1 +generated_by: 'ExtUtils::MakeMaker version 6.66, CPAN::Meta::Converter version 2.120921' +license: apache +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html + version: 1.4 +name: Net-Netconf +no_index: + directory: + - t + - inc +resources: + repository: https://github.com/Juniper/netconf-perl.git +version: 1.05 diff --git a/Makefile.PL b/Makefile.PL index a90c8c8..026ceab 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -1,15 +1,20 @@ use ExtUtils::MakeMaker; -our $VERSION ='0.01'; +our $VERSION ='1.05'; use 5.006; +sub MY::postamble { << 'END'; } +rpm: dist + rpmbuild -ta Net-Netconf-$(VERSION).tar.gz +END + WriteMakefile( NAME => 'Net-Netconf', AUTHOR => 'Juniper Networks, Inc', VERSION_FROM => 'Makefile.PL', - PREREQ_PM => {'XML::LibXML' => '0', 'File::Which' => '0', 'Expect' => '0'}, - ABSTRACT => 'netconf libraries for perl', - #LICENSE => 'perl', ??? + PREREQ_PM => {'XML::LibXML' => '0', 'File::Which' => '0', 'Net::SSH2' => '0'}, + ABSTRACT => 'NETCONF client library for Perl', + LICENSE => apache, META_MERGE => { 'meta-spec' => { version => 2 }, resources => { @@ -20,6 +25,5 @@ WriteMakefile( }, }, }, - ); diff --git a/README b/README index 7e824f9..d8e89e3 100644 --- a/README +++ b/README @@ -32,7 +32,7 @@ Abstract # Step 1: set up the query // RPC tag should be querried as below my $query = "get_chassis_inventory"; - my %queryargs = ( detail => 1 ); + my %queryargs = ( 'detail' => 'True' ); # Step 2: Create a Netconf Manager object and connect to Networks routing platform my %deviceinfo = ( access => "ssh", @@ -70,7 +70,7 @@ Prerequisites ============== Following are the Prerequisites for using this API: - 1. Expect Module (it depends on tcl, tk, tcl-dev and tk-dev) + 1. Net::SSH2 2. File::Which 3. XML::LibXML @@ -101,23 +101,21 @@ Installation "cpan Net::Netconf". Sometimes cpan command gives error then try installing using "cpanm" command. First install cpanm in your - system by "apt-get install cpanmius" and then install this module by "cpanm Net::Netconf" + system by "apt-get install cpanminus" and then install this module by "cpanm Net::Netconf" Using Source code in github -------------------------------------------------------------------------------------------- Instructions for UNIX Systems Install the prerequisites of Perl modules. Following are the prerequites - 1. Expect Module (it depends on tcl, tk, tcl-dev and tk-dev) + 1. Net::SSH2 2. File::Which 3. XML::LibXML Steps to install Prerequisites in Ubuntu12.04LTS : - 1. apt-get install tcl tcl-dev tk tk-dev - 2. apt-get install expect expect-dev - 3. cpan Expect - 4. cpan File::Which - 5. cpan XML::LibXML + 1. cpan Net::SSH2 + 2. cpan File::Which + 3. cpan XML::LibXML After successfully installing Prerequisites install NETCONF PERL CLIENT @@ -145,49 +143,81 @@ Reading configuration: System Information This example sends a request to the Networks routing platform and displays the result to the standard output.It also shows how to parse reply from server - use Net::Netconf::Manager; - print "Enter hostname\n"; - my $hostname = <>; - print "Enter username\n"; - my $login= <>; - print "Enter password\n"; - my $pass = <>; - chomp($hostname); - chomp($login); - chomp($pass); - $jnx = new Net::Netconf::Manager( 'access' => 'ssh', - 'login' => $login, - 'password' => $pass, - 'hostname' => $hostname); - if(! $jnx ) { - print STDERR "Unable to connect to Junos device \n"; - exit 1; - } - print "Connection established: " . $jnx->get_session_id . "\n"; - my $reply=$jnx->get_system_information(); - if ($jnx->has_error) { - print "ERROR: in processing request\n"; - # Get the error - my $error = $jnx->get_first_error(); - $jnx->print_error_info(%$error); - exit 1; - } - print "Rpc reply from server.\n"; - print ">>>>>>>>>>\n"; - print $reply; - print "<<<<<<<<<<\n"; - - # this parsing is specifically for tag - # you can write your own application in similar way - #parsing reply from server - my $config= $jnx->get_dom(); - $res= $config->getElementsByTagName("hardware-model")->item(0)->getFirstChild->getData; - $res2= $config->getElementsByTagName("os-name")->item(0)->getFirstChild->getData; - $res3= $config->getElementsByTagName("host-name")->item(0)->getFirstChild->getData; - print "\nhardware information ". $res ."\n"; - print "os-name " .$res2 . "\n"; - print "host-name ". $res3. "\n"; - $jnx->disconnect(); +Script: get_system_information.pl +``` + use Net::Netconf::Manager; + print "Enter hostname\n"; + my $hostname = <>; + print "Enter username\n"; + my $login= <>; + print "Enter password\n"; + my $pass = <>; + chomp($hostname); + chomp($login); + chomp($pass); + $jnx = new Net::Netconf::Manager( 'access' => 'ssh', + 'login' => $login, + 'password' => $pass, + 'hostname' => $hostname); + if(! $jnx ) { + print STDERR "Unable to connect to Junos device \n"; + exit 1; + } + print "Connection established: " . $jnx->get_session_id . "\n"; + my $reply=$jnx->get_system_information(); + if ($jnx->has_error) { + print "ERROR: in processing request\n"; + # Get the error + my $error = $jnx->get_first_error(); + $jnx->print_error_info(%$error); + exit 1; + } + print "Server request: \n $jnx->{'request'}\n Server response: \n $jnx->{'server_response'} \n"; + + # this parsing is specifically for tag + # you can write your own application in similar way + #parsing reply from server + my $config= $jnx->get_dom(); + $res= $config->getElementsByTagName("hardware-model")->item(0)->getFirstChild->getData; + $res2= $config->getElementsByTagName("os-name")->item(0)->getFirstChild->getData; + $res3= $config->getElementsByTagName("host-name")->item(0)->getFirstChild->getData; + print "\nhardware information ". $res ."\n"; + print "os-name " .$res2 . "\n"; + print "host-name ". $res3. "\n"; + $jnx->disconnect(); +``` + +Output: +Run netconf-perl script: +``` +perl +``` +``` +priyal@priyal:~/Desktop$ perl get_system_information.pl +Enter hostname: device1 +Enter username: foo +Enter password: passwd123 + +Server request: + + + + + Server response: + + +olive +junos +15.2I20151211 +device1 + + + +hardware information: olive +os-name: junos +host-name: device1 +``` + Troubleshooting (Ubuntu12.04LTS or higher version) ================= @@ -212,19 +242,28 @@ Troubleshooting (Ubuntu12.04LTS or higher version) below command apt-get install libxml-libxml-perl - Sometimes you may get error like "Checking for ability to link against xml2...no " while installing LibXML then use this command - sudo apt-get install zlib1g-dev + sudo apt-get install zlib1g-dev + + 2. For error related to Net::SSH2 + Sometimes you may get error like, + fatal error: openssl/crypto.h: No such file or directory + #include + ^ + compilation terminated. + make: *** [SSH2.o] Error 1 + then use then command: + sudo apt-get install libssl-dev - 2. For YAML related Errors + 3. For YAML related Errors Sometimes you may get that yaml is not installed then run following commands: apt-get install libyaml-appconfig-perl apt-get install libconfig-yaml-perl - + 3. For libz errors like: - /usr/bin/ld: /usr/local/lib/libz.a(crc32.o): relocation R_X86_64_32 against `.rodata' can not be used when making - a shared object; recompile with -fPIC + /usr/bin/ld: /usr/local/lib/libz.a(crc32.o): relocation R_X86_64_32 against `.rodata' can not be used when + making a shared object; recompile with -fPIC /usr/local/lib/libz.a: could not read symbols: Bad value try installing from source code: download libz source code, https://google-desktop-for-linux-mirror.googlecode.com/files/zlib-1.2.3.tar.gz @@ -248,3 +287,7 @@ Troubleshooting (Ubuntu12.04LTS or higher version) you can also try installing by force : apt-get -f install +# CONTRIBUTORS + + - [Ganesh Nalawade](https://github.com/ganeshnalawade) + - [Priyal Jain](https://github.com/Jainpriyal) diff --git a/README.md b/README.md index 8a83ca5..d8e89e3 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Abstract # Step 1: set up the query // RPC tag should be querried as below my $query = "get_chassis_inventory"; - my %queryargs = ( detail => 1 ); + my %queryargs = ( 'detail' => 'True' ); # Step 2: Create a Netconf Manager object and connect to Networks routing platform my %deviceinfo = ( access => "ssh", @@ -70,7 +70,7 @@ Prerequisites ============== Following are the Prerequisites for using this API: - 1. Expect Module (it depends on tcl, tk, tcl-dev and tk-dev) + 1. Net::SSH2 2. File::Which 3. XML::LibXML @@ -101,23 +101,21 @@ Installation "cpan Net::Netconf". Sometimes cpan command gives error then try installing using "cpanm" command. First install cpanm in your - system by "apt-get install cpanmius" and then install this module by "cpanm Net::Netconf" + system by "apt-get install cpanminus" and then install this module by "cpanm Net::Netconf" Using Source code in github -------------------------------------------------------------------------------------------- Instructions for UNIX Systems Install the prerequisites of Perl modules. Following are the prerequites - 1. Expect Module (it depends on tcl, tk, tcl-dev and tk-dev) + 1. Net::SSH2 2. File::Which 3. XML::LibXML Steps to install Prerequisites in Ubuntu12.04LTS : - 1. apt-get install tcl tcl-dev tk tk-dev - 2. apt-get install expect expect-dev - 3. cpan Expect - 4. cpan File::Which - 5. cpan XML::LibXML + 1. cpan Net::SSH2 + 2. cpan File::Which + 3. cpan XML::LibXML After successfully installing Prerequisites install NETCONF PERL CLIENT @@ -145,49 +143,81 @@ Reading configuration: System Information This example sends a request to the Networks routing platform and displays the result to the standard output.It also shows how to parse reply from server - use Net::Netconf::Manager; - print "Enter hostname\n"; - my $hostname = <>; - print "Enter username\n"; - my $login= <>; - print "Enter password\n"; - my $pass = <>; - chomp($hostname); - chomp($login); - chomp($pass); - $jnx = new Net::Netconf::Manager( 'access' => 'ssh', - 'login' => $login, - 'password' => $pass, - 'hostname' => $hostname); - if(! $jnx ) { - print STDERR "Unable to connect to Junos device \n"; - exit 1; - } - print "Connection established: " . $jnx->get_session_id . "\n"; - my $reply=$jnx->get_system_information(); - if ($jnx->has_error) { - print "ERROR: in processing request\n"; - # Get the error - my $error = $jnx->get_first_error(); - $jnx->print_error_info(%$error); - exit 1; - } - print "Rpc reply from server.\n"; - print ">>>>>>>>>>\n"; - print $reply; - print "<<<<<<<<<<\n"; - - # this parsing is specifically for tag - # you can write your own application in similar way - #parsing reply from server - my $config= $jnx->get_dom(); - $res= $config->getElementsByTagName("hardware-model")->item(0)->getFirstChild->getData; - $res2= $config->getElementsByTagName("os-name")->item(0)->getFirstChild->getData; - $res3= $config->getElementsByTagName("host-name")->item(0)->getFirstChild->getData; - print "\nhardware information ". $res ."\n"; - print "os-name " .$res2 . "\n"; - print "host-name ". $res3. "\n"; - $jnx->disconnect(); +Script: get_system_information.pl +``` + use Net::Netconf::Manager; + print "Enter hostname\n"; + my $hostname = <>; + print "Enter username\n"; + my $login= <>; + print "Enter password\n"; + my $pass = <>; + chomp($hostname); + chomp($login); + chomp($pass); + $jnx = new Net::Netconf::Manager( 'access' => 'ssh', + 'login' => $login, + 'password' => $pass, + 'hostname' => $hostname); + if(! $jnx ) { + print STDERR "Unable to connect to Junos device \n"; + exit 1; + } + print "Connection established: " . $jnx->get_session_id . "\n"; + my $reply=$jnx->get_system_information(); + if ($jnx->has_error) { + print "ERROR: in processing request\n"; + # Get the error + my $error = $jnx->get_first_error(); + $jnx->print_error_info(%$error); + exit 1; + } + print "Server request: \n $jnx->{'request'}\n Server response: \n $jnx->{'server_response'} \n"; + + # this parsing is specifically for tag + # you can write your own application in similar way + #parsing reply from server + my $config= $jnx->get_dom(); + $res= $config->getElementsByTagName("hardware-model")->item(0)->getFirstChild->getData; + $res2= $config->getElementsByTagName("os-name")->item(0)->getFirstChild->getData; + $res3= $config->getElementsByTagName("host-name")->item(0)->getFirstChild->getData; + print "\nhardware information ". $res ."\n"; + print "os-name " .$res2 . "\n"; + print "host-name ". $res3. "\n"; + $jnx->disconnect(); +``` + +Output: +Run netconf-perl script: +``` +perl +``` +``` +priyal@priyal:~/Desktop$ perl get_system_information.pl +Enter hostname: device1 +Enter username: foo +Enter password: passwd123 + +Server request: + + + + + Server response: + + +olive +junos +15.2I20151211 +device1 + + + +hardware information: olive +os-name: junos +host-name: device1 +``` + Troubleshooting (Ubuntu12.04LTS or higher version) ================= @@ -215,13 +245,36 @@ Troubleshooting (Ubuntu12.04LTS or higher version) Sometimes you may get error like "Checking for ability to link against xml2...no " while installing LibXML then use this command sudo apt-get install zlib1g-dev + + 2. For error related to Net::SSH2 + Sometimes you may get error like, + fatal error: openssl/crypto.h: No such file or directory + #include + ^ + compilation terminated. + make: *** [SSH2.o] Error 1 + then use then command: + sudo apt-get install libssl-dev - 2. For YAML related Errors + 3. For YAML related Errors Sometimes you may get that yaml is not installed then run following commands: apt-get install libyaml-appconfig-perl apt-get install libconfig-yaml-perl + + 3. For libz errors like: + /usr/bin/ld: /usr/local/lib/libz.a(crc32.o): relocation R_X86_64_32 against `.rodata' can not be used when + making a shared object; recompile with -fPIC + /usr/local/lib/libz.a: could not read symbols: Bad value + try installing from source code: + download libz source code, https://google-desktop-for-linux-mirror.googlecode.com/files/zlib-1.2.3.tar.gz + according to platform u are using. + open Makefile and add -fPIC in cflag, (smth like: CFLAGS=-O3 -DUSE_MMAP -fPIC) + then compile it: + ./configure + make test + make install - 3. For cpan related errors: + 4. For cpan related errors: While installing some files using cpan, for example "cpan File::Which" you may get error like checksum mismatch for distribution, then try installing that package using "cpanm" First install cpanm in your system by: @@ -234,7 +287,6 @@ Troubleshooting (Ubuntu12.04LTS or higher version) you can also try installing by force : apt-get -f install - # CONTRIBUTORS - [Ganesh Nalawade](https://github.com/ganeshnalawade) diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..734e677 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,12 @@ +FROM ubuntu:14.04 + +RUN apt-get update && apt-get install -y perl libssh2-1-dev zlib1g-dev libyaml-appconfig-perl \ + libconfig-yaml-perl make libxml2 libxml2-dev libnet-ssh2-perl libfile-which-perl libxml-libxml-perl + +COPY . /src + +RUN cd /src && perl Makefile.PL && make && make install + +WORKDIR /scripts + +ENTRYPOINT ["/bin/bash"] diff --git a/docker/Makefile b/docker/Makefile new file mode 100644 index 0000000..6ecba1c --- /dev/null +++ b/docker/Makefile @@ -0,0 +1,18 @@ +CONTAINER=netconf-perl +#HUB= + +all: build + +build: Dockerfile + cd .. && docker build -f docker/Dockerfile -t $(CONTAINER) . + +run: + docker rm $(CONTAINER) || true + docker run -ti --rm --name $(CONTAINER) $(CONTAINER) || true + +#push: +# docker tag $(CONTAINER) $(HUB)/$(CONTAINER) +# docker push $(HUB)/$(CONTAINER) + +clean: + docker rmi `docker images | grep "^" | awk '{print $$3}'` 2>/dev/null || true diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..d262e5a --- /dev/null +++ b/docker/README.md @@ -0,0 +1,71 @@ +## Docker Container with NETCONF Perl client + +A small, ubuntu based, container with a working netconf client can be built from this directory with + +``` +make +``` + +Once ready, launch it and test the sample apps. The option '-v $PWD:/scripts' mounts +optionally the current directory into the container, allowing read-write access to files. + +``` +$ docker images | head -2 +REPOSITORY TAG IMAGE ID CREATED SIZE +netconf-perl latest 8dca418c8bd9 6 minutes ago 298.2 MB + +$ make run +docker run -ti --rm -v $PWD:/scripts --name netconf-perl netconf-perl +root@cae70c23b993:/scripts# perl /src/examples/get_system_information.pl + +Options: + +hostname : hostname of target router +username : A login name accepted by the target router. +password : The password for the login name + + hostname: 172.17.0.82 + + username: lab + + password: lab123 + Connection established: 14410 + Server request is : + + + + + Server response is: + + +vmx +junos +16.1R3.10 +VM586D46B4DE +vmxt2 + + + + +Rpc reply from server. +>>>>>>>>>> +<<<<<<<<<< + +using XML::LibXMl::DOM function +inside dom object +hardware information vmx +os-name junos +host-name vmxt2 + +using xpath +inside dom object +hardware information vmx +os-name junos +host-name vmxt2 +root@cae70c23b993:/src# exit +``` + + + + + diff --git a/examples/collect_show_interface.pl b/examples/collect_show_interface.pl new file mode 100644 index 0000000..3dc250a --- /dev/null +++ b/examples/collect_show_interface.pl @@ -0,0 +1,71 @@ +#!/usr/bin/perl + +use Net::Netconf::Manager; +use warnings; +use strict; +use XML::XPath; + +my $hostname = "10.254.254.100"; +my $login= "lab"; +my $pass = "lab123"; + +my $jnx = new Net::Netconf::Manager( 'access' => 'ssh', + 'login' => $login, + 'password' => $pass, + 'hostname' => $hostname); + +if(! $jnx ) { + print STDERR "Unable to connect to Junos device \n"; + exit 1; +} + +print "Connection established: " . $jnx->get_session_id . "\n"; + +my $query = "get_interface_information"; + +my %queryargs = ( 'interface-name' => 'ge-0/0/0' ); + +my $reply=$jnx->$query(%queryargs); + +if ($jnx->has_error) { + print "ERROR: in processing request\n"; + # Get the error + my $error = $jnx->get_first_error(); + $jnx->print_error_info(%$error); + exit 1; +} + +open(FILEOUTPUT, ">interface_output.xml"); + +print FILEOUTPUT $jnx->{'server_response'}; + +close(FILEOUTPUT); + +# this parsing is specifically for tag +# you can write your own application in similar way +# parsing reply from server + +print "\n\n\n"; +print "Example using XML::XPath\n----------------------------------------------\n\n"; +my $interfaceOutput = XML::XPath->new(filename => 'interface_output.xml'); + +my $interfaceDescription = $interfaceOutput->find('/rpc-reply/interface-information/physical-interface/description'); +my $interfaceIPv4Address = $interfaceOutput->find('/rpc-reply/interface-information/physical-interface/logical-interface/address-family/interface-address/ifa-local'); + +# Parsing using XML::XPath + +print "Interface description = $interfaceDescription\n"; +print "Interface address = $interfaceIPv4Address\n"; + +print "\n\n\n\n"; + +print "Example using DOM\n----------------------------------------------\n\n"; +my $XMLOutput= $jnx->get_dom(); +my $res= $XMLOutput->getElementsByTagName("description")->item(0)->getFirstChild->getData; +my $res2= $XMLOutput->getElementsByTagName("ifa-local")->item(0)->getFirstChild->getData; +print "\n"; +print "DOM - Interface description: " . $res . "\n"; +print "DOM - Interface address ". $res2 . "\n"; +print "\n"; + +$jnx->disconnect(); diff --git a/examples/Diaganose_bgp/bgp.xml b/examples/diagnose_bgp/bgp.xml similarity index 100% rename from examples/Diaganose_bgp/bgp.xml rename to examples/diagnose_bgp/bgp.xml diff --git a/examples/Diaganose_bgp/diagnose_bgp.pl b/examples/diagnose_bgp/diagnose_bgp.pl similarity index 100% rename from examples/Diaganose_bgp/diagnose_bgp.pl rename to examples/diagnose_bgp/diagnose_bgp.pl diff --git a/examples/chassis_inventory/chassis_inventory.xml b/examples/get_chassis_inventory/chassis_inventory.xml similarity index 100% rename from examples/chassis_inventory/chassis_inventory.xml rename to examples/get_chassis_inventory/chassis_inventory.xml diff --git a/examples/chassis_inventory/get_chassis_inventory.pl b/examples/get_chassis_inventory/get_chassis_inventory.pl similarity index 99% rename from examples/chassis_inventory/get_chassis_inventory.pl rename to examples/get_chassis_inventory/get_chassis_inventory.pl index 285a28f..a0fdbd0 100755 --- a/examples/chassis_inventory/get_chassis_inventory.pl +++ b/examples/get_chassis_inventory/get_chassis_inventory.pl @@ -142,7 +142,7 @@ sub print_response } my $query = "get_chassis_inventory"; -my %queryargs = ( 'detail' => 1 ); +my %queryargs = ( 'detail' => 'True' ); # send the command and get the server response my $res = $jnx->$query(%queryargs); diff --git a/examples/get_system_information.pl b/examples/get_system_information.pl index 9023eac..3e7f74b 100644 --- a/examples/get_system_information.pl +++ b/examples/get_system_information.pl @@ -51,7 +51,8 @@ sub output_usage $jnx = new Net::Netconf::Manager( 'access' => 'ssh', 'login' => $login, 'password' => $pass, - 'hostname' => $hostname); + 'hostname' => $hostname, + 'port' => 22); if(! $jnx ) { print STDERR "Unable to connect to Junos device \n"; diff --git a/lib/Net/Netconf.pm b/lib/Net/Netconf.pm index 8abe0e7..a1f8bdc 100644 --- a/lib/Net/Netconf.pm +++ b/lib/Net/Netconf.pm @@ -1,5 +1,5 @@ package Net::Netconf; -our $VERSION ='0.01'; +our $VERSION ='1.05'; =head1 NAME Net::Netconf : Netconf Perl Client diff --git a/lib/Net/Netconf/Access.pm b/lib/Net/Netconf/Access.pm index fef07e3..f3ee9ad 100644 --- a/lib/Net/Netconf/Access.pm +++ b/lib/Net/Netconf/Access.pm @@ -3,7 +3,7 @@ package Net::Netconf::Access; use strict; use Net::Netconf::Trace; use Carp; -our $VERSION ='0.01'; +our $VERSION ='1.02'; sub new { diff --git a/lib/Net/Netconf/Access/ssh.pm b/lib/Net/Netconf/Access/ssh.pm index f9ae399..24b6018 100644 --- a/lib/Net/Netconf/Access/ssh.pm +++ b/lib/Net/Netconf/Access/ssh.pm @@ -1,143 +1,149 @@ -# child of Access - package Net::Netconf::Access::ssh; -use Expect; +use Net::SSH2; use Net::Netconf::Trace; -use Net::Netconf::Access; use Net::Netconf::Constants; use Carp; -use File::Which; -our $VERSION ='0.01'; -use vars qw(@ISA); -@ISA = qw(Net::Netconf::Access); +use parent 'Net::Netconf::Access'; + +our $VERSION = '1.02'; -sub disconnect -{ - my ($self) = shift; - $self->{'ssh_obj'}->hard_close(); +# Just for convenience +sub trace { + my $self = shift; + $self->{'trace_obj'}->trace(Net::Netconf::Trace::TRACE_LEVEL, + sprintf("[%s] %s", __PACKAGE__, @_)); } -sub start -{ - my($self) = @_; - my $sshprog; - my $exp; - # Get ssh port number if it exists - my $rport = (getservbyname('ssh', 'tcp'))[2]; - $rport = Net::Netconf::Constants::NC_DEFAULT_PORT unless ( defined $self->{'server'} && $self->{'server'} eq 'junoscript'); +# Initialises an Net::SSH2 object, connects and authenticates to the Netconf +# server. Net::SSH2 object is stored in $self->{'ssh2'}, channel in +# $self->{'chan'}. +sub start { + my $self = shift; + + $self->{'server'} ||= 'netconf'; + + my $port = ($self->{'server'} eq 'netconf') ? + $self->{'port'} || Net::Netconf::Constants::NC_DEFAULT_PORT : + $self->{'port'} || (getservbyname('ssh', 'tcp'))[2]; + + + my $ssh2 = Net::SSH2->new(); + croak "Failed to create a new Net::SSH2 object" unless(ref $ssh2); + + $self->trace("Making SSH connection to '$self->{'hostname'}:$port'..."); + $ssh2->connect($self->{'hostname'}, $port); + croak "SSH connection failed: " . $ssh2->error() if($ssh2->error()); + $self->trace("SSH connection succeeded!"); + + $self->trace("Performing SSH authentication"); + $ssh2->auth(username => $self->{'login'}, + password => $self->{'password'}); + croak "SSH authentication failed" if(!$ssh2->auth_ok() or $ssh2->error()); + $self->trace("Authentication succeeded!"); - $self->{'server'} = 'netconf' unless $self->{'server'}; + $self->trace("Requesting SSH channel..."); + my $chan = $ssh2->channel(); + croak "Failed to create SSH channel" if(!ref $chan or $ssh2->error()); + $self->trace("Successfully created SSH channel!"); - my $echostate = 'stty -echo;'; - if (exists($self->{'sshprog'})) { - $sshprog = $self->{'sshprog'}; + $self->trace("Starting subsystem '$self->{'server'}'..."); + my $subsystem = $chan->subsystem($self->{'server'}); + if(!$subsystem) { + $self->trace("Failed to start '$self->{'server'}' subsystem, trying to exec"); + $chan->exec($self->{'server'}) + or croak "Failed to exec ". $self->{'server'}; + $chan->flush(); + $self->trace("Started server '$self->{'server'}' in exec"); } else { - $sshprog = which('ssh'); - if (defined($sshprog) && ($sshprog ne '')) { - chomp($sshprog); - } else { - croak "Could not find sshclient on the system"; - } + $self->trace("Successfully started subsystem!"); } - # This implementation assumes OpenSSH. - my $command = $echostate . $sshprog . ' -l ' . - $self->{'login'} . ' -p ' . $rport . - ' -s ' . $self->{'hostname'} . - ' ' . $self->{'server'}; + $self->{'ssh2'} = $ssh2; + $self->{'chan'} = $chan; + return $self; +} - - # take expect object from user ow build your own - if (defined $self->{'exp_obj'}){ - $exp = $self->{'exp_obj'}; - } - else{ - $exp = new Expect; - } - my $ssh=$exp->spawn($command); - - # Create the Expect object - # my $ssh = Expect->spawn($command); - # Turn off logging to stdout - $ssh->log_stdout(0); - $ssh->log_file($self->out); - - # Send our password or passphrase - if ($ssh->expect(10, 'password:', 'Password:', '(yes/no)?', '-re', 'passphrase.*:')) { - my $m_num = $ssh->match_number(); - - SWITCH: { - if (($m_num == 1) || ($m_num == 2) || ($m_num == 4)) { - print $ssh "$self->{'password'}\r"; - last SWITCH; - } - if ($m_num == 3) { - # Host-key authenticity. - print $ssh "yes\r"; - if ($ssh->expect(10, 'password:', 'Password:', '-re', 'passphrase.*:')) { - print $ssh "$self->{'password'}\r"; - } - # After the yes/no option, expect the line: 'Warning: - # Permanently added <....> to the list of known hosts.' - $ssh->expect(10, 'known hosts.'); - last SWITCH; - } - } # SWITCH - # If password prompted second time, it means user has give invalid password - if ($ssh->expect(10, 'password:', 'Password:', '-re', 'passphrase.*:')) - { - print "Failed to login to $self->{'hostname'}\n"; - $self->{'seen_eof'} = 1; - } - } - else { - if ($ssh->expect(10, '-re', '')) { - # Things are good. Do nothing. - } else { - $self->{'seen_eof'} = 1; - } - } +# Gracefully disconnect from Netconf server +sub disconnect { + my $self = shift; + + my $ssh2 = $self->{'ssh2'}; + $ssh2->disconnect("Bye bye from $0 [" . __PACKAGE__ . " v$VERSION]"); + $self->trace("Disconnected from SSH server"); - $self->{'ssh_obj'} = $ssh; - $self; + undef $self; } -sub send -{ +# Writes an XML request to the Netconf server. +sub send { my ($self, $xml) = @_; - my $ssh = $self->{'ssh_obj'}; + $xml .= ']]>]]>'; - print $ssh "$xml\r"; - 1; -} -sub recv -{ - my $self = shift; - my $xml; - my $ssh = $self->{'ssh_obj'}; - if ($ssh->expect(600, ']]>]]>')) { - $xml = $ssh->before() . $ssh->match(); - } else { - print "Failed to login to $self->{'hostname'}\n"; - $self->{'seen_eof'} = 1; + my $len = length($xml); + + $self->trace("Will write $len bytes to the SSH channel:"); + $self->trace("$xml"); + + # Make the channel blocking, so the write() call below waits until there + # is available buffer space. Otherwise we'll end up busy-looping. + $self->{'chan'}->blocking(1); + + my $written = 0; + while($written != $len) { + my $nbytes = $self->{'chan'}->write($xml) + or croak "Failed to write XML data to SSH channel!"; + $written += $nbytes; + $self->trace("Wrote $nbytes bytes (total written: $written)."); + substr($xml, 0, $nbytes) = ''; } - $xml =~ s/]]>]]>//g; - $xml; + $self->trace("Successfully wrote $written bytes to SSH channel!"); + return 1; } -sub out -{ - my $self = @_; - foreach $line (@_) { - if ($line =~ /Permission\ denied/) { - print "Login failed: Permission Denied\n"; - $self->{'ssh_obj'}->hard_close(); - $self->{'seen_eof'} = 1; +# Reads an XML reply from the Netconf server. +sub recv { + my $self = shift; + my $ssh2 = $self->{'ssh2'}; + my $chan = $self->{'chan'}; + + # Make the channel non-blocking, so that read() below allows for partial + # reads (as we can't possibly know if the data we're about to receive is an + # exact multiple of the buffer size argument, and doing it one character at + # a time instead would be terribly inefficient). + $chan->blocking(0); + + $self->trace("Reading XML response from Netconf server..."); + my ($resp, $buf); + my $end_time = time() + 15; + do { + # Wait up to 10 seconds for data to become available before attempting + # to read anything (in order to avoid busy-looping on $chan->read()) + my @poll = ({ handle => $chan, events => 'in' }); + $ssh2->poll(40000, \@poll); + + $nbytes = $chan->read($buf, 65536); + + if (!defined $nbytes || time() > $end_time) { + croak "Failed to read XML data from SSH channel!"; } - } + if($nbytes > 0){ + $end_time = time() + 15; + } + $self->trace("Read $nbytes bytes from SSH channel: '$buf'"); + $resp .= $buf; + } until($resp =~ s/]]>]]>$//); + $self->trace("Received XML response '$resp'"); + + return $resp; +} + +# Checks if the server sent us an EOF +sub eof { + my $self = shift; + return $self->{'chan'}->eof(); } 1; @@ -150,14 +156,14 @@ Net::Netconf::Access::ssh =head1 SYNOPSIS -The Net::Netconf::Access::ssh module is used internally to provide ssh access to -a Net::Netconf::Access instance. +The Net::Netconf::Access::ssh module is used internally to provide SSH access to +a Net::Netconf::Access instance, using Net::SSH2. =head1 DESCRIPTION -This is a subclass of Net::Netconf::Access class that manages an ssh connection -with the destination host. The underlying mechanics for managing the ssh -connection is based on OpenSSH. +This is a subclass of Net::Netconf::Access class that manages an SSH connection +with the destination host. The underlying mechanics for managing the SSH +connection is based on Net::SSH2. =head1 CONSTRUCTOR @@ -171,7 +177,11 @@ Please refer to the constructor of Net::Netconf::Access class. =item * -Expect.pm +Net::SSH2 + +=item * + +Net::SSH2::Channel =item * @@ -189,6 +199,9 @@ Net::Netconf::Device =head1 AUTHOR -Juniper Networks Perl Team, send bug reports, hints, tips and suggestions to -netconf-support@juniper.net. +Tore Anderson + +Net::Netconf is maintained by the Juniper Networks Perl Team, send bug reports, +hints, tips and suggestions to netconf-support@juniper.net. +# vim: syntax=perl tw=80 ts=4 et: diff --git a/lib/Net/Netconf/Constants.pm b/lib/Net/Netconf/Constants.pm index 139ea2a..5d3de46 100644 --- a/lib/Net/Netconf/Constants.pm +++ b/lib/Net/Netconf/Constants.pm @@ -1,6 +1,6 @@ package Net::Netconf::Constants; -our $VERSION ='0.01'; +our $VERSION ='1.02'; # Netconf server: minimum version use constant NC_VERS_MIN => 7.5; # Constants pertaining to the Netconf states diff --git a/lib/Net/Netconf/Device.pm b/lib/Net/Netconf/Device.pm index e908572..e7e53a5 100755 --- a/lib/Net/Netconf/Device.pm +++ b/lib/Net/Netconf/Device.pm @@ -10,6 +10,7 @@ use XML::LibXML::SAX; use XML::LibXML::SAX::Parser; use Net::Netconf::SAXHandler; use File::Basename; + use Carp; no strict 'subs'; require Exporter; @@ -18,7 +19,7 @@ use vars qw(@EXPORT); use vars qw(@ISA); use vars qw($AUTOLOAD); -our $VERSION ='0.01'; +our $VERSION ='1.02'; my $NO_ARGS = bless {}, 'NO_ARGS'; my $TOGGLE = bless { 1 => 1 }, 'TOGGLE'; my $TOGGLE_NO = bless {}, 'TOGGLE'; @@ -126,6 +127,7 @@ sub new my $saxparser = new XML::LibXML::SAX::Parser('Handler' => $handler) || croak 'Cannot create parser: ' . $!; $self->{'sax_parser'} = $saxparser; + $self->{'handler'} = $handler; # DOM parser my $domparser = XML::LibXML->new(); @@ -145,6 +147,8 @@ sub new # Create the trace object $self->{'trace_obj'} = new Net::Netconf::Trace($self->{'debug_level'}); + $self->{'methods'} = \%methods; + # Now bring up the connection return $self->connect() unless $self->{'do_not_connect'}; $self; @@ -154,8 +158,8 @@ sub new sub connect { my($self) = @_; - my $conn; - my $trace_obj = $self->{'trace_obj'}; + my $conn; + my $trace_obj = $self->{'trace_obj'}; $trace_obj->trace(Net::Netconf::Trace::TRACE_LEVEL, 'preparing to connect'); # If we are already connected, we have the connection object @@ -220,6 +224,7 @@ EOF EOF + # Send our capabilities to the Netconf server # Get the server capability my $server_cap = $self->send_and_recv_rpc($client_capability, @@ -244,17 +249,19 @@ EOF }; if ($@) { - $self->report_error(1, 'error in parsing server capability'); - } + $self->report_error(0, 'error in parsing server capability'); + $self->disconnect(); + return; + } # Now save the session-id and server capabilities - $self->{'session_id'} = $Net::Netconf::SAXHandler::session_id; - @{$self->{'server_capabilities'}} = @Net::Netconf::SAXHandler::parsed_cap; + $self->{'session_id'} = $self->{'handler'}->{'session_id'}; + @{$self->{'server_capabilities'}} = @{$self->{'handler'}->{'parsed_cap'}}; # See if any errors were emitted and save them - $self->{'found_rpc_errors'} = $Net::Netconf::SAXHandler::found_error; + $self->{'found_rpc_errors'} = $self->{'handler'}->{'found_error'}; if ($self->{'found_rpc_errors'}) { - %{$self->{'rpc_errors'}} = (%Net::Netconf::SAXHandler::rpc_errors); + %{$self->{'rpc_errors'}} = (%{$self->{'handler'}->{'rpc_errors'}}); } $self; } @@ -272,8 +279,10 @@ sub disconnect { my($self) = @_; my $conn = $self->{'conn_obj'}; + #carp "Disconnecting\n"; $conn->disconnect if ($conn and $self->{'conn_state'} != Net::Netconf::Constants::NC_STATE_DISCONN && not $conn->eof); + $self->{'conn_obj'} = undef; } # Helper function for sending and receiving Netconf commands and responses @@ -283,7 +292,7 @@ sub disconnect # $state_recv = state after we receive data (optional) sub send_and_recv_rpc { -my($self, $xml, $endtag, $state_sent, $state_recv) = @_; + my($self, $xml, $endtag, $state_sent, $state_recv) = @_; return unless ($xml); my $conn = $self->{'conn_obj'}; my $traceobj = $self->{'trace_obj'}; @@ -297,16 +306,30 @@ my($self, $xml, $endtag, $state_sent, $state_recv) = @_; $endtag = Net::Netconf::Constants::NC_REPLY_TAG unless ($endtag); # Send the request to the Netconf server - unless ($conn->send($xml)) { - carp 'failed to send user request'; - return; + eval { + unless ($conn->send($xml)) { + carp 'failed to send user request'; + return; + }; }; + if ($@) { + $self->report_error(0, 'connection to Netconf server lost'); + $self->disconnect(); + return undef; + } $self->{'conn_state'} = $state_sent; # Get a response from the Netconf server $in = $self->read_rpc( $endtag ); - $self->parse_response( $in ); + if (!defined $in) { + carp 'failed to recv user response'; + $self->report_error(0, 'connection to Netconf server lost'); + $self->disconnect(); + return undef; + } + + $self->parse_response( $in ); } # Helper function to read RPC response until end tag @@ -321,20 +344,30 @@ sub read_rpc while ( $self->{'conn_state'} != Net::Netconf::Constants::NC_STATE_HELLO_RECVD ) { if ($conn->eof) { - $self->report_error(1, 'connection to Netconf server lost'); + $self->report_error(0, 'connection to Netconf server lost'); + $self->disconnect(); return undef; } - $in .= $conn->recv(); + eval { + $in .= $conn->recv(); + }; + if ($@) { + $self->report_error(0, 'connection to Netconf server lost'); + $self->disconnect(); + return undef; + } + # Check to see if you received the end-tag if ($in =~ /<\/\s*$endtag\s*>/gs) { - $self->{'conn_state'} = Net::Netconf::Constants::NC_STATE_HELLO_RECVD; + $self->{'conn_state'} = Net::Netconf::Constants::NC_STATE_HELLO_RECVD; } elsif ($conn->eof) { - $self->report_error(1, 'connection to Netconf server lost'); - return undef; + $self->report_error(0, 'connection to Netconf server lost'); + $self->disconnect(); + return undef; } } @@ -362,10 +395,10 @@ sub parse_response # Save the total number of s received and error information - $self->{'found_rpc_errors'} = $Net::Netconf::SAXHandler::found_error; + $self->{'found_rpc_errors'} = $self->{'handler'}->{'found_error'}; #Net::Netconf::SAXHandler::found_error; # found_error an attribute of SAXHandler - $self->{'no_error'} = $Net::Netconf::SAXHandler::no_error; + $self->{'no_error'} = $self->{'handler'}->{'no_error'};#Net::Netconf::SAXHandler::no_error; #no_error an attribute of SAXHandler # We should not get both and @@ -376,7 +409,7 @@ sub parse_response #} if ($self->{'found_rpc_errors'}) { - %{$self->{'rpc_errors'}} = (%Net::Netconf::SAXHandler::rpc_errors); + %{$self->{'rpc_errors'}} = (%{$self->{'handler'}->{'rpc_errors'}});#Net::Netconf::SAXHandler::rpc_errors); } # Then we return the response as a string return $self->{'server_response'}; @@ -386,7 +419,7 @@ sub parse_response # This is useful for and operations sub get_dom { - print "inside dom object"; + #print "inside dom object"; my($self) = @_; # Create a DOM object and return that. # Server response is in: $self->{server_response} @@ -491,7 +524,7 @@ sub generate_rpc ($tag = $field) =~ s/_/-/g; - if (ref($type) eq 'TOGGLE' || ref($value) eq 'TOGGLE') { + if (ref($type) eq 'TOGGLE' || ref($value) eq 'TOGGLE' || $value eq 'True') { if ($value ne '0') { $output .= " <$tag/>\n"; } @@ -530,7 +563,7 @@ sub generate_rpc $output .= "\n"; } else { - $output .= " <${tag}>${value}\n"; + $output .= " <${field}>${value}\n"; } } if ($bindings->{'tag_name'}) { @@ -572,7 +605,7 @@ sub get_config $request .= $args{'source'}; $request .= '/> '; } - $request .= $args{filter}; + $request .= $args{'filter'}; $request .= ' '; $self->send_and_recv_rpc($request); } diff --git a/lib/Net/Netconf/EzEditXML.pm b/lib/Net/Netconf/EzEditXML.pm index 9be2940..d11cae2 100644 --- a/lib/Net/Netconf/EzEditXML.pm +++ b/lib/Net/Netconf/EzEditXML.pm @@ -35,7 +35,7 @@ use XML::LibXML; require Exporter; @ISA = qw(Exporter); -our $VERSION ='0.01'; +our $VERSION ='1.02'; =head1 METHODS =cut diff --git a/lib/Net/Netconf/Manager.pm b/lib/Net/Netconf/Manager.pm index e5970ab..d0d1b4d 100644 --- a/lib/Net/Netconf/Manager.pm +++ b/lib/Net/Netconf/Manager.pm @@ -1,7 +1,7 @@ package Net::Netconf::Manager; use Carp; -our $VERSION ='0.01'; +our $VERSION ='1.02'; # This instantiates a Junoscript or a Netconf device depending on the 'server' # specified. Default is Netconf. diff --git a/lib/Net/Netconf/SAXHandler.pm b/lib/Net/Netconf/SAXHandler.pm index d813234..60147ec 100644 --- a/lib/Net/Netconf/SAXHandler.pm +++ b/lib/Net/Netconf/SAXHandler.pm @@ -2,19 +2,10 @@ package Net::Netconf::SAXHandler; use strict; use Carp; -our $VERSION ='0.01'; - -use vars qw(@EXPORT_OK @parsed_cap $session_id $found_error $no_error -%rpc_errors $junos_version); -require Exporter; -@EXPORT_OK = qw(@parsed_cap $session_id $found_error $no_error %rpc_errors -$junos_version); +our $VERSION ='1.02'; use base qw(XML::SAX::Base); -$found_error = 0; -$no_error = 0; - sub attlist_decl { my $self = shift; @@ -52,19 +43,19 @@ sub start_element } elsif ($self->{'get_pkg'} && ($data->{'LocalName'} eq 'comment')) { $self->{'get_junos_ver'} = 1; } elsif ($data->{'LocalName'} eq 'rpc-reply') { - $found_error = 0; - $no_error = 0; - %rpc_errors = (); + $self->{'found_error'} = 0; + $self->{'no_error'} = 0; + $self->{'rpc_errors'} = (); } elsif ($data->{'LocalName'} eq 'capability') { $self->{'add_capability'} = 1; } elsif (($data->{'LocalName'} eq 'session-id') && ($self->{'seen_hello'})) { $self->{'get_session_id'} = 1; } elsif ($data->{'LocalName'} eq 'rpc-error') { - $found_error++; + $self->{'found_error'}++; $self->{'get_error'} = 1; } elsif ($data->{'LocalName'} eq 'ok') { - $no_error = 1; + $self->{'no_error'} = 1; } elsif ($self->{'get_error'}) { # Insert this field into the hash $self->{'capture_error'} = $data->{'LocalName'}; @@ -77,7 +68,7 @@ sub end_element my ($self, $data) = @_; if ($data->{'LocalName'} eq 'capability') { if ($self->{'current_cap'}) { - push @parsed_cap, $self->{'current_cap'}; + push @{$self->{'parsed_cap'}}, $self->{'current_cap'}; undef $self->{'current_cap'}; } $self->{'add_capability'} = 0; @@ -106,19 +97,19 @@ sub characters $self->{'current_cap'} .= $capability; } elsif ($self->{'get_session_id'}) { if ($data->{'Data'} =~ /\S/) { - $session_id = $data->{'Data'}; + $self->{'session_id'} = $data->{'Data'}; } } elsif ($self->{'get_pkg'} && $self->{'get_junos_ver'}) { if ($data->{'Data'} =~ /JUNOS Base OS/) { my @comment; @comment = split(/\[/, $data->{'Data'}); - $junos_version = $comment[1]; - $junos_version = substr($junos_version, 0, 3); + $self->{'junos_version'} = $comment[1]; + $self->{'junos_version'} = substr($self->{'junos_version'}, 0, 3); } } elsif ($self->{'get_error'}) { #Get the error value if ($data->{'Data'} =~ /\S/) { $self->{'capture_error'} =~ s/-/_/gs; - $rpc_errors{$found_error}{$self->{'capture_error'}}=$data->{'Data'}; + $self->{'rpc_errors'}{$self->{'found_error'}}{$self->{'capture_error'}}=$data->{'Data'}; } } $self->SUPER::characters($data); diff --git a/lib/Net/Netconf/Trace.pm b/lib/Net/Netconf/Trace.pm index 3529236..4492c54 100644 --- a/lib/Net/Netconf/Trace.pm +++ b/lib/Net/Netconf/Trace.pm @@ -2,7 +2,7 @@ package Net::Netconf::Trace; use strict; use Carp; -our $VERSION ='0.01'; +our $VERSION ='1.02'; use constant DEBUG_LEVEL => 1; use constant TRACE_LEVEL => 2; use constant INFO_LEVEL => 3; diff --git a/perl-Net-Netconf.spec b/perl-Net-Netconf.spec new file mode 100644 index 0000000..5896043 --- /dev/null +++ b/perl-Net-Netconf.spec @@ -0,0 +1,45 @@ +Summary: perl-Net-Netconf +Name: perl-Net-Netconf +Version: 1.05 +Release: 1%{?dist} +License: Apache +Group: GRNOC +URL: https://github.com/Juniper/netconf-perl +Source: Net-Netconf-%{version}.tar.gz + +BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) +BuildRequires: perl +Requires: perl-Net-SSH2 +Requires: perl-File-Which +Requires: perl-XML-LibXML + +%description +Netconf library for JUNOS devices + +%prep +%setup -q -n Net-Netconf-%{version} + +%build +%{__perl} Makefile.PL PREFIX="%{buildroot}%{_prefix}" INSTALLDIRS="vendor" +make + +%install +rm -rf $RPM_BUILD_ROOT + +%{__install} -d -p %{buildroot}%{perl_vendorlib}/Net +cp -ar lib/Net/* %{buildroot}%{perl_vendorlib}/Net + +%clean +rm -rf $RPM_BUILD_ROOT + + +%files +%{perl_vendorlib}/Net + +%doc + + +%changelog +* Thu Jun 29 2017 Jonathan Stout - Net-Netconf +- Initial build. +