├── LICENSE
├── README.md
├── arista
├── README.md
├── bgp
│ ├── bgp_evpn_report.py
│ └── ip_bgp_report.py
├── interface
│ ├── __pycache__
│ │ ├── arista_arp_details.cpython-311.pyc
│ │ ├── arista_interface_detail.cpython-311.pyc
│ │ └── arista_interface_status.cpython-311.pyc
│ ├── arista_arp_details.py
│ ├── arista_interface_detail.py
│ └── arista_interface_status.py
├── ip
│ ├── __pycache__
│ │ └── ip_bgp_report.cpython-311.pyc
│ └── ip_bgp_report.py
└── plugins
│ └── main_arista.py
├── cisco-nx
├── README.md
├── ip
│ ├── ip_bgp_report.py
│ ├── ip_interface_report.py
│ └── ip_route_report.py
├── mac
│ └── mac_address_table_report.py
└── plugins
│ ├── Cisco_nxos_lldp_neighbor
│ ├── ip_reports.py
│ └── mac_reports.py
├── juniper
├── README.md
├── eth_switch
│ └── ethernet_switching_table_report.py
└── plugins
│ ├── ethernet_switching_reports.py
│ └── show_interfaces.py
├── lab
├── README.md
├── evpn-mh-topology.jpg
└── evpn
│ ├── leaf1-1-startup.cfg
│ ├── leaf1-2-startup.cfg
│ ├── leaf2-1-startup.cfg
│ ├── leaf2-2-startup.cfg
│ └── spine-startup.cfg
├── nokia
├── README.md
├── bgp
│ └── sros_bgpsummary.py
├── evpn
│ └── evpn_report.py
└── plugins
│ ├── service_report.py
│ ├── sros_bgp_report.py
│ └── sros_router_report.py
└── srl-evpn-mh.clab.yml
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MultiCLI
2 |
3 | Welcome to the MultiCLI project for Nokia SR Linux.
4 |
5 | [SR Linux](https://learn.srlinux.dev/) is the industry's most modern Network Operating System (NOS) enabling unmatched automation and programmability features.
6 |
7 | One of its capabilities is the ability to customize the CLI on SR Linux.
8 |
9 | All SR Linux CLI show commands shipped with the software are written in executable python scripts leveraging the state yang models.
10 |
11 | Users are allowed to take those python scripts, modify them to fit their use case or build a brand new CLI command leveraging the same state models.
12 |
13 | These customized CLI scripts are called **Custom CLI plugins** in SR Linux.
14 |
15 | Since everything is modeled in yang from the ground up, this allows the user to use CLI to access any state object or attribute in the system and display it in the format they are familiar with.
16 |
17 | Custom CLI commands also provide the same auto-completion and help features that come with native SR Linux commands.
18 |
19 | So how does this benefit the user? There are few use cases that we could think of like:
20 | - Use the same MOP and command set that you previously used for another vendor
21 | - Use the same automation or monitoring scripts that was written for another vendor
22 | - allow users to start using SR Linux using the same commands they used for another vendor
23 |
24 | All these are CLI heavy use cases. You may have more use cases in your mind.
25 |
26 | This is awesome! But how do i put this into action?
27 |
28 | ## What is MultiCLI about?
29 |
30 | The mission of MultiCLI is to get you started with SR Linux CLI customization feature for your environment.
31 |
32 | As part of this project, we are publishing custom CLI plugins for `show` commands that will match the command and output of our friends in the industry.
33 |
34 | The user community is free to take these scripts, use them as is or modify them based on their end goal. We will also be happy to accept new contributions from the community.
35 |
36 | ## MultiCLI Plugins
37 |
38 | This repo is structured based on the vendor that we try to match in SR Linux CLI.
39 |
40 | In this initial release, we have scripts for:
41 |
42 | - [Arista EOS](arista/)
43 | - [Cisco NX-OS](cisco-nx/)
44 | - [Juniper JUNOS](juniper/)
45 | - [Nokia SR OS](nokia/)
46 |
47 | ## Learn how to customize SR Linux CLI
48 |
49 | For those intereted in learning the process of customizing the SR Linux CLI, refer to the official [SR Linux CLI plug-in guide](https://documentation.nokia.com/srlinux/24-10/title/cli_plugin.html).
50 |
51 | For practical experience, start by using the beginner SReXperts hackathon use case [here](https://github.com/nokia/SReXperts/tree/main/hackathon/activities/srlinux-i-cli-plugin-show-version) following by an intermediate use case [here](https://github.com/nokia/SReXperts/tree/main/hackathon/activities/srlinux-i-custom-cli).
52 |
53 | Also, check out these blogs on SR Linux CLI customization:
54 |
55 | [Learn SRLinux](https://learn.srlinux.dev/cli/plugins/getting-started/)
56 |
57 | [Blog by Alperen](https://networkcloudandeverything.com/programming-a-custom-show-command-with-sr-linux-cli-plugin/)
58 |
59 | ## Test Lab
60 |
61 | With the power of [Containerlab](https://containerlab.dev/) and the free SR Linux docker image, testing these custom CLI commands is a simple process. This repo contains a [lab](lab/) topology with startup configs where these custom commands can be tested.
62 |
63 | To test these scripts:
64 | - Clone this repo to your host or use codespaces
65 | - Deploy the EVPN lab or MPLS lab (coming soon)
66 | - All custom CLI plugin files are bound to the nodes using the `bind` function in the topology file
67 | - Each node is configured with 4 custom users:
68 |
69 | | User | Password | NOS |
70 | |------|----------|-----|
71 | | auser | auser | Arista EOS commands |
72 | | cnxuser | cnxuser | Cisco NX-OS commands |
73 | | juser | juser | Juniper JUNOS commands |
74 | | nokuser | nokuser | Nokia SR OS commands |
75 |
76 | - Each of the above user's directory is loaded with the custom cli plugin files for that NOS.
77 | - Login to any leaf or spine node using any of the 4 usernames to try these commands.
78 |
79 | For example, to try NX-OS plugins, login to any leaf or spine nodes using `cnxuser/cnxuser` and try the supported NX-OS commands.
80 |
81 | ---
82 |
83 |
84 | 
85 |
86 | **[Run](https://codespaces.new/srl-labs/multicli?quickstart=1) this lab in GitHub Codespaces for free**.
87 | [Learn more](https://containerlab.dev/manual/codespaces/) about Containerlab for Codespaces.
88 |
89 |
90 |
91 | ---
92 |
93 | To deploy evpn lab:
94 |
95 | Clone this repo:
96 |
97 | ```bash
98 | git clone https://github.com/srl-labs/multicli.git
99 | ```
100 |
101 | Change to the repo directory:
102 |
103 | ```bash
104 | cd multicli
105 | ```
106 |
107 | Deploy the lab:
108 |
109 | ```bash
110 | sudo clab dep -t srl-evpn-mh.clab.yml
111 | ```
112 |
113 | All non-MPLS commands are tested on Nokia 7220 IXR-D2L on SR Linux release 25.3.1.
114 |
115 | ## This is great, but i want more commands supported for my network
116 |
117 | We are inviting contributions from the open source community towards this project.
118 |
119 | Or contact your Nokia Account team to engage Nokia Professional Services.
120 |
121 | ## Resources for further learning
122 |
123 | * [SR Linux documentation](https://documentation.nokia.com/srlinux/)
124 | * [Learn SR Linux](https://learn.srlinux.dev/)
125 | * [YANG Browser](https://yang.srlinux.dev/)
126 | * [gNxI Browser](https://gnxi.srlinux.dev/)
127 |
--------------------------------------------------------------------------------
/arista/README.md:
--------------------------------------------------------------------------------
1 | # Custom CLI Plugins for Arista EOS
2 |
3 | The following CLI plugins are available in this repo:
4 |
5 | > [!NOTE]
6 | > At the time of releasing these scripts, SR Linux did not support a custom CLI path that was loaded on top of an existing native path. Due to this reason, some EOS commands start with the syntax `show eos`. This will be fixed in a future release.
7 |
8 | | Command | Contributor |
9 | |---|---|
10 | | `show eos interface` | [mfzhsn](https://github.com/mfzhsn) |
11 | | `show eos interface status` | [mfzhsn](https://github.com/mfzhsn) |
12 | | `show arp` | [mfzhsn](https://github.com/mfzhsn) |
13 | | `show ip bgp summary` | [sajusal](https://github.com/sajusal) |
14 | | `show bgp evpn route-type auto-discovery` | [sajusal](https://github.com/sajusal) |
15 | | `show bgp evpn route-type mac-ip` | [sajusal](https://github.com/sajusal) |
16 | | `show bgp evpn route-type imet` | [sajusal](https://github.com/sajusal) |
17 | | `show bgp evpn route-type ethernet-segment` | [sajusal](https://github.com/sajusal) |
18 | | `show bgp evpn route-type ip-prefix` | [sajusal](https://github.com/sajusal) |
19 | | `show bgp evpn summary` | [sajusal](https://github.com/sajusal) |
20 |
21 | ## Testing
22 |
23 | Deploy the EVPN lab. Login to any leaf or spine node using `auser/auser` and try any of the above commands.
24 |
25 | ## Custom CLI Plugin scripts
26 |
27 | This folder contains custom CLI python scripts for EOS commands.
28 |
29 | The scripts are arranged in this format. The main_arista.py checks the imports in the shown path below
30 |
31 | ```
32 | /home/auser/cli
33 | ├── bgp
34 | │ └── bgp_evpn_report.py # Handles BGP EVPN Route Type and summary reports
35 | │
36 | ├── interface
37 | │ ├── arista_arp_details.py # Parses and formats ARP entries
38 | │ ├── arista_interface_detail.py # Displays detailed interface info (Arista style)
39 | │ ├── arista_interface_status.py # Displays brief interface status (Arista style)
40 | │
41 | │
42 | ├── ip
43 | │ └── ip_bgp_report.py # Generates standard BGP summary reports
44 | │
45 | └── plugins
46 | ├── main_arista.py # Loads Arista-style CLI plugins
47 |
48 | ```
49 |
50 | ### Verification commands:
51 |
52 | **Interface Status**:
53 | ```
54 | show eos interface status
55 | ```
56 |
57 | **Interface Details**:
58 | ```
59 | show eos interface {interface_name}
60 | ```
61 | *OR, to list all interfaces*
62 |
63 | ```
64 | show eos interface
65 | ```
66 |
67 | **ARP Details**:
68 | ```
69 | show eos arp
70 | ```
71 | OR, to provide optional arguments,
72 |
73 | ```
74 | show eos arp interface {interface_name}
75 | ```
76 |
77 |
--------------------------------------------------------------------------------
/arista/interface/__pycache__/arista_arp_details.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/srl-labs/MultiCLI/6e29987c434184e4c30d8c98e48ead4436a5f126/arista/interface/__pycache__/arista_arp_details.cpython-311.pyc
--------------------------------------------------------------------------------
/arista/interface/__pycache__/arista_interface_detail.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/srl-labs/MultiCLI/6e29987c434184e4c30d8c98e48ead4436a5f126/arista/interface/__pycache__/arista_interface_detail.cpython-311.pyc
--------------------------------------------------------------------------------
/arista/interface/__pycache__/arista_interface_status.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/srl-labs/MultiCLI/6e29987c434184e4c30d8c98e48ead4436a5f126/arista/interface/__pycache__/arista_interface_status.cpython-311.pyc
--------------------------------------------------------------------------------
/arista/interface/arista_arp_details.py:
--------------------------------------------------------------------------------
1 | """
2 | Arista eos Interface Details
3 | Author: Mohammad Zaman
4 | Email: mohammad.zaman@nokia.com
5 |
6 | This code is a plugin for SR Linux CLI that provides detailed information about physical network interfaces in Arista format,
7 | Arista command: show arp
8 | SRLinux command: show arpnd arp-entries
9 | Current usage on SRLinux: show eos arp
10 |
11 | This plugin will be updated to exact Arista CLI in 25.3.2 and 24.10.4
12 | """
13 |
14 | from srlinux import strings
15 | from srlinux.data import ColumnFormatter, Data, Alignment, Formatter, print_line
16 | from srlinux.data.utilities import Percentage
17 | from srlinux.location import build_path
18 | from srlinux.mgmt.cli.cli_plugin import CliPlugin
19 | from srlinux.mgmt.cli.key_completer import KeyCompleter
20 | from srlinux.schema import FixedSchemaRoot
21 | from srlinux.syntax import Syntax
22 | from datetime import datetime, timedelta
23 |
24 | class ArpDetails(object):
25 |
26 | def _get_syntax_arp(self):
27 | result = Syntax('arp', help='Show IPv4 arp entries report')
28 | result.add_named_argument('interface', default='*', suggestions=KeyCompleter(path=self._interface_path))
29 | result.add_named_argument('subinterface', default='*', suggestions=KeyCompleter(path=self._subinterface_path))
30 | result.add_named_argument('ipv4-address', default='*',
31 | suggestions=KeyCompleter(path=lambda arguments: self._ipv4_address_path(arguments)))
32 | return result
33 |
34 | def _get_arp_schema(self, v4):
35 | root = FixedSchemaRoot()
36 | if v4:
37 | root.add_child('neighbor',
38 | keys=['Address'],
39 | fields=['Age_sec', 'Hardware_Addr', 'Interface'])
40 | else:
41 | root.add_child('neighbor',
42 | keys=['Subinterface', 'Age (sec)', 'Neighbor'],
43 | fields=[
44 | 'Origin',
45 | 'Link layer address',
46 | 'Current state',
47 | 'Next state change',
48 | 'Is Router'
49 | ])
50 | return root
51 |
52 | def _interface_path(self, arguments):
53 | return build_path('/interface[name=*]')
54 |
55 | def _subinterface_path(self, arguments):
56 | return build_path(
57 | '/interface[name={name}]/subinterface[index=*]',
58 | name=arguments.get('interface'))
59 |
60 | def _ipv4_address_path(self, arguments, wildcard=True):
61 | return build_path(
62 | '/interface[name={name}]/subinterface[index={index}]/ipv4/arp/neighbor[ipv4-address={address}]',
63 | name=arguments.get('interface'),
64 | index=arguments.get('subinterface'),
65 | address='*' if wildcard else arguments.get('ipv4-address')
66 | )
67 |
68 | def _ipv6_address_path(self, arguments, wildcard=True):
69 | return build_path(
70 | '/interface[name={name}]/subinterface[index={index}]/ipv6/neighbor-discovery/neighbor[ipv6-address={address}]',
71 | name=arguments.get('interface'),
72 | index=arguments.get('subinterface'),
73 | address='*' if wildcard else arguments.get('ipv6-address')
74 | )
75 |
76 | def _fetch_state(self, state, arguments, v4):
77 | path = self._ipv4_address_path(
78 | arguments, False) if v4 else self._ipv6_address_path(arguments, False)
79 |
80 | return state.server_data_store.stream_data(path, recursive=True)
81 |
82 | def _init_members(self):
83 | self._total_entries = 0
84 | self._static_entries = 0
85 | self._dynamic_entries = 0
86 |
87 | def _populate_data(self, data, server_data, v4):
88 | self._init_members()
89 | data.synchronizer.flush_fields(data)
90 |
91 | for interface in server_data.interface.items():
92 | self._add_subinterface(data, interface.name,
93 | interface.subinterface, v4)
94 |
95 | data.synchronizer.flush_children(data.neighbor)
96 | return data
97 |
98 | def _add_subinterface(self, data, interface_name, server_data, v4):
99 | for subinterface in server_data.items():
100 | self._add_neighbor(data, interface_name, subinterface.index,
101 | subinterface.ipv4.get().arp.get().neighbor if v4
102 | else subinterface.ipv6.get().neighbor_discovery.get().neighbor, v4)
103 |
104 | @staticmethod
105 | def convert_mac(mac):
106 | mac = mac.replace(":", "").replace("-", "").lower()
107 | if len(mac) != 12:
108 | return mac
109 | return f"{mac[0:4]}.{mac[4:8]}.{mac[8:12]}"
110 |
111 | @staticmethod
112 | def convert_iso_to_hms(iso_time_str):
113 | dt = datetime.fromisoformat(iso_time_str.replace("Z", "+00:00")) # Convert to datetime object
114 | return dt.strftime("%H:%M:%S") # Format as hh:mm:ss
115 |
116 |
117 |
118 | def _add_neighbor(self, data, interface_name, subinterface_index, server_data, v4):
119 | for neighbor in server_data.items():
120 | if v4:
121 | # Create the neighbor row using the new schema.
122 | child = data.neighbor.create(neighbor.ipv4_address)
123 | self._total_entries += 1
124 |
125 | # Compute the "Age (sec)" as a natural relative time string.
126 | expiration_time = neighbor.expiration_time
127 | # time_remaining = strings.natural_relative_time(expiration_time) if expiration_time else "0:00:00"
128 | child.age_sec = self.convert_iso_to_hms(expiration_time)
129 | # The "Hardware Addr" column.
130 | child.hardware_addr = self.convert_mac(neighbor.link_layer_address)
131 | child.interface = f"{interface_name}.{subinterface_index}" if subinterface_index != interface_name else interface_name
132 | child.synchronizer.flush_fields(child)
133 | else:
134 | child = data.neighbor.create(interface_name, subinterface_index, neighbor.ipv6_address)
135 | self._total_entries += 1
136 | expiration_time = neighbor.next_state_time
137 | time_remaining = strings.natural_relative_time(expiration_time) if expiration_time else None
138 | child.link_layer_address = neighbor.link_layer_address
139 | child.next_state_change = time_remaining
140 | child.current_state = neighbor.current_state
141 | child.is_router = neighbor.is_router
142 | child.synchronizer.flush_fields(child)
143 |
144 |
145 | def _set_formatters(self, data, v4):
146 | if v4:
147 | data.set_formatter('/neighbor',
148 | ColumnFormatter(ancestor_keys=True,
149 | borders=0,
150 | widths=[15, 10, 15, 20],
151 | horizontal_alignment={
152 | 'address': Alignment.Left,
153 | 'age_sec': Alignment.Center,
154 | 'hardware_addr': Alignment.Center,
155 | 'interface': Alignment.Center,
156 | },
157 | )
158 | )
159 | else:
160 | ip_length = 39
161 | column_widths = [
162 | Percentage(10),
163 | ip_length,
164 | Percentage(10),
165 | Percentage(20),
166 | ]
167 | if not v4:
168 | column_widths.extend([Percentage(10), Percentage(20), Percentage(10)])
169 | data.set_formatter('/neighbor',
170 | ColumnFormatter(ancestor_keys=True,
171 | borders=0,
172 | widths=column_widths,
173 | horizontal_alignment={
174 | 'Subinterface': Alignment.Right,
175 | 'Neighbor': Alignment.Right,
176 | 'Age (sec)': Alignment.Right
177 | }
178 | )
179 | )
180 |
181 | def _ipv4_variant(self, arguments):
182 | return arguments.node.name == 'arp'
183 |
184 | def print(self, state, arguments, output, **_kwargs):
185 | v4 = self._ipv4_variant(arguments)
186 | server_data = self._fetch_state(state, arguments, v4)
187 | result = Data(arguments.schema)
188 | self._set_formatters(result, v4)
189 |
190 | with output.stream_data(result):
191 | self._populate_data(result, server_data, v4)
192 |
193 |
194 | class SummaryFormatter(Formatter):
195 |
196 | def iter_format(self, entry, max_width):
197 | yield print_line(max_width, '-')
198 | yield from self._format_entry(entry, max_width)
199 | yield print_line(max_width, '-')
200 |
201 | def _format_entry(self, entry, max_width):
202 | yield f' Total entries : {entry.total_entries} ({entry.static_entries} static, {entry.dynamic_entries} dynamic)'
203 |
--------------------------------------------------------------------------------
/arista/interface/arista_interface_detail.py:
--------------------------------------------------------------------------------
1 | """
2 | Arista eos Interface Details
3 | Author: Mohammad Zaman
4 | Email: mohammad.zaman@nokia.com
5 |
6 | This code is a plugin for SR Linux CLI that provides detailed information about physical network interfaces in Arista format,
7 | Arista command: show interface {interface_name}
8 | SRLinux command: show interface {interface_name} detail
9 | Current usage on SRLinux: show eos interface detail
10 |
11 | This plugin will be updated to exact Arista CLI in 25.3.2 and 24.10.4
12 | """
13 |
14 | from srlinux.mgmt.cli import CliPlugin, KeyCompleter, MultipleKeyCompleters
15 | from srlinux.syntax import Syntax
16 | from srlinux.schema import FixedSchemaRoot
17 | from srlinux.location import build_path
18 | from srlinux import strings
19 | from srlinux.data import Border, ColumnFormatter, TagValueFormatter, Borders, Data, Indent
20 | from srlinux.syntax.value_checkers import IntegerValueInRangeChecker
21 | import json
22 | from jinja2 import Template
23 | from datetime import datetime
24 |
25 |
26 | class InterfaceDetails(object):
27 |
28 | def get_syntax_details(self):
29 | result = Syntax('interface', help='Show interface report')
30 | result.add_unnamed_argument(
31 | 'name', default='*', suggestions=MultipleKeyCompleters(keycompleters=[KeyCompleter(path="/interface[name=*]")]))
32 | return result
33 |
34 |
35 | def _timedelta_str(self, timestamp):
36 | last_chg_time = datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ")
37 | diff = datetime.utcnow() - last_chg_time
38 | seconds = int(diff.total_seconds())
39 |
40 | days, seconds = divmod(seconds, 86400)
41 | hours, seconds = divmod(seconds, 3600)
42 | minutes, seconds = divmod(seconds, 60)
43 |
44 | time_parts = []
45 | if days > 0:
46 | time_parts.append(f"{days} days")
47 | if hours > 0:
48 | time_parts.append(f"{hours} hours")
49 | if minutes > 0:
50 | time_parts.append(f"{minutes} minutes")
51 | if seconds > 0 or not time_parts:
52 | time_parts.append(f"{seconds} seconds")
53 |
54 | return ", ".join(time_parts)
55 |
56 | def _build_last_change_string(self, last_change):
57 | str_time = self._timedelta_str(last_change)
58 | return f'{str_time}'
59 |
60 | def convert_mac(self, mac):
61 | try:
62 | if not mac or not isinstance(mac, str):
63 | raise ValueError("Invalid MAC address provided")
64 |
65 | print(mac)
66 | mac = mac.replace(":", "").replace("-", "").lower()
67 |
68 | if len(mac) != 12:
69 | return mac
70 |
71 | return f"{mac[0:4]}.{mac[4:8]}.{mac[8:12]}"
72 |
73 | except Exception as e:
74 | return None
75 |
76 | def convert_speed_to_bps(self, speed):
77 | try:
78 | if not speed or not isinstance(speed, str):
79 | raise ValueError("Invalid speed value provided")
80 |
81 | number = float(speed.rstrip('GMK')) # Remove G/M/K suffixes
82 | unit = speed[-1]
83 |
84 | if unit == 'G':
85 | return int(number * 1_000_000_000)
86 | elif unit == 'M':
87 | return int(number * 1_000_000)
88 | elif unit == 'K':
89 | return int(number * 1_000)
90 | else:
91 | return int(number)
92 |
93 | except Exception as e:
94 | print(f"Error converting speed '{speed}' to bps: {e}")
95 | return None
96 |
97 |
98 |
99 |
100 | def calculate_utilization(self, traffic_rate, port_speed):
101 | if traffic_rate is None:
102 | # print("Warning: traffic_rate is None, setting to 0")
103 | traffic_rate = 0.0
104 | if port_speed is None:
105 | # print("Warning: port_speed is None, setting to 1 to avoid division by zero")
106 | port_speed = "1G" # Default to avoid division by zero
107 |
108 | traffic_rate_bps = float(traffic_rate)
109 | port_speed_bps = self.convert_speed_to_bps(port_speed)
110 |
111 | if port_speed_bps == 0:
112 | return 0.0
113 |
114 | utilization = (traffic_rate_bps / port_speed_bps) * 100
115 | return round(utilization, 2)
116 |
117 |
118 | def _fetch_state(self, state, arguments):
119 | interface_name = arguments.get('interface', 'name')
120 |
121 | path = build_path('/interface[name={name}]', name=interface_name)
122 | my_data = state.server_data_store.get_data(path, recursive=True, include_container_children=True)
123 |
124 | for intf in my_data.interface.items():
125 | interface_name = intf.name
126 | admin_status = getattr(intf, 'admin_state', 'disable')
127 | oper_status = getattr(intf, 'oper_state', 'down')
128 | if admin_status == "enable":
129 | line_protocol_status = "up" if oper_status == "up" else "down"
130 | else:
131 | line_protocol_status = "down"
132 | line_protocol_status = line_protocol_status
133 | if oper_status == "up":
134 | connection_status = "connected"
135 | else:
136 | connection_status = "notconnected"
137 | connection_status = connection_status
138 | # hardware = intf.hardware
139 | raw_mac_address = intf.ethernet.get().hw_mac_address
140 | mac_address = self.convert_mac(raw_mac_address)
141 | bia_address = mac_address
142 | mtu = intf.mtu
143 | #bandwidth calculation with exception handling
144 | try:
145 | port_speed = intf.ethernet.get().port_speed
146 | if not port_speed or not isinstance(port_speed, str):
147 | raise ValueError("Invalid or missing port speed")
148 |
149 | bandwidth = int(port_speed.rstrip("G")) * 1_000_000
150 |
151 | except Exception as e:
152 | print(f"Error calculating bandwidth from port speed: {e}")
153 | bandwidth = None
154 | try:
155 | duplex = intf.ethernet.get().duplex_mode
156 | except:
157 | duplex = "Full"
158 | port_speed = intf.ethernet.get().port_speed
159 | auto_negotiation = "on"
160 | uni_link = "disabled"
161 | #calculating the uptime
162 | if oper_status == "up":
163 | uptime = self._build_last_change_string(intf.last_change)
164 | else:
165 | uptime = "00"
166 | loopback_mode = intf.loopback_mode
167 | link_changes = intf.statistics.get().carrier_transitions
168 | input_packets = intf.statistics.get().in_packets
169 | input_bytes = intf.statistics.get().in_octets
170 | received_broadcasts = intf.statistics.get().in_broadcast_packets
171 | received_multicast = intf.statistics.get().in_multicast_packets
172 | # runts = intf.statistics.get().in_runts
173 | giants = intf.ethernet.get().statistics.get().in_oversize_frames
174 | input_errors = intf.statistics.get().in_error_packets
175 | crc_errors = intf.ethernet.get().statistics.get().in_crc_error_frames
176 | #alignment_errors = intf.statistics.get().in_alignment_error_frames
177 | #symbol_errors = intf.statistics.get().in_symbol_error_frames
178 | input_discards = intf.statistics.get().in_discarded_packets
179 | pause_input = intf.ethernet.get().statistics.get().in_mac_pause_frames
180 | output_packets = intf.statistics.get().out_packets
181 | output_bytes = intf.statistics.get().out_octets
182 | sent_broadcasts = intf.statistics.get().out_broadcast_packets
183 | sent_multicast = intf.statistics.get().out_multicast_packets
184 | output_errors = intf.statistics.get().out_error_packets
185 | #collisions = intf.statistics.get().out_collision_packets
186 | #late_collisions = intf.statistics.get().out_late_collision_packets
187 | #deferred = intf.statistics.get().out_deferred_packets
188 | output_discards = intf.statistics.get().out_discarded_packets
189 | pause_output = getattr(getattr(getattr(intf.ethernet.get(), 'statistics', None), 'get', lambda: None)(), 'out_mac_pause_frames', None) or 0
190 | input_packets_rate = intf.traffic_rate.get().in_bps
191 | output_packets_rate = intf.traffic_rate.get().out_bps
192 | input_utilization = self.calculate_utilization(input_packets_rate, port_speed)
193 | output_utilization = self.calculate_utilization(output_packets_rate, port_speed)
194 |
195 |
196 | template_string = """
197 | {{ interface_name }} is {{ oper_status }}, line protocol is {{ line_protocol_status }} ({{ connection_status }})
198 | Hardware is {{ hardware }}, address is {{ mac_address }} (bia {{ bia_address }})
199 | Ethernet MTU {{ mtu }} bytes, BW {{ bandwidth }} kbit
200 | {{ duplex }}-duplex, {{ speed }}, auto negotiation: {{ auto_negotiation }}, uni-link: {{ uni_link }}
201 | Up {{ uptime }}
202 | Loopback Mode : {{ loopback_mode }}
203 | {{ link_changes }} link status changes since last clear
204 | Last clearing of "show interface" counters {{ last_clearing }}
205 | 1 second input rate {{ input_rate }} bps ({{ input_utilization }}% with framing overhead), {{ input_packets_rate }} packets/sec
206 | 1 second output rate {{ output_rate }} bps ({{ output_utilization }}% with framing overhead), {{ output_packets_rate }} packets/sec
207 | {{ input_packets }} packets input, {{ input_bytes }} bytes
208 | Received {{ received_broadcasts }} broadcasts, {{ received_multicast }} multicast
209 | {{ runts }} runts, {{ giants }} giants
210 | {{ input_errors }} input errors, {{ crc_errors }} CRC, {{ alignment_errors }} alignment, {{ symbol_errors }} symbol, {{ input_discards }} input discards
211 | {{ pause_input }} PAUSE input
212 | {{ output_packets }} packets output, {{ output_bytes }} bytes
213 | Sent {{ sent_broadcasts }} broadcasts, {{ sent_multicast }} multicast
214 | {{ output_errors }} output errors, {{ collisions }} collisions
215 | {{ late_collisions }} late collision, {{ deferred }} deferred, {{ output_discards }} output discards
216 | {{ pause_output }} PAUSE output"""
217 |
218 |
219 | data = {
220 | "interface_name": interface_name,
221 | "oper_status": oper_status,
222 | "line_protocol_status": oper_status,
223 | "connection_status": connection_status,
224 | "hardware": "Ethernet",
225 | "mac_address": mac_address,
226 | "bia_address": bia_address,
227 | "mtu": mtu,
228 | "bandwidth": bandwidth,
229 | "duplex": duplex,
230 | "speed": port_speed,
231 | "auto_negotiation": auto_negotiation,
232 | "uni_link": uni_link,
233 | "uptime": uptime,
234 | "loopback_mode": loopback_mode,
235 | "link_changes": link_changes,
236 | "last_clearing": uptime,
237 | "input_rate": input_packets_rate,
238 | "input_utilization": input_utilization,
239 | "input_packets_rate": input_packets_rate,
240 | "output_rate": output_packets_rate,
241 | "output_utilization": output_utilization,
242 | "output_packets_rate": output_packets_rate,
243 | "input_packets": input_packets,
244 | "input_bytes": input_bytes,
245 | "received_broadcasts": received_broadcasts,
246 | "received_multicast": received_multicast,
247 | "runts": 0,
248 | "giants": giants,
249 | "input_errors": input_errors,
250 | "crc_errors": crc_errors,
251 | "alignment_errors": 0,
252 | "symbol_errors": 0,
253 | "input_discards": input_discards,
254 | "pause_input": pause_input,
255 | "output_packets": output_packets,
256 | "output_bytes": output_bytes,
257 | "sent_broadcasts": sent_broadcasts,
258 | "sent_multicast": sent_multicast,
259 | "output_errors": output_errors,
260 | "collisions": 0,
261 | "late_collisions": 0,
262 | "deferred": 0,
263 | "output_discards": output_discards,
264 | "pause_output": pause_output,
265 | }
266 |
267 | template = Template(template_string)
268 | output = template.render(data)
269 | print(output, end='')
270 | print()
271 |
272 | return my_data
273 |
274 | def print(self, state, arguments, output, **_kwargs):
275 | self._fetch_state(state, arguments)
276 |
--------------------------------------------------------------------------------
/arista/interface/arista_interface_status.py:
--------------------------------------------------------------------------------
1 | """
2 | Arista eos Interface Status
3 | Author: Mohammad Zaman
4 | Email: mohammad.zaman@nokia.com
5 |
6 | This code is a plugin for SR Linux CLI that provides detailed information about physical network interfaces in Arista format,
7 | Arista command: show interface status
8 | SRLinux command: show interface brief
9 | Current usage on SRLinux: show eos interface status
10 |
11 | This plugin will be updated to exact Arista CLI in 25.3.2 and 24.10.4
12 | """
13 |
14 | from srlinux.data import ColumnFormatter, Data, Borders, ColumnFormatter, Alignment
15 | from srlinux.location import build_path
16 | from srlinux.schema import FixedSchemaRoot
17 | from srlinux.syntax import Syntax
18 |
19 |
20 | class InterfaceStatus(object):
21 | def get_syntax_status(self):
22 | result = Syntax('status', help='Show arista interface status for interfaces')
23 | return result
24 |
25 | def get_data_schema(self):
26 | root = FixedSchemaRoot()
27 | root.add_child(
28 | 'IfBrief',
29 | key='Port',
30 | fields=['Name', 'Status', 'vlan', 'Duplex', 'Speed', 'Type'],
31 | )
32 | return root
33 |
34 | def print(self, state, arguments, output, **_kwargs):
35 | serve_data = self._stream_data(state, arguments)
36 | result = Data(arguments.schema)
37 | self._set_formatters(result)
38 | with output.stream_data(result):
39 | self._populate_data(result, serve_data)
40 |
41 | def _stream_data(self, state, arguments):
42 | path = build_path('/interface[name={name}]', name=arguments.get('interface', 'name'))
43 | return state.server_data_store.stream_data(path, recursive=False, include_container_children=True)
44 |
45 | def _populate_data(self, data, serve_data):
46 | data.synchronizer.flush_fields(data)
47 | for interface in serve_data.interface.items():
48 | child = data.ifbrief.create(interface.name)
49 | child.name = interface.description
50 | child.status = "notconnected"
51 | if interface.oper_state == "up":
52 | child.status = "connected"
53 | child.vlan = interface.vlan_tagging
54 | try:
55 | child.duplex = interface.ethernet.get().duplex_mode
56 | except:
57 | child.duplex = "full"
58 | child.speed = interface.ethernet.get().port_speed
59 | child.type = interface.transceiver.get().ethernet_pmd
60 | child.synchronizer.flush_fields(child)
61 | data.synchronizer.flush_children(data.ifbrief)
62 |
63 | def _set_formatters(self, interface_data):
64 | # data = interface_data
65 | formatter = ColumnFormatter(
66 | borders=Borders.Nothing,
67 | horizontal_alignment={
68 | 'Port': Alignment.Left,
69 | 'Name': Alignment.Left,
70 | 'Status': Alignment.Right,
71 | 'vlan': Alignment.Center,
72 | 'Duplex': Alignment.Center,
73 | 'Speed': Alignment.Center,
74 | 'Type': Alignment.Left
75 | },
76 | widths={
77 | 'Port': 14,
78 | 'Name': 10,
79 | 'Status': 14,
80 | 'vlan': 10,
81 | 'Duplex': 10,
82 | 'Speed': 10,
83 | 'Type': 10
84 | }
85 |
86 | )
87 | interface_data.set_formatter('/IfBrief', formatter)
--------------------------------------------------------------------------------
/arista/ip/__pycache__/ip_bgp_report.cpython-311.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/srl-labs/MultiCLI/6e29987c434184e4c30d8c98e48ead4436a5f126/arista/ip/__pycache__/ip_bgp_report.cpython-311.pyc
--------------------------------------------------------------------------------
/arista/ip/ip_bgp_report.py:
--------------------------------------------------------------------------------
1 | from srlinux.syntax import Syntax
2 | from srlinux.location import build_path
3 | from srlinux.mgmt.cli import KeyCompleter
4 | import datetime
5 |
6 | class IpBgpReport:
7 | """Handles the 'show ip bgp summary' command functionality."""
8 |
9 | # Class level constants
10 | PATH_TEMPLATES = {
11 | 'bgp_instance': '/network-instance[name={network_instance}]/protocols/bgp',
12 | }
13 |
14 | BGP_STATE_MAP = {
15 | 'idle': 'Idle',
16 | 'connect': 'Connect',
17 | 'active': 'Active',
18 | 'opensent': 'OpenSent',
19 | 'openconfirm': 'OpenConfirm',
20 | 'established': 'Estab'
21 | }
22 |
23 | def show_bgp_summary(self, state, output, network_instance='default'):
24 | """Main function to display BGP summary"""
25 | # Get BGP instance data
26 | try:
27 | bgp_data = self._get_bgp_data(state, network_instance)
28 | # Silently exit if no data found (don't print error messages)
29 | if not bgp_data:
30 | return
31 |
32 | if not self._has_bgp_config(bgp_data):
33 | return
34 |
35 | # Print header and neighbor data
36 | self._print_bgp_header(bgp_data, network_instance)
37 | neighbors = self._get_neighbor_data(bgp_data)
38 |
39 | if neighbors:
40 | self._print_neighbor_table(neighbors)
41 | else:
42 | # No neighbors - exit silently
43 | pass
44 |
45 | except Exception as e:
46 | # Silent error handling - don't print errors
47 | pass
48 |
49 | def _get_bgp_data(self, state, network_instance):
50 | """Get BGP instance data"""
51 | try:
52 | path = build_path(self.PATH_TEMPLATES['bgp_instance'].format(
53 | network_instance=network_instance
54 | ))
55 | return state.server_data_store.get_data(path, recursive=True)
56 | except Exception:
57 | # Silently handle error
58 | return None
59 |
60 | def _has_bgp_config(self, bgp_data):
61 | """Check if BGP is configured"""
62 | if not bgp_data:
63 | return False
64 |
65 | try:
66 | network_instance = bgp_data.network_instance.get()
67 | if not network_instance:
68 | return False
69 |
70 | protocols = network_instance.protocols.get()
71 | if not protocols:
72 | return False
73 |
74 | return bool(protocols.bgp.get())
75 | except (AttributeError, Exception):
76 | return False
77 |
78 | def _print_bgp_header(self, bgp_data, network_instance):
79 | """Print BGP header information"""
80 | router_id = "0.0.0.0"
81 | local_as = "N/A"
82 |
83 | try:
84 | bgp = bgp_data.network_instance.get().protocols.get().bgp.get()
85 | if hasattr(bgp, 'router_id') and bgp.router_id:
86 | router_id = bgp.router_id
87 |
88 | if hasattr(bgp, 'autonomous_system') and bgp.autonomous_system:
89 | local_as = bgp.autonomous_system
90 | except (AttributeError, Exception):
91 | pass
92 |
93 | print(f"BGP summary information for VRF {network_instance}")
94 | print(f"Router identifier {router_id}, local AS number {local_as}")
95 | print("Neighbor Status Codes: m – Under maintenance")
96 |
97 | # Print column headers
98 | print(" Neighbor V AS MsgRcvd MsgSent InQ OutQ Up/Down State PfxRcd PfxAcc")
99 |
100 | def _get_neighbor_data(self, bgp_data):
101 | """Get BGP neighbor data"""
102 | neighbors = []
103 |
104 | try:
105 | bgp = bgp_data.network_instance.get().protocols.get().bgp.get()
106 | if not hasattr(bgp, 'neighbor'):
107 | return neighbors
108 |
109 | for neighbor in bgp.neighbor.items():
110 | if not neighbor:
111 | continue
112 | # check for EVPN neighbors
113 | if hasattr(neighbor, 'afi_safi'):
114 | for afi_safi in neighbor.afi_safi.items():
115 | if not afi_safi:
116 | continue
117 | if afi_safi.afi_safi_name == 'ipv4-unicast' and afi_safi.admin_state == 'enable':
118 | # Extract neighbor data with safe defaults
119 | peer_address = neighbor.peer_address
120 | peer_as = "?"
121 | if hasattr(neighbor, 'peer_as'):
122 | peer_as = str(neighbor.peer_as)
123 | # Get session state - be cautious about case sensitivity
124 | session_state = "Idle"
125 | state_lower = None
126 | if hasattr(neighbor, 'session_state'):
127 | state_lower = neighbor.session_state.lower() if neighbor.session_state else None
128 | # Map to display format with proper capitalization
129 | session_state = self.BGP_STATE_MAP.get(state_lower, "Idle")
130 |
131 | # Get message statistics
132 | messages_received = 0
133 | messages_sent = 0
134 |
135 | if hasattr(neighbor, 'received_messages'):
136 | rm = neighbor.received_messages.get()
137 | if rm and hasattr(rm, 'total_messages'):
138 | messages_received = rm.total_messages
139 | received_queue = rm.queue_depth
140 |
141 | if hasattr(neighbor, 'sent_messages'):
142 | sm = neighbor.sent_messages.get()
143 | if sm and hasattr(sm, 'total_messages'):
144 | messages_sent = sm.total_messages
145 | sent_queue = sm.queue_depth
146 | # Get prefix information
147 | prefixes_received = 0
148 | prefixes_accepted = 0
149 | if hasattr(afi_safi, 'received_routes'):
150 | prefixes_received = afi_safi.received_routes
151 | if hasattr(afi_safi, 'active_routes'):
152 | prefixes_accepted = afi_safi.active_routes
153 |
154 | # Format uptime
155 | uptime = self._format_uptime(neighbor)
156 | neighbor_info = {
157 | 'peer_address': peer_address,
158 | 'address_family': "4",
159 | 'peer_as': peer_as,
160 | 'msg_rcvd': messages_received,
161 | 'msg_sent': messages_sent,
162 | 'state': session_state,
163 | 'state_lower': state_lower,
164 | 'pfx_received': prefixes_received,
165 | 'pfx_accepted': prefixes_accepted,
166 | 'up_time': uptime,
167 | 'rx_queue': received_queue,
168 | 'tx_queue': sent_queue
169 | }
170 | neighbors.append(neighbor_info)
171 | if afi_safi.afi_safi_name == 'ipv6-unicast' and afi_safi.admin_state == 'enable':
172 | # Extract neighbor data with safe defaults
173 | peer_address = neighbor.peer_address
174 | peer_as = "?"
175 | if hasattr(neighbor, 'peer_as'):
176 | peer_as = str(neighbor.peer_as)
177 | # Get session state - be cautious about case sensitivity
178 | session_state = "Idle"
179 | state_lower = None
180 | if hasattr(neighbor, 'session_state'):
181 | state_lower = neighbor.session_state.lower() if neighbor.session_state else None
182 | # Map to display format with proper capitalization
183 | session_state = self.BGP_STATE_MAP.get(state_lower, "Idle")
184 |
185 | # Get message statistics
186 | messages_received = 0
187 | messages_sent = 0
188 |
189 | if hasattr(neighbor, 'received_messages'):
190 | rm = neighbor.received_messages.get()
191 | if rm and hasattr(rm, 'total_messages'):
192 | messages_received = rm.total_messages
193 | received_queue = rm.queue_depth
194 |
195 | if hasattr(neighbor, 'sent_messages'):
196 | sm = neighbor.sent_messages.get()
197 | if sm and hasattr(sm, 'total_messages'):
198 | messages_sent = sm.total_messages
199 | sent_queue = sm.queue_depth
200 | # Get prefix information
201 | prefixes_received = 0
202 | prefixes_accepted = 0
203 | if hasattr(afi_safi, 'received_routes'):
204 | prefixes_received = afi_safi.received_routes
205 | if hasattr(afi_safi, 'active_routes'):
206 | prefixes_accepted = afi_safi.active_routes
207 |
208 | # Format uptime
209 | uptime = self._format_uptime(neighbor)
210 | neighbor_info = {
211 | 'peer_address': peer_address,
212 | 'address_family': "6",
213 | 'peer_as': peer_as,
214 | 'msg_rcvd': messages_received,
215 | 'msg_sent': messages_sent,
216 | 'state': session_state,
217 | 'state_lower': state_lower,
218 | 'pfx_received': prefixes_received,
219 | 'pfx_accepted': prefixes_accepted,
220 | 'up_time': uptime,
221 | 'rx_queue': received_queue,
222 | 'tx_queue': sent_queue
223 | }
224 | neighbors.append(neighbor_info)
225 | if afi_safi.afi_safi_name == 'evpn' and afi_safi.admin_state == 'enable':
226 | # Extract neighbor data with safe defaults
227 | peer_address = neighbor.peer_address
228 | peer_as = "?"
229 | if hasattr(neighbor, 'peer_as'):
230 | peer_as = str(neighbor.peer_as)
231 | # Get session state - be cautious about case sensitivity
232 | session_state = "Idle"
233 | state_lower = None
234 | if hasattr(neighbor, 'session_state'):
235 | state_lower = neighbor.session_state.lower() if neighbor.session_state else None
236 | # Map to display format with proper capitalization
237 | session_state = self.BGP_STATE_MAP.get(state_lower, "Idle")
238 |
239 | # Get message statistics
240 | messages_received = 0
241 | messages_sent = 0
242 |
243 | if hasattr(neighbor, 'received_messages'):
244 | rm = neighbor.received_messages.get()
245 | if rm and hasattr(rm, 'total_messages'):
246 | messages_received = rm.total_messages
247 | received_queue = rm.queue_depth
248 |
249 | if hasattr(neighbor, 'sent_messages'):
250 | sm = neighbor.sent_messages.get()
251 | if sm and hasattr(sm, 'total_messages'):
252 | messages_sent = sm.total_messages
253 | sent_queue = sm.queue_depth
254 | # Get prefix information
255 | prefixes_received = 0
256 | prefixes_accepted = 0
257 | if hasattr(afi_safi, 'received_routes'):
258 | prefixes_received = afi_safi.received_routes
259 | if hasattr(afi_safi, 'active_routes'):
260 | prefixes_accepted = afi_safi.active_routes
261 |
262 | # Format uptime
263 | uptime = self._format_uptime(neighbor)
264 | neighbor_info = {
265 | 'peer_address': peer_address,
266 | 'address_family': "E",
267 | 'peer_as': peer_as,
268 | 'msg_rcvd': messages_received,
269 | 'msg_sent': messages_sent,
270 | 'state': session_state,
271 | 'state_lower': state_lower,
272 | 'pfx_received': prefixes_received,
273 | 'pfx_accepted': prefixes_accepted,
274 | 'up_time': uptime,
275 | 'rx_queue': received_queue,
276 | 'tx_queue': sent_queue
277 | }
278 | neighbors.append(neighbor_info)
279 |
280 | except Exception:
281 | # Silent error handling
282 | pass
283 |
284 | return neighbors
285 |
286 | def _create_route_entry(self, route_network, status):
287 | """Create basic route entry with standard fields"""
288 | return {
289 | 'network_info': route_network,
290 | 'status_info': status,
291 | 'nexthop_info': '',
292 | 'metric_info': '',
293 | 'locpref_info': '',
294 | 'weight_info': '',
295 | 'path_info': ''
296 | }
297 |
298 | def _populate_route_attrs(self, state, route_entry, netinst_name, attr_id, route_type='*'):
299 | if not attr_id in self._attrSets_dict:
300 | path_attr = build_path('/network-instance[name={vrf}]/bgp-rib/attr-sets/attr-set[index={atr}]',
301 | vrf=netinst_name, atr=str(attr_id))
302 | attrSets = state.server_data_store.get_data(path_attr, recursive=True, include_container_children=True)
303 | self._attrSets_dict[attr_id] = attrSets
304 | else:
305 | attrSets = self._attrSets_dict[attr_id]
306 | for attr in attrSets.get_descendants('/network-instance/bgp-rib/attr-sets/attr-set'):
307 | route_entry['nexthop_info'] = attr.next_hop
308 | route_entry['locpref_info'] = attr.local_pref
309 | as_path = attr.as_path.get().segment.get().member
310 | if attr.origin == 'igp':
311 | route_entry['path_info'] = ' '.join(map(str,as_path)) + ' i'
312 | elif attr.origin == 'egp':
313 | route_entry['path_info'] = ' '.join(map(str,as_path)) + ' e'
314 | elif attr.origin == 'incomplete':
315 | route_entry['path_info'] = '?'
316 | if hasattr(attr, 'med') and attr.med:
317 | route_entry['metric_info'] = attr.med
318 | else:
319 | route_entry['metric_info'] = '-'
320 | route_entry['weight_info'] = '0'
321 |
322 | def _set_status_code(self, route):
323 | status = ""
324 | if route.used_route:
325 | status = "u"
326 | #self._used_routes += 1
327 | if route.stale_route:
328 | status += "x"
329 | #self._statle_routes += 1
330 | if route.valid_route:
331 | status += "*"
332 | #self._valid_routes += 1
333 | if route.best_route:
334 | status += ">"
335 | return status or '-'
336 |
337 | def _print_neighbor_table(self, neighbors):
338 | """Print formatted neighbor table"""
339 | for neighbor in sorted(neighbors, key=lambda x: str(x.get('peer_address', ''))):
340 | # Show the correct format based on BGP state
341 | # For established neighbors, show the prefix count
342 | # For non-established, show the state
343 | if neighbor.get('state_lower') == 'established':
344 | state_display = str(neighbor.get('pfx_received', 0))
345 | else:
346 | state_display = neighbor.get('state', 'Idle')
347 |
348 | print(f" {neighbor['peer_address']:<15} {neighbor['address_family']:<1} {neighbor['peer_as']:<6} "
349 | f"{neighbor['msg_rcvd']:<9} {neighbor['msg_sent']:<9} "
350 | f"{neighbor['rx_queue']:<6} {neighbor['tx_queue']:<6} "
351 | f"{neighbor['up_time']:<9} {neighbor['state']:<9} "
352 | f"{neighbor['pfx_received']:<9} {neighbor['pfx_accepted']:<9}")
353 |
354 | def _format_uptime(self, neighbor):
355 | """Format uptime for display with safer parsing"""
356 | # Default for non-established sessions
357 | if not hasattr(neighbor, 'session_state') or neighbor.session_state != 'established':
358 | return "never"
359 |
360 | # Check if last_established exists and has a value
361 | if not hasattr(neighbor, 'last_established') or not neighbor.last_established:
362 | return "never"
363 |
364 | try:
365 | # Safer parsing that handles potential format variations
366 | timestamp_str = neighbor.last_established
367 | if '(' in timestamp_str:
368 | timestamp_str = timestamp_str.split('(')[0].strip()
369 |
370 | # Handle ISO format timestamps with timezone information
371 | if 'Z' in timestamp_str:
372 | timestamp_str = timestamp_str.replace('Z', '+00:00')
373 |
374 | last_established_time = datetime.datetime.fromisoformat(timestamp_str)
375 | current_time = datetime.datetime.now(datetime.timezone.utc)
376 |
377 | # Handle potential timezone differences
378 | if last_established_time.tzinfo is None:
379 | last_established_time = last_established_time.replace(tzinfo=datetime.timezone.utc)
380 |
381 | uptime = current_time - last_established_time
382 |
383 | # Format the uptime string
384 | days = uptime.days
385 | hours = uptime.seconds // 3600
386 | minutes = (uptime.seconds % 3600) // 60
387 |
388 | if days > 0:
389 | return f"{days}d{hours}h"
390 | elif hours > 0:
391 | return f"{hours}h{minutes}m"
392 | else:
393 | return f"{minutes}m"
394 |
395 | except Exception:
396 | # Fall back to "never" if there's any parsing error
397 | return "never"
398 |
399 | def _print_rt_table(self, rt_entries):
400 | """Print formatted route type table"""
401 | for rt_entry in rt_entries:
402 | print(f" {rt_entry['status_info']:<7} {rt_entry['network_info']:<50}\n "
403 | f" {rt_entry['nexthop_info']:<16} {rt_entry['metric_info']:<7} "
404 | f"{rt_entry['locpref_info']:<7} {rt_entry['weight_info']:<6} "
405 | f"{rt_entry['path_info']:<20} ")
--------------------------------------------------------------------------------
/arista/plugins/main_arista.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | ###########################################################################
3 | # Description: CLI plugin for EOS bgp command
4 | #
5 | # Copyright (c) 2025 Nokia
6 | ###########################################################################
7 |
8 | from srlinux.mgmt.cli import CliPlugin, KeyCompleter
9 | from srlinux.syntax import Syntax
10 | from srlinux.location import build_path
11 | import sys
12 | import os
13 |
14 | # Try potential base directories
15 | potential_paths = [
16 | os.path.expanduser('~/cli'),
17 | '/etc/opt/srlinux/cli'
18 | ]
19 |
20 | # Find the first valid path
21 | import_base = None
22 | for path in potential_paths:
23 | if os.path.exists(path):
24 | import_base = path
25 | break
26 |
27 | if import_base is None:
28 | raise ImportError("Could not find a valid CLI plugin base directory")
29 |
30 | ####################################################################
31 | ##### Construct the import path. ###################################
32 | '''
33 | Add your directory and create a similar structure like below
34 | '''
35 | ####################################################################
36 | import_path_ip = os.path.join(import_base, "ip")
37 | if import_path_ip not in sys.path:
38 | sys.path.insert(0, import_path_ip)
39 | from ip_bgp_report import IpBgpReport as BaseBgpReport
40 |
41 | import_path_bgp = os.path.join(import_base, "bgp")
42 | if import_path_bgp not in sys.path:
43 | sys.path.insert(0, import_path_bgp)
44 | from bgp_evpn_report import IpBgpReport as EvpnBgpReport
45 |
46 | import_path = os.path.join(import_base, "interface")
47 | if import_path not in sys.path:
48 | sys.path.insert(0, import_path)
49 | from arista_interface_detail import InterfaceDetails
50 | from arista_interface_status import InterfaceStatus
51 | from arista_arp_details import ArpDetails
52 |
53 | class Plugin(CliPlugin):
54 |
55 | __slots__ = (
56 | '_header_string',
57 | '_netinst',
58 | '_arguments',
59 | '_netinst_data',
60 | '_current_netinst',
61 | '_used_routes',
62 | '_valid_routes',
63 | '_received_count',
64 | '_attrSets_dict',
65 | '_route_type',
66 | '_rd',
67 | '_mac_address',
68 | '_ip_address',
69 | '_ip_prefix',
70 | '_esi',
71 | '_ethernet_tag',
72 | '_originating_router',
73 | '_neighbor',
74 | '_multicast_source_address',
75 | '_multicast_group_address',
76 | '_bgp_rib'
77 | )
78 |
79 | def load(self, cli, **_kwargs):
80 | syntax = Syntax('ip', help='display ip protocol information')
81 | ip = cli.show_mode.add_command(syntax, update_location=True)
82 | bgp = ip.add_command(
83 | Syntax('bgp', help='show bgp information'),
84 | update_location=True)
85 | bgp_summary = bgp.add_command(
86 | Syntax('summary')
87 | .add_named_argument('vrf', default='default', help = 'network instance name', suggestions=KeyCompleter('/network-instance[name=*]')),
88 | callback = self._print_summary)
89 |
90 | ##### adding evpn: saju ######
91 |
92 | syntax = Syntax('bgp', help='display bgp information')
93 | bgp = cli.show_mode.add_command(syntax, update_location=True)
94 | evpn = bgp.add_command(
95 | Syntax('evpn', help='show EVPN information'),
96 | update_location=True)
97 | evpn_summary = evpn.add_command(
98 | Syntax('summary')
99 | .add_named_argument('vrf', default='default', help = 'network instance name', suggestions=KeyCompleter('/network-instance[name=*]')),
100 | callback = self._print_evpn_summary)
101 | route_type = evpn.add_command(Syntax('route-type', help='specify the EVPN route type'))
102 | rt_eth_ad = route_type.add_command(
103 | Syntax('auto-discovery')
104 | .add_named_argument('vrf', default='default', help = 'network instance name', suggestions=KeyCompleter('/network-instance[name=*]'))
105 | .add_named_argument('esi', default='*', help = 'ESI value'),
106 | callback = self._print_1)
107 | rt_mac_ip = route_type.add_command(
108 | Syntax('mac-ip')
109 | .add_named_argument('vrf', default='default', help = 'network instance name', suggestions=KeyCompleter('/network-instance[name=*]'))
110 | .add_named_argument('mac-address', default='*', help = 'MAC address'),
111 | callback = self._print_2)
112 | rt_imet = route_type.add_command(
113 | Syntax('imet')
114 | .add_named_argument('vrf', default='default', help = 'network instance name', suggestions=KeyCompleter('/network-instance[name=*]'))
115 | .add_named_argument('origin-router', default='*', help = 'Originating router IPv4 or IPv6 address'),
116 | callback = self._print_3)
117 | rt_eth_seg = route_type.add_command(
118 | Syntax('ethernet-segment')
119 | .add_named_argument('vrf', default='default', help = 'network instance name', suggestions=KeyCompleter('/network-instance[name=*]'))
120 | .add_named_argument('esi', default='*', help = 'ESI value'),
121 | callback = self._print_4)
122 | rt_ip_prefix = route_type.add_command(
123 | Syntax('ip-prefix')
124 | .add_named_argument('vrf', default='default', help = 'network instance name', suggestions=KeyCompleter('/network-instance[name=*]'))
125 | .add_named_argument('ip-address', default='*', help = 'IPv4 or IPv6 address prefix'),
126 | callback = self._print_5)
127 |
128 | #### interface and arp : mozaman ####
129 |
130 | '''
131 | This section has preprend command called eos because there is an overlap command with arista and srlinux
132 | Going forward in new release eos preprend will be depreciated and run the native commands
133 | example: show eos interface status
134 | '''
135 |
136 | syntax = Syntax('eos', help='Show Arista EOS reports')
137 | eos = cli.show_mode.add_command(syntax)
138 | # Adding sub-commands of Arista command: "show arista interface detail"
139 | interfaces = eos.add_command(
140 | InterfaceDetails().get_syntax_details(),
141 | update_location=True,
142 | callback= self._interface_details
143 | )
144 | # Adding sub-commands of Arista command: "show Arista EOS interface status"
145 | interfaces.add_command(
146 | InterfaceStatus().get_syntax_status(),
147 | update_location=True,
148 | callback=self._interface_status,
149 | schema = InterfaceStatus().get_data_schema()
150 | )
151 | # ARP Commands of Arista EOS command: "show arp"
152 | eos.add_command(
153 | ArpDetails()._get_syntax_arp(),
154 | update_location=True,
155 | callback=self._arp_entries,
156 | # callback=ArpDetails()._print,
157 | schema=ArpDetails()._get_arp_schema(True),
158 | )
159 |
160 | ############# Base BGP IP : saju ###########
161 |
162 | def _print_summary(self, state, arguments, output, **_kwargs):
163 | self._arguments = arguments
164 | netinst = self._arguments.get('summary', 'vrf')
165 | BaseBgpReport().show_bgp_summary(state, output, network_instance=netinst)
166 | print("-" * 100)
167 | print(f'Try SR Linux command: show network-instance {netinst} protocols bgp neighbor')
168 |
169 | ############### EVPN BGP : saju ############
170 |
171 | def _print_evpn_summary(self, state, arguments, output, **_kwargs):
172 | self._arguments = arguments
173 | netinst = self._arguments.get('summary', 'vrf')
174 | EvpnBgpReport().show_bgp_summary(state, output, network_instance=netinst)
175 | print("-" * 100)
176 | print(f'Try SR Linux command: show network-instance {netinst} protocols bgp neighbor')
177 |
178 |
179 | def __init__(self):
180 | self._rd = '*'
181 | self._esi = '*'
182 | self._mac_address = '*'
183 | self._ip_address = '*'
184 | self._ip_prefix = '*'
185 | self._originating_router = '*'
186 | self._ethernet_tag = '*'
187 | self._neighbor = '*'
188 | self._multicast_source_address = '*'
189 | self._multicast_group_address = '*'
190 | self._bgp_rib = None
191 | self._attrSets_dict = {}
192 |
193 | def reset_counters(self):
194 | self._used_routes = 0
195 | self._valid_routes = 0
196 | self._received_count = 0
197 |
198 | def _print_1(self, state, arguments, output, **_kwargs):
199 | self._route_type = '1'
200 | self._arguments = arguments
201 | netinst = self._arguments.get('auto-discovery', 'vrf')
202 | esi_input = self._arguments.get('auto-discovery', 'esi')
203 | EvpnBgpReport().show_evpn_rt1(state, output, network_instance=netinst, esi_value=esi_input)
204 | print("-" * 100)
205 | print('Try SR Linux command: show network-instance default protocols bgp routes evpn route-type 1 summary')
206 |
207 | def _print_2(self, state, arguments, output, **_kwargs):
208 | self._route_type = '2'
209 | self._arguments = arguments
210 | netinst = self._arguments.get('mac-ip', 'vrf')
211 | mac_input = self._arguments.get('mac-ip', 'mac-address')
212 | EvpnBgpReport().show_evpn_rt2(state, output, network_instance=netinst, mac_value=mac_input)
213 | print("-" * 100)
214 | print('Try SR Linux command: show network-instance default protocols bgp routes evpn route-type 2 summary')
215 |
216 | def _print_3(self, state, arguments, output, **_kwargs):
217 | self._route_type = '3'
218 | self._arguments = arguments
219 | netinst = self._arguments.get('imet', 'vrf')
220 | originr_input = self._arguments.get('imet', 'origin-router')
221 | EvpnBgpReport().show_evpn_rt3(state, output, network_instance=netinst, originr_value=originr_input)
222 | print("-" * 100)
223 | print('Try SR Linux command: show network-instance default protocols bgp routes evpn route-type 3 summary')
224 |
225 | def _print_4(self, state, arguments, output, **_kwargs):
226 | self._route_type = '4'
227 | self._arguments = arguments
228 | netinst = self._arguments.get('ethernet-segment', 'vrf')
229 | esi4_input = self._arguments.get('ethernet-segment', 'esi')
230 | EvpnBgpReport().show_evpn_rt4(state, output, network_instance=netinst, esi4_value=esi4_input)
231 | print("-" * 100)
232 | print('Try SR Linux command: show network-instance default protocols bgp routes evpn route-type 4 summary')
233 |
234 | def _print_5(self, state, arguments, output, **_kwargs):
235 | self._route_type = '5'
236 | self._arguments = arguments
237 | netinst = self._arguments.get('ip-prefix', 'vrf')
238 | ip_input = self._arguments.get('ip-prefix', 'ip-address')
239 | EvpnBgpReport().show_evpn_rt5(state, output, network_instance=netinst, ip_value=ip_input)
240 | print("-" * 100)
241 | print('Try SR Linux command: show network-instance default protocols bgp routes evpn route-type 5 summary')
242 |
243 | ########## Interface and ARP: mozaman ###########
244 |
245 | def _interface_details(self, state, arguments, output, **_kwargs):
246 | if state.is_intermediate_command:
247 | return
248 | InterfaceDetails().print(state, arguments, output, **_kwargs)
249 | msg = 'Try SR Linux command: show interface {interface_name} detail'
250 | output.print_line(f'\n{"-" * len(msg)}\n{msg}')
251 |
252 | def _interface_status(self, state, arguments, output, **_kwargs):
253 | if state.is_intermediate_command:
254 | return
255 | InterfaceStatus().print(state, arguments, output)
256 | msg = 'Try SR Linux command: show interface {interface_name} brief'
257 | output.print_line(f'\n{"-" * len(msg)}\n{msg}')
258 |
259 | def _arp_entries(self, state, arguments, output, **_kwargs):
260 | if state.is_intermediate_command:
261 | return
262 | ArpDetails().print(state, arguments, output)
263 | msg = 'Try SR Linux command: show arpnd arp-entries'
264 | output.print_line(f'\n{"-" * len(msg)}\n{msg}')
265 |
--------------------------------------------------------------------------------
/cisco-nx/README.md:
--------------------------------------------------------------------------------
1 | # Custom CLI Plugins for Cisco NX-OS
2 |
3 | The following CLI plugins are available in this repo:
4 |
5 | | Command | Contributor |
6 | |---|---|
7 | | `show ip route` | [aaakpinar](https://github.com/aaakpinar) |
8 | | `show ip interface brief` | [aaakpinar](https://github.com/aaakpinar) |
9 | | `show ip bgp summary` | [aaakpinar](https://github.com/aaakpinar) |
10 | | `show lldp neighbors` | [shashsha09](https://github.com/shashsha09) |
11 | | `show mac address-table` | [michelredondo](https://github.com/michelredondo) |
12 |
13 | ## Testing
14 |
15 | Deploy the EVPN lab. Login to any leaf or spine node using `cnxuser/cnxuser` and try any of the above commands.
16 |
--------------------------------------------------------------------------------
/cisco-nx/ip/ip_bgp_report.py:
--------------------------------------------------------------------------------
1 | """
2 | CLI Plugin for SR Linux for Cisco NXOS-style BGP Summary Command
3 | Author: Alperen Akpinar
4 | """
5 | from srlinux.syntax import Syntax
6 | from srlinux.location import build_path
7 | from srlinux.mgmt.cli import KeyCompleter
8 | import datetime
9 |
10 | class IpBgpReport:
11 | """Handles the 'show ip bgp summary' command functionality."""
12 |
13 | # Class level constants
14 | PATH_TEMPLATES = {
15 | 'bgp_instance': '/network-instance[name={network_instance}]/protocols/bgp',
16 | }
17 |
18 | BGP_STATE_MAP = {
19 | 'idle': 'Idle',
20 | 'connect': 'Connect',
21 | 'active': 'Active',
22 | 'opensent': 'OpenSent',
23 | 'openconfirm': 'OpenConfirm',
24 | 'established': 'Established'
25 | }
26 |
27 | def show_bgp_summary(self, state, output, network_instance='default'):
28 | """Main function to display BGP summary"""
29 | # Get BGP instance data
30 | try:
31 | bgp_data = self._get_bgp_data(state, network_instance)
32 |
33 | # Silently exit if no data found (don't print error messages)
34 | if not bgp_data:
35 | return
36 |
37 | if not self._has_bgp_config(bgp_data):
38 | return
39 |
40 | # Print header and neighbor data
41 | self._print_bgp_header(bgp_data, network_instance)
42 | neighbors = self._get_neighbor_data(bgp_data)
43 |
44 | if neighbors:
45 | self._print_neighbor_table(neighbors)
46 | else:
47 | # No neighbors - exit silently
48 | pass
49 |
50 | except Exception as e:
51 | # Silent error handling - don't print errors
52 | pass
53 |
54 | def _get_bgp_data(self, state, network_instance):
55 | """Get BGP instance data"""
56 | try:
57 | path = build_path(self.PATH_TEMPLATES['bgp_instance'].format(
58 | network_instance=network_instance
59 | ))
60 | return state.server_data_store.get_data(path, recursive=True)
61 | except Exception:
62 | # Silently handle error
63 | return None
64 |
65 | def _has_bgp_config(self, bgp_data):
66 | """Check if BGP is configured"""
67 | if not bgp_data:
68 | return False
69 |
70 | try:
71 | network_instance = bgp_data.network_instance.get()
72 | if not network_instance:
73 | return False
74 |
75 | protocols = network_instance.protocols.get()
76 | if not protocols:
77 | return False
78 |
79 | return bool(protocols.bgp.get())
80 | except (AttributeError, Exception):
81 | return False
82 |
83 | def _print_bgp_header(self, bgp_data, network_instance):
84 | """Print BGP header information"""
85 | router_id = "0.0.0.0"
86 | local_as = "N/A"
87 |
88 | try:
89 | bgp = bgp_data.network_instance.get().protocols.get().bgp.get()
90 | if hasattr(bgp, 'router_id') and bgp.router_id:
91 | router_id = bgp.router_id
92 |
93 | if hasattr(bgp, 'autonomous_system') and bgp.autonomous_system:
94 | local_as = bgp.autonomous_system
95 | except (AttributeError, Exception):
96 | pass
97 |
98 | print(f"BGP summary information for VRF {network_instance}, address family IPv4 Unicast")
99 | print(f"BGP router identifier {router_id}, local AS number {local_as}\n")
100 |
101 | # Print column headers
102 | print("Neighbor V AS MsgRcvd MsgSent InQ OutQ Up/Down State/PfxRcd")
103 | print("-" * 75)
104 |
105 | def _get_neighbor_data(self, bgp_data):
106 | """Get BGP neighbor data"""
107 | neighbors = []
108 |
109 | try:
110 | bgp = bgp_data.network_instance.get().protocols.get().bgp.get()
111 | if not hasattr(bgp, 'neighbor'):
112 | return neighbors
113 |
114 | for neighbor in bgp.neighbor.items():
115 | if not neighbor:
116 | continue
117 |
118 | # Extract neighbor data with safe defaults
119 | peer_address = neighbor.peer_address
120 | peer_as = "?"
121 | if hasattr(neighbor, 'peer_as'):
122 | peer_as = str(neighbor.peer_as)
123 |
124 | # Get session state - be cautious about case sensitivity
125 | session_state = "Idle"
126 | state_lower = None
127 | if hasattr(neighbor, 'session_state'):
128 | state_lower = neighbor.session_state.lower() if neighbor.session_state else None
129 | # Map to display format with proper capitalization
130 | session_state = self.BGP_STATE_MAP.get(state_lower, "Idle")
131 |
132 | # Get message statistics
133 | messages_received = 0
134 | messages_sent = 0
135 |
136 | if hasattr(neighbor, 'received_messages'):
137 | rm = neighbor.received_messages.get()
138 | if rm and hasattr(rm, 'total_messages'):
139 | messages_received = rm.total_messages
140 |
141 | if hasattr(neighbor, 'sent_messages'):
142 | sm = neighbor.sent_messages.get()
143 | if sm and hasattr(sm, 'total_messages'):
144 | messages_sent = sm.total_messages
145 |
146 | # Get prefix information
147 | prefixes_received = 0
148 | if hasattr(neighbor, 'afi_safi'):
149 | for afi_safi in neighbor.afi_safi.items():
150 | if not afi_safi:
151 | continue
152 |
153 | if afi_safi.afi_safi_name == 'ipv4-unicast' and hasattr(afi_safi, 'received_routes'):
154 | prefixes_received = afi_safi.received_routes
155 | break
156 |
157 | # Format uptime
158 | uptime = self._format_uptime(neighbor)
159 |
160 | neighbor_info = {
161 | 'peer_address': peer_address,
162 | 'peer_as': peer_as,
163 | 'msg_rcvd': messages_received,
164 | 'msg_sent': messages_sent,
165 | 'state': session_state,
166 | 'state_lower': state_lower,
167 | 'pfx_received': prefixes_received,
168 | 'up_time': uptime
169 | }
170 | neighbors.append(neighbor_info)
171 |
172 | except Exception:
173 | # Silent error handling
174 | pass
175 |
176 | return neighbors
177 |
178 | def _print_neighbor_table(self, neighbors):
179 | """Print formatted neighbor table"""
180 | for neighbor in sorted(neighbors, key=lambda x: str(x.get('peer_address', ''))):
181 | # Show the correct format based on BGP state
182 | # For established neighbors, show the prefix count
183 | # For non-established, show the state
184 | if neighbor.get('state_lower') == 'established':
185 | state_display = str(neighbor.get('pfx_received', 0))
186 | else:
187 | state_display = neighbor.get('state', 'Idle')
188 |
189 | print(f"{neighbor['peer_address']:<14} 4 {neighbor['peer_as']:<6} "
190 | f"{neighbor['msg_rcvd']:<9} {neighbor['msg_sent']:<9} "
191 | f"0 0 {neighbor['up_time']:<9} {state_display}")
192 |
193 | def _format_uptime(self, neighbor):
194 | """Format uptime for display with safer parsing"""
195 | # Default for non-established sessions
196 | if not hasattr(neighbor, 'session_state') or neighbor.session_state != 'established':
197 | return "never"
198 |
199 | # Check if last_established exists and has a value
200 | if not hasattr(neighbor, 'last_established') or not neighbor.last_established:
201 | return "never"
202 |
203 | try:
204 | # Safer parsing that handles potential format variations
205 | timestamp_str = neighbor.last_established
206 | if '(' in timestamp_str:
207 | timestamp_str = timestamp_str.split('(')[0].strip()
208 |
209 | # Handle ISO format timestamps with timezone information
210 | if 'Z' in timestamp_str:
211 | timestamp_str = timestamp_str.replace('Z', '+00:00')
212 |
213 | last_established_time = datetime.datetime.fromisoformat(timestamp_str)
214 | current_time = datetime.datetime.now(datetime.timezone.utc)
215 |
216 | # Handle potential timezone differences
217 | if last_established_time.tzinfo is None:
218 | last_established_time = last_established_time.replace(tzinfo=datetime.timezone.utc)
219 |
220 | uptime = current_time - last_established_time
221 |
222 | # Format the uptime string
223 | days = uptime.days
224 | hours = uptime.seconds // 3600
225 | minutes = (uptime.seconds % 3600) // 60
226 |
227 | if days > 0:
228 | return f"{days}d{hours}h"
229 | elif hours > 0:
230 | return f"{hours}h{minutes}m"
231 | else:
232 | return f"{minutes}m"
233 |
234 | except Exception:
235 | # Fall back to "never" if there's any parsing error
236 | return "never"
--------------------------------------------------------------------------------
/cisco-nx/ip/ip_interface_report.py:
--------------------------------------------------------------------------------
1 | """
2 | CLI Plugin for SR Linux for Cisco-style IP Interface Command
3 | Provides alternate command syntax for interface information
4 | Author: Alperen Akpinar
5 | Email: alperen.akpinar@nokia.com
6 | """
7 | from srlinux.location import build_path
8 | from srlinux.data import ColumnFormatter, Data, Borders, Alignment, Border
9 | from srlinux.schema import FixedSchemaRoot
10 |
11 | class IpInterfaceReport:
12 | def _get_schema(self):
13 | root = FixedSchemaRoot()
14 | interfaces = root.add_child(
15 | 'interfaces',
16 | fields=['header']
17 | )
18 | interfaces.add_child(
19 | 'interface', # This must be lowercase
20 | key='Interface', # This is what shows up in output
21 | fields=[
22 | 'IP-Address',
23 | 'Interface-Status',
24 | 'Protocol-Status',
25 | 'VRF'
26 | ]
27 | )
28 | return root
29 |
30 | def _fetch_state(self, state):
31 | # Get interface data with recursive=True to get all nested data including IP addresses
32 | interface_path = build_path('/interface[name=*]')
33 | self.interface_data = state.server_data_store.get_data(interface_path, recursive=True)
34 |
35 | # Get network-instance interface mapping
36 | ni_path = build_path('/network-instance[name=*]/interface[name=*]')
37 | self.ni_data = state.server_data_store.get_data(ni_path, recursive=True)
38 |
39 | def _get_interface_vrf(self, interface_name):
40 | # Normalize interface name for matching
41 | normalized_name = interface_name.replace('Ethernet', 'ethernet-')
42 |
43 | # If no network instance data, return empty string
44 | if not hasattr(self.ni_data, 'network_instance'):
45 | return ""
46 |
47 | # Iterate through network instances
48 | for ni in self.ni_data.network_instance.items():
49 | # Check if this network instance has interfaces
50 | if hasattr(ni, 'interface'):
51 | for intf in ni.interface.items():
52 | # Compare normalized interface names, including subinterface
53 | if intf.name == normalized_name or intf.name.startswith(normalized_name + '.'):
54 | return ni.name
55 |
56 | # If no VRF found
57 | return ""
58 |
59 | def _format_interface_name(self, base_name, subindex=None):
60 | if base_name.startswith('ethernet-'):
61 | name = f"Ethernet{base_name[9:]}"
62 | elif base_name.startswith('lo'):
63 | name = f"Loopback{base_name[2:]}"
64 | elif base_name.startswith('vlan'):
65 | name = f"Vlan{base_name[4:]}"
66 | else:
67 | name = base_name
68 |
69 | if subindex is not None and subindex != 0:
70 | return f"{name}.{subindex}"
71 | return name
72 |
73 | def _set_formatters(self, data):
74 | formatter = ColumnFormatter(
75 | borders=Borders.Nothing,
76 | horizontal_alignment={
77 | 'Interface': Alignment.Left,
78 | 'IP-Address': Alignment.Left,
79 | 'Interface-Status': Alignment.Left,
80 | 'Protocol-Status': Alignment.Left,
81 | 'VRF': Alignment.Center
82 | },
83 | widths={
84 | 'Interface': 16, # Fixed width for Interface
85 | 'IP-Address': 15, # Enough for IPs like 192.168.100.1/24
86 | 'Interface-Status': 16, # Wide enough for 'admin down' text
87 | 'Protocol-Status': 16, # Wide enough for 'up' or 'down'
88 | 'VRF': 15 # Adjust as needed
89 | }
90 | )
91 |
92 | # Apply borders correctly
93 | bordered_formatter = Border(
94 | formatter,
95 | position=Border.Above | Border.Below, # Add borders above and below
96 | character='-' # Border character
97 | )
98 |
99 | data.set_formatter('/interfaces/interface', bordered_formatter)
100 |
101 | def _populate_data(self, result, state):
102 | result.synchronizer.flush_fields(result)
103 | data = result.interfaces.create()
104 | data.header = ""
105 | self._fetch_state(state)
106 | processed_interfaces = set() # Change to a set for faster lookup
107 |
108 | # Process all interfaces
109 | for interface in self.interface_data.interface.items():
110 | base_name = interface.name
111 |
112 | if hasattr(interface, 'subinterface'):
113 | for subif in interface.subinterface.items():
114 | intf_name = self._format_interface_name(base_name, subif.index)
115 |
116 | # Use set to check and prevent duplicates
117 | if intf_name in processed_interfaces:
118 | continue
119 | processed_interfaces.add(intf_name)
120 |
121 | # Change this block to prevent multiple data_child creation
122 | try:
123 | data_child = data.interface.create(intf_name)
124 | except Exception as e:
125 | # If interface already exists, skip
126 | print(f"Skipping duplicate interface: {intf_name}")
127 | continue
128 |
129 | # Rest of the code remains the same
130 | ip_address = "unassigned"
131 | if hasattr(subif, 'ipv4'):
132 | ipv4 = subif.ipv4.get()
133 | try:
134 | for addr in ipv4.address.items():
135 | full_prefix = getattr(addr, 'ip_prefix', 'unassigned')
136 | ip_address = full_prefix.split('/')[0] if full_prefix != 'unassigned' else full_prefix
137 | break
138 | except Exception:
139 | pass
140 |
141 | # Get states
142 | admin_state = getattr(subif, 'admin_state', 'disable')
143 | oper_state = getattr(subif, 'oper_state', 'down')
144 |
145 | if admin_state == "enable":
146 | if oper_state == "up":
147 | intf_status = "up"
148 | proto_status = "up"
149 | else:
150 | intf_status = "up"
151 | proto_status = "down"
152 | else:
153 | intf_status = "admin down"
154 | proto_status = "down"
155 |
156 | data_child.ip_address = ip_address
157 | data_child.interface_status = intf_status
158 | data_child.protocol_status = proto_status
159 | data_child.vrf = self._get_interface_vrf(intf_name)
160 | data_child.synchronizer.flush_fields(data_child)
161 |
162 | # Handle unconfigured interfaces
163 | if base_name not in processed_interfaces:
164 | intf_name = self._format_interface_name(base_name)
165 |
166 | # Check if interface already exists to prevent duplicate
167 | if intf_name not in processed_interfaces:
168 | processed_interfaces.add(intf_name)
169 |
170 | data_child = data.interface.create(intf_name)
171 | data_child.ip_address = "unassigned"
172 | data_child.interface_status = "admin down"
173 | data_child.protocol_status = "down"
174 | data_child.vrf = ""
175 | data_child.synchronizer.flush_fields(data_child)
176 |
177 | result.synchronizer.flush_children(result.interfaces)
178 | return result
179 |
180 | def show_interfaces_brief(self, state, output):
181 | """Main function to display interface brief"""
182 | result = Data(self._get_schema())
183 | self._set_formatters(result)
184 | with output.stream_data(result):
185 | self._populate_data(result, state)
--------------------------------------------------------------------------------
/cisco-nx/ip/ip_route_report.py:
--------------------------------------------------------------------------------
1 | """
2 | CLI Plugin for SR Linux for Cisco-style IP Route Command
3 | Provides alternate command syntax for route information
4 | Author: Alperen Akpinar
5 | Email: alperen.akpinar@nokia.com
6 | """
7 | from srlinux.syntax import Syntax
8 | from srlinux.location import build_path
9 | from srlinux.mgmt.cli import KeyCompleter
10 | import datetime
11 | import ipaddress
12 | from srlinux.schema import FixedSchemaRoot
13 |
14 | class IpRouteReport:
15 | """Handles the 'ip route' command functionality."""
16 |
17 | # Class level constants
18 | ROUTE_CODES = {
19 | 'aggregate': 'Ag',
20 | 'arp-nd': 'Ar',
21 | 'bgp': 'B',
22 | 'bgp-label': 'BL',
23 | 'bgp-evpn': 'BE',
24 | 'bgp-vpn': 'BV',
25 | 'dhcp': 'D',
26 | 'gribi': 'G',
27 | 'host': 'H',
28 | 'isis': 'IS',
29 | 'linux': 'Li',
30 | 'ndk1': 'N1',
31 | 'ndk2': 'N2',
32 | 'ospfv2': 'O',
33 | 'ospfv3': 'O',
34 | 'static': 'S',
35 | }
36 |
37 | PATH_TEMPLATES = {
38 | 'routes': '/network-instance[name={network_instance}]/route-table/ipv4-unicast/route',
39 | 'next_hop_group': '/network-instance[name={network_instance}]/route-table/next-hop-group[index={nhg_id}]',
40 | 'next_hop': '/network-instance[name={network_instance}]/route-table/next-hop[index={nh_id}]',
41 | 'route_detail': '/network-instance[name={network_instance}]/route-table/ipv4-unicast/route[ipv4-prefix={ip_prefix}][route-type={route_type}][route-owner={route_owner}]'
42 | }
43 |
44 | def _show_routes(self, state, output, network_instance):
45 | """Main function to display routes"""
46 | self._print_header()
47 |
48 | if network_instance != 'default':
49 | print(f'Routing Table: VRF {network_instance}\n')
50 |
51 | # Get all routes
52 | routes_data = self._get_routes_data(state, network_instance)
53 | if not routes_data:
54 | self._print_not_found_message(network_instance)
55 | return
56 |
57 | route_entries = self._process_routes(state, network_instance, routes_data)
58 | self._display_routes(route_entries, network_instance)
59 |
60 | def _print_header(self):
61 | """Print command header and legend"""
62 | print('''Codes: C - connected, L - local, S - static, B - BGP, O - OSPF, IS - IS-IS,
63 | Ag - aggregate, Ar - arp-nd, BL - bgp-label, BE - bgp-evpn, BV - bgp-vpn
64 | D - dhcp, G - gribi, H - host, Li - linux, N1/N2 - ndk\n''')
65 |
66 | def _print_not_found_message(self, network_instance):
67 | """Print error message when VRF/routes not found"""
68 | print(f"Error: VRF '{network_instance}' not found or no routes present.")
69 |
70 | def _get_routes_data(self, state, network_instance):
71 | """Get routes with proper error handling"""
72 | try:
73 | routes_path = build_path(self.PATH_TEMPLATES['routes'].format(network_instance=network_instance))
74 | return state.server_data_store.get_data(routes_path, recursive=True)
75 | except Exception as e:
76 | return None
77 |
78 | def _process_routes(self, state, network_instance, routes_data):
79 | """Process all routes and return sorted entries"""
80 | all_routes = []
81 |
82 | for ni in routes_data.network_instance.items():
83 | route_table = ni.route_table.get()
84 | ipv4_unicast = route_table.ipv4_unicast.get()
85 |
86 | for route in ipv4_unicast.route.items():
87 | route_entry = self._create_route_entry(route)
88 |
89 | if route.route_type in ['local', 'connected']:
90 | self._process_connected_route(state, network_instance, route, route_entry)
91 | else:
92 | self._process_regular_route(state, network_instance, route, route_entry)
93 |
94 | all_routes.append(route_entry)
95 |
96 | return sorted(all_routes, key=lambda x: int(ipaddress.ip_network(x['prefix']).network_address))
97 |
98 | def _create_route_entry(self, route):
99 | """Create basic route entry with standard fields"""
100 | return {
101 | 'prefix': route.ipv4_prefix,
102 | 'code': self._get_route_code(route.route_type, route.route_owner),
103 | 'type': route.route_type,
104 | 'owner': route.route_owner,
105 | 'next_hops': [],
106 | 'uptime': self._format_uptime(route),
107 | 'interface': None,
108 | 'preference': route.preference,
109 | 'metric': route.metric
110 | }
111 |
112 | def _process_connected_route(self, state, network_instance, route, route_entry):
113 | """Process connected/local route types"""
114 | next_hop_group = getattr(route, 'next_hop_group', None)
115 | if next_hop_group:
116 | try:
117 | next_hops = self._get_next_hops(state, network_instance, next_hop_group)
118 | for nh in next_hops:
119 | if nh.get('interface'):
120 | route_entry['interface'] = nh['interface']
121 | break
122 | except Exception as e:
123 | pass
124 |
125 | def _process_regular_route(self, state, network_instance, route, route_entry):
126 | """Process non-connected route types"""
127 | next_hop_group = getattr(route, 'next_hop_group', None)
128 | if next_hop_group:
129 | try:
130 | route_entry['next_hops'] = self._get_next_hops(state, network_instance, next_hop_group)
131 | except Exception as e:
132 | pass
133 |
134 | def _get_next_hops(self, state, network_instance, next_hop_group):
135 | """Get next-hop information for a route"""
136 | next_hops = []
137 | try:
138 | nhg_path = build_path(self.PATH_TEMPLATES['next_hop_group'].format(
139 | network_instance=network_instance,
140 | nhg_id=next_hop_group
141 | ))
142 | nhg_data = state.server_data_store.get_data(nhg_path, recursive=True)
143 |
144 | for ni in nhg_data.network_instance.items():
145 | nhg = ni.route_table.get().next_hop_group.get()
146 | for nh in nhg.next_hop.items():
147 | if hasattr(nh, 'next_hop') and getattr(nh, 'resolved', False):
148 | next_hop_info = self._get_next_hop_info(state, network_instance, nh.next_hop)
149 | if next_hop_info:
150 | next_hops.append(next_hop_info)
151 | except Exception as e:
152 | pass
153 |
154 | return next_hops
155 |
156 | def _get_next_hop_info(self, state, network_instance, next_hop_id):
157 | """Get detailed next-hop information"""
158 | try:
159 | nh_path = build_path(self.PATH_TEMPLATES['next_hop'].format(
160 | network_instance=network_instance,
161 | nh_id=next_hop_id
162 | ))
163 | nh_data = state.server_data_store.get_data(nh_path, recursive=True)
164 | next_hop = nh_data.network_instance.get().route_table.get().next_hop.get()
165 |
166 | subinterface = None
167 | if getattr(next_hop, 'type', '') == 'indirect' and hasattr(next_hop, 'resolving_route'):
168 | subinterface = self._get_resolving_route_interface(state, network_instance, next_hop.resolving_route)
169 | else:
170 | subinterface = getattr(next_hop, 'subinterface', None)
171 |
172 | if hasattr(next_hop, 'ip_address'):
173 | return {
174 | 'ip': next_hop.ip_address,
175 | 'interface': subinterface or ''
176 | }
177 | except Exception as e:
178 | pass
179 | return None
180 |
181 | def _get_resolving_route_interface(self, state, network_instance, resolving_route):
182 | """Follow next-hop chain recursively until finding the interface"""
183 | try:
184 | resolving_route_data = resolving_route.get()
185 | route_path = build_path(self.PATH_TEMPLATES['route_detail'].format(
186 | network_instance=network_instance,
187 | ip_prefix=resolving_route_data.ip_prefix,
188 | route_type=resolving_route_data.route_type,
189 | route_owner=resolving_route_data.route_owner
190 | ))
191 |
192 | route_data = state.server_data_store.get_data(route_path, recursive=True)
193 | nhg_id = route_data.network_instance.get().route_table.get().ipv4_unicast.get().route.get().next_hop_group
194 |
195 | next_hops = self._get_next_hops(state, network_instance, nhg_id)
196 | for nh in next_hops:
197 | if nh.get('interface'):
198 | return nh['interface']
199 |
200 | except Exception:
201 | pass
202 | return None
203 |
204 | def _format_uptime(self, route):
205 | """Extract and format uptime for a route"""
206 | try:
207 | if not getattr(route, 'active', False):
208 | return ""
209 |
210 | if route.route_type == 'bgp':
211 | try:
212 | last_update_str = route.last_app_update
213 | if last_update_str:
214 | timestamp = last_update_str.split(' (')[0]
215 | last_update_time = datetime.datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
216 | current_time = datetime.datetime.now(datetime.timezone.utc)
217 | uptime = current_time - last_update_time
218 | days, seconds = uptime.days, uptime.seconds
219 | hours = seconds // 3600
220 | if days > 0:
221 | return f"{days}d{hours:02d}h"
222 | else:
223 | minutes, seconds = divmod(seconds % 3600, 60)
224 | return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
225 | except Exception:
226 | pass
227 | return ""
228 | except Exception:
229 | return ""
230 |
231 | def _get_route_code(self, route_type, route_owner):
232 | """Get single character code for route type"""
233 | if route_type == 'host':
234 | return 'L'
235 | if route_type == 'local':
236 | return 'C'
237 | return self.ROUTE_CODES.get(route_type.lower(), '?')
238 |
239 | def _display_routes(self, routes, network_instance):
240 | """Display formatted routes"""
241 | # Check for default route
242 | default_route_exists = any(route['prefix'] == '0.0.0.0/0' for route in routes)
243 | if not default_route_exists:
244 | print("Gateway of last resort is not set")
245 |
246 | for route in routes:
247 | self._display_route(route)
248 |
249 | def _display_route(self, route):
250 | """Display a single route entry"""
251 | if route['interface']:
252 | print(f"{route['code']} {route['prefix']} is directly connected, {route['interface']}")
253 | elif route['code'] == 'L':
254 | print(f"{route['code']} {route['prefix']} is directly connected")
255 | elif not route['next_hops']:
256 | print(f"{route['code']} {route['prefix']}")
257 | else:
258 | self._display_route_with_next_hops(route)
259 |
260 | def _display_route_with_next_hops(self, route):
261 | """Display route with its next-hops"""
262 | if len(route['next_hops']) > 1:
263 | # First next-hop
264 | first_hop = route['next_hops'][0]
265 | self._print_next_hop(route, first_hop, is_first=True)
266 |
267 | # Additional next-hops
268 | for next_hop in route['next_hops'][1:]:
269 | self._print_next_hop(route, next_hop, is_first=False)
270 | else:
271 | # Single next-hop
272 | self._print_next_hop(route, route['next_hops'][0], is_first=True)
273 |
274 | def _print_next_hop(self, route, next_hop, is_first):
275 | """Print a single next-hop entry"""
276 | if is_first:
277 | line = f"{route['code']} {route['prefix']} [{route['preference']}/{route['metric']}] via {next_hop['ip']}"
278 | else:
279 | line = f" [{route['preference']}/{route['metric']}] via {next_hop['ip']}"
280 |
281 | if route['uptime']:
282 | line += f", {route['uptime']}"
283 | if next_hop['interface']:
284 | line += f", {next_hop['interface']}"
285 |
286 | print(line)
--------------------------------------------------------------------------------
/cisco-nx/plugins/Cisco_nxos_lldp_neighbor:
--------------------------------------------------------------------------------
1 | """
2 | CLI Plugin for Cisco-style LLDP neighbors
3 | Author: Shashi Sharma
4 | Email: Shashi.Sharma@nokia.com
5 | """
6 |
7 | from srlinux.mgmt.cli import CliPlugin
8 | from srlinux.syntax import Syntax
9 | from srlinux.location import build_path
10 | from srlinux.data import Data, ColumnFormatter, Borders, Formatter
11 | from srlinux.schema import FixedSchemaRoot
12 | import sys
13 | import logging
14 |
15 | # Setup basic logging
16 | logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
17 | logger = logging.getLogger("LLDPPlugin")
18 |
19 | class Plugin(CliPlugin):
20 | def load(self, cli, **_kwargs):
21 | """Load the LLDP plugin and define the CLI command structure."""
22 | logger.debug("Loading LLDP plugin")
23 | lldp_cmd = cli.show_mode.add_command(Syntax('lldp', help='LLDP info'))
24 | lldp_cmd.add_command(
25 | Syntax('neighbor', help='Show LLDP neighbors Cisco-style'),
26 | callback=self._show_lldp_neighbor,
27 | schema=self._get_schema()
28 | )
29 |
30 | def _get_schema(self):
31 | """Define the schema for the LLDP neighbor data."""
32 | logger.debug("Setting up schema")
33 | root = FixedSchemaRoot()
34 | root.add_child('neighbors', key='Local_Intf', fields=['Device_ID', 'Hold_time', 'Capability', 'Port_ID'])
35 | return root
36 |
37 | def _show_lldp_neighbor(self, state, arguments, output, **_kwargs):
38 | """Handle the 'show lldp neighbor' command."""
39 | logger.debug("Running show lldp neighbor")
40 | lldp_data = self._get_lldp_data(state)
41 | if not lldp_data:
42 | sys.stdout.write("No LLDP neighbors found.\n")
43 | output.print_line("\n--------------------------------------")
44 | output.print_line("Try SR Linux command: show system lldp neighbor")
45 | return
46 |
47 | result = Data(arguments.schema)
48 | self._set_formatters(result)
49 | total = self._populate_data(result, lldp_data, state)
50 | output.print_line("Capability codes: (R) Router, (B) Bridge, (W) WLAN AP, (S) Switch, (O) Other")
51 | output.print_line("")
52 | with output.stream_data(result):
53 | pass
54 | output.print_line("\n--------------------------------------")
55 | output.print_line("Try SR Linux command: show system lldp neighbor")
56 |
57 | def _get_lldp_data(self, state):
58 | """Fetch LLDP neighbor data from the system."""
59 | path = build_path('/system/lldp/interface[name=*]/neighbor[id=*]')
60 | logger.debug(f"Grabbing LLDP data from: {path}")
61 | try:
62 | data = state.server_data_store.get_data(path, recursive=True)
63 | return data
64 | except Exception as e:
65 | logger.error(f"Couldn’t fetch LLDP data: {e}")
66 | return None
67 |
68 | def _get_hold_time(self, state):
69 | """Calculate the hold time, using defaults if configuration attributes are missing."""
70 | default_hello_timer = 3
71 | default_hold_multiplier = 40
72 | default_hold_time = default_hello_timer * default_hold_multiplier # 120
73 |
74 | config_path = build_path('/system/lldp')
75 | try:
76 | config = state.server_data_store.get_data(config_path, recursive=True)
77 | if hasattr(config, 'hello_timer') and hasattr(config, 'hold_multiplier'):
78 | hello_timer = config.hello_timer
79 | hold_multiplier = config.hold_multiplier
80 | hold_time = hello_timer * hold_multiplier
81 | return str(hold_time)
82 | else:
83 | return str(default_hold_time)
84 | except Exception:
85 | return str(default_hold_time)
86 |
87 | def _get_capability(self, neighbor):
88 | """Determine the capability code based on neighbor's capability child node."""
89 | if not hasattr(neighbor, 'capability') or not neighbor.capability:
90 | logger.debug("No capability data found, defaulting to Switch")
91 | return 'S' # Default to Switch
92 |
93 | capability = neighbor.capability
94 | # Log the capability object's attributes and content for debugging
95 | logger.debug(f"Capability attributes: {dir(capability)}")
96 | logger.debug(f"Capability content: {capability.__dict__ if hasattr(capability, '__dict__') else str(capability)}")
97 |
98 | # Check for a 'type' field
99 | cap_type = getattr(capability, 'type', None)
100 | if cap_type:
101 | if cap_type == 'router':
102 | return 'R'
103 | elif cap_type == 'bridge' or cap_type == 'mac-bridge':
104 | return 'B'
105 | elif cap_type == 'wlan-access-point' or cap_type == 'wlan_access_point':
106 | return 'W'
107 | elif cap_type == 'switch':
108 | return 'S'
109 |
110 | # Check for boolean attributes or flags
111 | if hasattr(capability, 'router') and capability.router:
112 | return 'R'
113 | elif hasattr(capability, 'bridge') and capability.bridge:
114 | return 'B'
115 | elif hasattr(capability, 'mac_bridge') and capability.mac_bridge:
116 | return 'B'
117 | elif hasattr(capability, 'wlan_access_point') and capability.wlan_access_point:
118 | return 'W'
119 | elif hasattr(capability, 'switch') and capability.switch:
120 | return 'S'
121 |
122 | logger.debug("No recognized capabilities, defaulting to Switch")
123 | return 'S' # Default to Switch instead of Other to match old code
124 |
125 | def _populate_data(self, data_root, lldp_data, state):
126 | """Populate the LLDP neighbor data into the output structure."""
127 | logger.debug("Filling in neighbor data")
128 | hold_time = self._get_hold_time(state)
129 | interfaces = lldp_data.get_descendants('/system/lldp/interface')
130 | total = 0
131 |
132 | for iface in interfaces:
133 | local_intf = getattr(iface, 'name', 'Unknown')
134 | if not hasattr(iface, 'neighbor'):
135 | logger.debug(f"No neighbors on {local_intf}")
136 | continue
137 |
138 | for neighbor in iface.neighbor.items():
139 | device_id = getattr(neighbor, 'system_name', 'Unknown')
140 | capability = self._get_capability(neighbor)
141 | port_id = getattr(neighbor, 'port_id', 'Unknown')
142 |
143 | logger.debug(f"Adding {device_id} for {local_intf}")
144 | row = data_root.neighbors.create(local_intf)
145 | row.device_id = device_id
146 | row.hold_time = hold_time
147 | row.capability = capability
148 | row.port_id = port_id
149 | total += 1
150 |
151 | return total
152 |
153 | def _set_formatters(self, data):
154 | """Set up the table formatting for Cisco-style output."""
155 | logger.debug("Setting up table look")
156 | data.set_formatter('/neighbors', CiscoTableFormatter())
157 | data.set_formatter(
158 | '/neighbors',
159 | ColumnFormatter(
160 | widths={'Local_Intf': 13, 'Device_ID': 11, 'Hold_time': 9, 'Capability': 10, 'Port_ID': 12},
161 | borders=Borders.Nothing
162 | )
163 | )
164 |
165 | class CiscoTableFormatter(Formatter):
166 | def iter_format(self, entry, max_width):
167 | """Format the LLDP neighbor data in a Cisco-style table."""
168 | logger.debug("Formatting table in CiscoTableFormatter")
169 | yield " {} {} {} {} {}".format(
170 | "Local_Intf",
171 | "Device_ID",
172 | "Hold_time",
173 | "Capability",
174 | "Port_ID"
175 | )
176 | for row in entry.neighbors.values():
177 | yield "{} {} {} {} {}".format(
178 | row.local_intf.ljust(13),
179 | row.device_id.ljust(11),
180 | row.hold_time.ljust(9),
181 | row.capability.ljust(10),
182 | row.port_id.ljust(12)
183 | )
184 |
185 | if __name__ == '__main__':
186 | Plugin()
187 |
--------------------------------------------------------------------------------
/cisco-nx/plugins/ip_reports.py:
--------------------------------------------------------------------------------
1 | """
2 | CLI Plugin for Cisco-style IP Commands
3 | Author: Alperen Akpinar
4 | Email: alperen.akpinar@nokia.com
5 | """
6 | from srlinux.mgmt.cli import CliPlugin, ExecuteError, KeyCompleter
7 | from srlinux.syntax import Syntax
8 | import sys
9 | import os
10 |
11 | # Try potential base directories
12 | potential_paths = [
13 | os.path.expanduser('~/cli'),
14 | '/etc/opt/srlinux/cli'
15 | ]
16 |
17 | # Find the first valid path
18 | import_base = None
19 | for path in potential_paths:
20 | if os.path.exists(path):
21 | import_base = path
22 | break
23 |
24 | if import_base is None:
25 | raise ImportError("Could not find a valid CLI plugin base directory")
26 |
27 | # Construct the import path
28 | import_path = os.path.join(import_base, "ip")
29 |
30 | # Add to Python path if not already present
31 | if import_path not in sys.path:
32 | sys.path.insert(0, import_path)
33 |
34 | from ip_route_report import IpRouteReport
35 | from ip_interface_report import IpInterfaceReport
36 | from ip_bgp_report import IpBgpReport
37 |
38 | class Plugin(CliPlugin):
39 | """Cisco-style IP CLI plugin."""
40 | def load(self, cli, **_kwargs):
41 | # Create top-level ip command
42 | ip_cmd = cli.show_mode.add_command(Syntax('ip'))
43 |
44 | # Add route command
45 | route_cmd = ip_cmd.add_command(
46 | Syntax('route'),
47 | callback=self._show_ip_route
48 | )
49 |
50 | # Add 'vrf' subcommand with network-instance completion for route
51 | route_cmd.add_command(
52 | Syntax('vrf')
53 | .add_unnamed_argument('vrf_name', suggestions=KeyCompleter('/network-instance[name=*]')),
54 | callback=self._show_vrf_route,
55 | update_location=False
56 | )
57 |
58 | # Add interface command
59 | interface_cmd = ip_cmd.add_command(
60 | Syntax('interface')
61 | )
62 |
63 | # Add interface brief subcommand
64 | interface_cmd.add_command(
65 | Syntax('brief', help='IP interface status and configuration'),
66 | callback=self._show_ip_interface_brief,
67 | update_location=False
68 | )
69 |
70 | # Add BGP commands
71 | bgp_cmd = ip_cmd.add_command(Syntax('bgp'))
72 |
73 | # Add BGP summary command
74 | bgp_cmd.add_command(
75 | Syntax('summary', help='BGP summary information'),
76 | callback=self._show_ip_bgp_summary,
77 | update_location=False
78 | )
79 |
80 | # Add BGP VRF command
81 | bgp_vrf_cmd = bgp_cmd.add_command(
82 | Syntax('vrf')
83 | .add_unnamed_argument('vrf_name', suggestions=KeyCompleter('/network-instance[name=*]'))
84 | )
85 |
86 | # Add summary command under VRF
87 | bgp_vrf_cmd.add_command(
88 | Syntax('summary', help='BGP summary for VRF'),
89 | callback=self._show_ip_bgp_vrf_summary,
90 | update_location=False
91 | )
92 |
93 | def _show_ip_route(self, state, arguments, output, **_kwargs):
94 | if state.is_intermediate_command:
95 | return
96 | IpRouteReport()._show_routes(state, output, network_instance='default')
97 | output.print_line(f'\nTry SR Linux command: show network-instance default route-table')
98 | def _show_vrf_route(self, state, arguments, output, **_kwargs):
99 | if state.is_intermediate_command:
100 | return
101 | network_instance = arguments.get('vrf_name')
102 | IpRouteReport()._show_routes(state, output, network_instance=network_instance)
103 | output.print_line(f'\nTry SR Linux command: show network-instance {network_instance} route-table')
104 |
105 | def _show_ip_interface_brief(self, state, arguments, output, **_kwargs):
106 | if state.is_intermediate_command:
107 | return
108 | IpInterfaceReport().show_interfaces_brief(state, output)
109 | output.print_line(f'\nTry SR Linux command: show interface brief')
110 |
111 | def _show_ip_bgp_summary(self, state, arguments, output, **_kwargs):
112 | if state.is_intermediate_command:
113 | return
114 | IpBgpReport().show_bgp_summary(state, output, network_instance='default')
115 | output.print_line(f'\nTry SR Linux command: show network-instance default protocols bgp neighbor')
116 |
117 | def _show_ip_bgp_vrf_summary(self, state, arguments, output, **_kwargs):
118 | if state.is_intermediate_command:
119 | return
120 | try:
121 | # The vrf_name is in the parent command group
122 | # Use the same pattern as netinst_summary_report.py
123 | network_instance = arguments.get('vrf', 'vrf_name')
124 | if not network_instance:
125 | network_instance = 'default'
126 | IpBgpReport().show_bgp_summary(state, output, network_instance=network_instance)
127 | output.print_line(f'\nTry SR Linux command: show network-instance {network_instance} protocols bgp neighbor')
128 | except Exception:
129 | # Silent error handling - don't print error messages for missing VRFs or no BGP config
130 | pass
--------------------------------------------------------------------------------
/cisco-nx/plugins/mac_reports.py:
--------------------------------------------------------------------------------
1 | """
2 | CLI Plugin for Cisco-like show "mac" commands
3 | Author: Miguel Redondo (Michel)
4 | Email: miguel.redondo_ferrero@nokia.com
5 |
6 | Provides the following commands:
7 | - show mac address-table
8 | - show mac address-table instance
9 | - show mac address-table vlan
10 | - show mac address-table interface
11 | - show mac address-table vni
12 |
13 | """
14 | from srlinux.mgmt.cli import CliPlugin, ExecuteError, KeyCompleter, MultipleKeyCompleters
15 | from srlinux.syntax import Syntax
16 | from srlinux.schema import FixedSchemaRoot
17 | from srlinux.mgmt.cli.cli_loader import CliLoader
18 |
19 | import sys
20 | import os
21 | import argparse
22 |
23 |
24 | # Try potential base directories
25 | potential_paths = [
26 | os.path.expanduser('~/cli'),
27 | '/etc/opt/srlinux/cli'
28 | ]
29 |
30 | # Find the first valid path
31 | import_base = None
32 | for path in potential_paths:
33 | if os.path.exists(path):
34 | import_base = path
35 | break
36 |
37 | if import_base is None:
38 | raise ImportError("Could not find a valid CLI plugin base directory")
39 |
40 | # Construct the import path
41 | import_path = os.path.join(import_base, "mac")
42 |
43 | # Add to Python path if not already present
44 | if import_path not in sys.path:
45 | sys.path.insert(0, import_path)
46 |
47 | from mac_address_table_report import MacAddressTableReport
48 |
49 | class Plugin(CliPlugin):
50 | """NXOS-like mac address-table CLI plugin."""
51 | def load(self, cli: CliLoader, arguments: argparse.Namespace) -> None:
52 | mac = cli.show_mode.add_command(Syntax('mac', help='Show MAC commands'))
53 |
54 | # Add address-table command as a subcommand
55 | mac_address_table = mac.add_command(
56 | Syntax('address-table', help='Show MAC Address Table'),
57 | callback=self._show_mac_address_table,
58 | schema=MacAddressTableReport().get_schema_instance()
59 | )
60 |
61 | # Add 'instance' subcommand with network-instance completion
62 | mac_address_table_instance = mac_address_table.add_command(
63 | Syntax('instance', help='Display information for a specified network-instance')
64 | .add_unnamed_argument('name', suggestions=KeyCompleter('/network-instance[name=*]')),
65 | callback=self._show_mac_address_table_instance,
66 | update_location=False,
67 | schema=MacAddressTableReport().get_schema_instance()
68 | )
69 |
70 | # Add 'vlan-id' subcommand with vlans completion
71 | mac_address_table_vlanid = mac_address_table.add_command(
72 | Syntax('vlan', help='Display MAC address learned on a specified VLAN')
73 | .add_unnamed_argument('value', suggestions=MultipleKeyCompleters(keycompleters=[KeyCompleter(path="/interface[name=*]/subinterface[index=*]/vlan/encap/single-tagged-range/low-vlan-id[range-low-vlan-id=*]"), KeyCompleter(path="/interface[name=*]/subinterface[index=*]/vlan/encap/single-tagged/vlan-id:")])),
74 | callback=self._show_mac_address_table_vlanid,
75 | update_location=False,
76 | schema=MacAddressTableReport().get_schema_instance()
77 | )
78 |
79 | # Add 'interface' subcommand with interface completion
80 | mac_address_table_interface = mac_address_table.add_command(
81 | Syntax('interface', help='Display MAC table for a specified interface')
82 | .add_unnamed_argument('name', suggestions=MultipleKeyCompleters(keycompleters=[KeyCompleter(path="/interface[name=*]"), KeyCompleter(path="/interface[name=*]/subinterface[index=*]/name:")])),
83 | callback=self._show_mac_address_table_interface,
84 | update_location=False,
85 | schema=MacAddressTableReport().get_schema_instance()
86 | )
87 |
88 | # Add 'vni' subcommand with interface completion
89 | mac_address_table_vni = mac_address_table.add_command(
90 | Syntax('vni', help='Display MAC table for a specified vni')
91 | .add_unnamed_argument('value', suggestions=KeyCompleter(path="/tunnel-interface[name=*]/vxlan-interface[index=*]/ingress/vni:")),
92 | callback=self._show_mac_address_table_vni,
93 | update_location=False,
94 | schema=MacAddressTableReport().get_schema_instance()
95 | )
96 |
97 | def _show_mac_address_table(self, state, arguments, output, **_kwargs):
98 | if state.is_intermediate_command:
99 | return
100 | MacAddressTableReport()._show_table_instance(state, output, arguments, **_kwargs)
101 |
102 | def _show_mac_address_table_instance(self, state, arguments, output, **_kwargs):
103 | if state.is_intermediate_command:
104 | return
105 | MacAddressTableReport()._show_table_instance(state, output, arguments, **_kwargs)
106 |
107 | def _show_mac_address_table_vlanid(self, state, arguments, output, **_kwargs):
108 | if state.is_intermediate_command:
109 | return
110 | MacAddressTableReport()._show_table_instance(state, output, arguments, **_kwargs)
111 |
112 | def _show_mac_address_table_interface(self, state, arguments, output, **_kwargs):
113 | if state.is_intermediate_command:
114 | return
115 | MacAddressTableReport()._show_table_instance(state, output, arguments, **_kwargs)
116 |
117 | def _show_mac_address_table_vni(self, state, arguments, output, **_kwargs):
118 | if state.is_intermediate_command:
119 | return
120 | MacAddressTableReport()._show_table_instance(state, output, arguments, **_kwargs)
121 |
122 |
--------------------------------------------------------------------------------
/juniper/README.md:
--------------------------------------------------------------------------------
1 | # Custom CLI Plugins for Juniper JUNOS
2 |
3 | The following CLI plugins are available in this repo:
4 |
5 | | Command | Contributor |
6 | |---|---|
7 | | `show interfaces` | [hendriksthomas](https://github.com/hendriksthomas) |
8 | | `show interfaces terse` | [hendriksthomas](https://github.com/hendriksthomas) |
9 | | `show interfaces brief` | [hendriksthomas](https://github.com/hendriksthomas) |
10 | | `show ethernet-switching table` | [michelredondo](https://github.com/michelredondo) |
11 |
12 | ## Testing
13 |
14 | Deploy the EVPN lab. Login to any leaf or spine node using `juser/juser` and try any of the above commands.
15 |
16 | > [!NOTE]
17 | > Some of these plugin scripts require other python scripts that are also copied into the `eth_switch` or `route` folder.
18 |
19 | ## `show_interfaces.py`
20 |
21 | This script introduces a custom command that allows the user to visualize state and configuration of interfaces in the SR Linux system in a manner similar to what is rendered by JunOS' `show interfaces` command.
22 |
23 | ### Command syntax (all options)
24 |
25 | ```
26 | /show interfaces
27 | /show interfaces terse
28 | /show interfaces terse
29 | /show interfaces brief
30 | /show interfaces brief
31 | ```
32 |
33 | Where `` is either omitted or identifies a (sub-)interface on the system.
34 |
35 |
36 | Example execution (terse)
37 |
38 | --{ running }--[ ]--
39 | A:admin@srl# show interfaces ethernet-1/3 terse
40 | Interface Admin Link Proto Local Remote
41 | ethernet-1/3 up up
42 | ethernet-1/3.0 up up inet 10.3.3.1/24
43 | inet6 fd00::3:3:1/104
44 | fd00::33:33:1/104
45 | fd00::333:333:1/104
46 | fd00::3333:3333:1/104
47 | fe80::1880:ff:feff:3/64
48 | ----------------------------------------------------------------------------------------------------
49 | Try SR Linux command: show interface
50 |
51 |
52 |
53 |
54 | Example execution (brief)
55 |
56 | --{ running }--[ ]--
57 | A:admin@srl# show interfaces brief ethernet-1/3
58 | Physical interface: ethernet-1/3, Enabled, Physical link is Up
59 | Link-level type: Ethernet, MTU: 9232, MRU: 9240, Unknown mode, Speed: 25G, Loopback: Disabled, Source filtering: N/A, Flow control: Disabled, Auto-negotiation: Enabled, Remote fault: Online
60 | Device flags : Present Running Up
61 | Interface flags: Up
62 | Link flags : None
63 |
64 |
65 | Logical interface ethernet-1/3.0
66 | Flags: Up Encapsulation: ENET2
67 | inet 10.3.3.1/24
68 | inet6 fd00::3:3:1/104
69 | fd00::33:33:1/104
70 | fd00::333:333:1/104
71 | fd00::3333:3333:1/104
72 | fe80::1880:ff:feff:3/64
73 |
74 | ----------------------------------------------------------------------------------------------------
75 | Try SR Linux command: show interface detail
76 |
77 |
78 |
79 |
80 | Example execution (regular)
81 |
82 | --{ running }--[ ]--
83 | A:admin@srl# show interfaces ethernet-1/3
84 | Physical interface: ethernet-1/3, Enabled, Physical link is Up
85 | Interface index: 81918, SNMP ifIndex: N/A
86 | Link-level type: Ethernet, MTU: 9232, MRU: 9240, Unknown mode, Speed: 25G, BPDU Error: N/A, Loop Detect PDU Error: N/A, Ethernet-Switching Error: N/A, MAC-REWRITE Error: N/A, Loopback: Disabled, Source filtering: N/A,Flow control: Disabled, Auto-negotiation: Enabled, Remote fault: Online
87 | Pad to minimum frame size: N/A
88 | Device flags : Present Running Up
89 | Interface flags: Up
90 | Link flags : None
91 | CoS queues : 8 supported, 8 maximum usable queues
92 | Current address: 1A:80:00:FF:00:03, Hardware address: 1A:80:00:FF:00:03
93 | Last flapped : 2025-04-17 11:40:48 UTC (0w0d 01:20 ago)
94 | Input rate : 0 bps (Uncalculated pps)
95 | Output rate : 0 bps (Uncalculated pps)
96 | Active alarms : N/A
97 | Active defects : N/A
98 | PCS statistics Seconds
99 | Bit errors 0
100 | Errored blocks 0
101 | Ethernet FEC statistics Errors
102 | FEC Corrected Errors N/A
103 | FEC Uncorrected Errors N/A
104 | FEC Corrected Errors Rate N/A
105 | FEC Uncorrected Errors Rate N/A
106 | Interface transmit statistics: Disabled
107 |
108 | Logical interface ethernet-1/3.0 (Index 65537) (SNMP ifIndex N/A)
109 | Flags: Up Encapsulation: ENET2
110 | Input packets : 55
111 | Output packets: 44
112 | Protocol inet, MTU: 1500
113 | Max nh cache: N/A, New hold nh limit: N/A, Curr nh cnt: 1, Curr new hold cnt: N/A, NH drop cnt: N/A
114 | Flags: Sendbcast-pkt-to-re
115 | Addresses, Flags: Primary Preferred
116 | Destination: 10.3.3.0/24, Local: 10.3.3.1, Broadcast: 10.3.3.255
117 | Protocol inet6, MTU: 1500
118 | Max nh cache: N/A, New hold nh limit: N/A, Curr nh cnt: 2, Curr new hold cnt: N/A, NH drop cnt: N/A
119 | Addresses, Flags: Primary Preferred
120 | Destination: fd00::3:0:0/104, Local: fd00::3:3:1
121 | Addresses, Flags: Preferred
122 | Destination: fd00::33:0:0/104, Local: fd00::33:33:1
123 | Addresses, Flags: Preferred
124 | Destination: fd00::333:300:0/104, Local: fd00::333:333:1
125 | Addresses, Flags: Preferred
126 | Destination: fd00::3333:3300:0/104, Local: fd00::3333:3333:1
127 | Addresses, Flags: Preferred
128 | Destination: fe80::/64, Local: fe80::1880:ff:feff:3
129 | Protocol multiservice, MTU: Unlimited
130 |
131 | ----------------------------------------------------------------------------------------------------
132 | Try SR Linux command: show interface detail
133 |
134 |
135 |
--------------------------------------------------------------------------------
/juniper/eth_switch/ethernet_switching_table_report.py:
--------------------------------------------------------------------------------
1 | """
2 | CLI Plugin for Junos-like show "ethernet-switching" commands
3 | Author: Miguel Redondo (Michel)
4 | Email: miguel.redondo_ferrero@nokia.com
5 |
6 | Provides the following commands:
7 | - show ethernet-switching table
8 | - show ethernet-switching table instance
9 | - show ethernet-switching table vlan-id
10 | - show ethernet-switching table interface
11 |
12 | """
13 | from srlinux.location import build_path
14 | from srlinux.data import ColumnFormatter, Data, Borders, Alignment, Border, Formatter
15 | from srlinux.mgmt.cli import CliPlugin, CommandNodeWithArguments
16 | from srlinux.mgmt.cli.cli_loader import CliLoader
17 | from srlinux.mgmt.cli.cli_output import CliOutput
18 | from srlinux.mgmt.cli.cli_state import CliState
19 | from srlinux.schema import FixedSchemaRoot
20 | from srlinux.data.utilities import Percentage, print_line, Width
21 | import itertools
22 | from srlinux import strings
23 | import logging
24 | import re
25 |
26 | #logger = logging.getLogger(__name__)
27 | #logger.level = logging.DEBUG
28 |
29 | class EthernetSwitchingReport:
30 | '''
31 | 'show ethernet-switching table' : Gives all MAC Table entries
32 | '''
33 | MAC_CODES = {
34 | 'static': 'T',
35 | 'duplicate': 'D',
36 | 'learnt' : 'L',
37 | 'irb-interface' : 'I',
38 | 'evpn' : 'E',
39 | 'evpn-static': 'ET',
40 | 'irb-interface-anycast': 'IA',
41 | 'proxy-anti-spoof': 'P',
42 | 'reserved': 'V',
43 | 'eth-cfm': 'C',
44 | 'irb-interface-vrrp': 'IR'
45 | }
46 |
47 | def get_schema_instance(self):
48 | root = FixedSchemaRoot()
49 | network = root.add_child('Network', key='Name')
50 | network.add_child('Statistics',
51 | fields=['Total', 'Active', 'Failed'])
52 | network.add_child('Ethernet Switching table',
53 | keys=['Vlan', 'Address'],
54 | fields=[
55 | 'MAC Flags',
56 | 'Logical Interface',
57 | 'SVLBNH/VENH Index',
58 | 'Active Source'])
59 | return root
60 |
61 | def _show_table_instance(self, state: CliState, output, arguments: CommandNodeWithArguments, **kwargs,):
62 | """Main display function"""
63 | self._state = state
64 | self._arguments = arguments
65 | if arguments.has_node('instance'):
66 | netinst_data = self._fetch_state_network(arguments.get('instance','name'))
67 | elif arguments.has_node('interface'):
68 | netinst_data = self._fetch_state_network('*')
69 | elif arguments.has_node('vlan'):
70 | netinst_data = self._fetch_state_network('*')
71 | else:
72 | netinst_data = self._fetch_state_network('*')
73 |
74 | data_root = Data(arguments.schema)
75 | self._set_all_formatters(data_root)
76 | with output.stream_data(data_root):
77 | self._populate_mac_table(netinst_data, data_root)
78 | output.print(srlinux_suggested_command)
79 | data_root.synchronizer.flush_children(data_root)
80 |
81 | def _fetch_state_network(self, netinst_name):
82 | table_path = build_path(
83 | '/network-instance[name={name}]',
84 | name=netinst_name
85 | )
86 | return self._state.server_data_store.get_data(table_path, recursive=False)
87 |
88 | def _fetch_state_network_interfaces(self, netinst_name):
89 | table_path = build_path(
90 | '/network-instance[name={name}]/interface[name=*]',
91 | name=netinst_name
92 | )
93 | return self._state.server_data_store.get_data(table_path, recursive=False, include_container_children=True)
94 |
95 | def _fetch_state_mac_table(self, netinst_name, mac_address=None):
96 | table_path = build_path(
97 | '/network-instance[name={name}]/bridge-table/mac-table/mac[address={mac}]',
98 | name=netinst_name,
99 | mac=mac_address or '*'
100 | )
101 | return self._state.server_data_store.stream_data(table_path, recursive=True)
102 |
103 | def _fetch_state_mac_table_stats(self, netinst_name):
104 | table_path = build_path(
105 | '/network-instance[name={name}]/bridge-table/statistics',
106 | name=netinst_name
107 | )
108 | return self._state.server_data_store.stream_data(table_path, recursive=False, include_container_children=True)
109 |
110 | def _fetch_state_subinterface(self, int_name, subint_index):
111 | table_path = build_path(
112 | '/interface[name={name}]/subinterface[index={index}]',
113 | name=int_name,
114 | index=str(subint_index)
115 | )
116 | return self._state.server_data_store.get_data(table_path, recursive=True, include_container_children=True)
117 |
118 | def _fetch_state_int_hw_mac(self, int_name):
119 | table_path = build_path(
120 | '/interface[name={name}]/ethernet/hw-mac-address',
121 | name=int_name
122 | )
123 | return self._state.server_data_store.stream_data(table_path, recursive=False, include_container_children=True)
124 |
125 | def _fetch_state_irb_subinterface_anycast_mac(self, int_name, subint_index):
126 | table_path = build_path(
127 | '/interface[name={name}]/subinterface[index={index}]/anycast-gw/anycast-gw-mac',
128 | name=int_name,
129 | index=str(subint_index)
130 | )
131 | return self._state.server_data_store.stream_data(table_path, recursive=False, include_container_children=True)
132 |
133 | def _get_interface_name_index_from_netinstance_data(self, network_interface_data ):
134 | interface_name_index_list=[]
135 | for network_interface_entry in network_interface_data.get_descendants('/network-instance/interface'):
136 | # get interface name and index for cases with interface-ref and without it
137 | if "interface-ref" in network_interface_entry.child_names:
138 | interface_ref = network_interface_entry.interface_ref.get()
139 | has_interface_ref = interface_ref.interface is not None and interface_ref.subinterface is not None
140 | if has_interface_ref:
141 | interface_name = interface_ref.interface
142 | subint_index = interface_ref.subinterface
143 | else:
144 | interface_name, subint_index = network_interface_entry.name.split('.', 1)
145 | else:
146 | interface_name, subint_index = network_interface_entry.name.split('.', 1)
147 |
148 | if any(sub in interface_name for sub in ["irb", "lo"]):
149 | continue
150 |
151 | # get vlan information from interface
152 | subinterface_data = self._fetch_state_subinterface(interface_name, subint_index)
153 | for subinterface in subinterface_data.get_descendants('/interface/subinterface'):
154 | vlan_encap = subinterface.vlan.get().encap.get()
155 | if vlan_encap.single_tagged.exists():
156 | vlan = vlan_encap.single_tagged.get().vlan_id
157 | tagging = vlan if vlan else 'null'
158 | interface_name_index_list.append({"name": interface_name, "index": str(subint_index), "tagging": str(tagging)})
159 | elif self._state.system_features.dot1q_vlan_ranges and vlan_encap.single_tagged_range.exists():
160 | vlan_ranges = vlan_encap.single_tagged_range.get()
161 | tag_ranges = ','.join(f'{entry.range_low_vlan_id}-{entry.high_vlan_id}' for entry in vlan_ranges.low_vlan_id.items())
162 | interface_name_index_list.append({"name": interface_name, "index": str(subint_index), "tagging": str(tag_ranges)})
163 | elif vlan_encap.untagged.exists():
164 | interface_name_index_list.append({"name": interface_name, "index": str(subint_index), "tagging": "untagged"})
165 | else:
166 | interface_name_index_list.append({"name": interface_name, "index": str(subint_index), "tagging": "null"})
167 |
168 | return interface_name_index_list
169 |
170 | def _get_irbs_from_netinstance_data(self, network_interface_data):
171 | interface_name_index_list=[]
172 | for network_interface_entry in network_interface_data.get_descendants('/network-instance/interface'):
173 | # get interface name and index for cases with interface-ref and without it
174 | if "interface-ref" in network_interface_entry.child_names:
175 | interface_ref = network_interface_entry.interface_ref.get()
176 | has_interface_ref = interface_ref.interface is not None and interface_ref.subinterface is not None
177 | if has_interface_ref:
178 | interface_name = interface_ref.interface
179 | subint_index = interface_ref.subinterface
180 | else:
181 | interface_name, subint_index = network_interface_entry.name.split('.', 1)
182 | else:
183 | interface_name, subint_index = network_interface_entry.name.split('.', 1)
184 |
185 | if not "irb" in interface_name:
186 | continue
187 |
188 | hw_mac_data = self._fetch_state_int_hw_mac(interface_name)
189 | anycast_gw_mac_data = self._fetch_state_irb_subinterface_anycast_mac(interface_name, subint_index)
190 |
191 | if anycast_gw_mac_data.get("interface").exists():
192 | anycast_gw_mac = anycast_gw_mac_data.interface.get().subinterface.get().anycast_gw.get().anycast_gw_mac
193 | else:
194 | anycast_gw_mac = ""
195 |
196 | hw_mac = hw_mac_data.interface.get().ethernet.get().hw_mac_address
197 | interface_name_index_list.append({"name": interface_name, "index": str(subint_index), "hw_mac": hw_mac, "anycast_gw_mac": anycast_gw_mac})
198 |
199 | return interface_name_index_list
200 |
201 | def _find_vlan(self, interfaces, query):
202 | # Extract interface name and index using regex
203 | match = re.match(r'(.+)\.(\d+)', query)
204 | if not match:
205 | return "-"
206 |
207 | interface_name, index = match.groups()
208 | index = str(index)
209 | # Search for the matching interface
210 | for entry in interfaces:
211 | if entry["name"] == interface_name and entry["index"] == index:
212 | return entry["tagging"]
213 | return "-"
214 |
215 | def _get_mac_code(self, mac_type, active):
216 | type = self.MAC_CODES.get(mac_type.lower(), '?')
217 | programming_status = "S" if active is True else "F"
218 | return f'{type},{programming_status}'
219 |
220 | def _get_logical_interface(self, mac_entry_address, mac_entry_destination, irb_interface_name_index_list):
221 | match_vxlan_interface = re.search(r'vxlan[\d.]+', mac_entry_destination)
222 | match_else = re.search(r'^\S+',mac_entry_destination)
223 | if match_vxlan_interface:
224 | logical_interface = match_vxlan_interface.group()
225 | elif "irb-interface" in mac_entry_destination:
226 | logical_interface = "irb(R)"
227 | for irb in irb_interface_name_index_list:
228 | if irb["hw_mac"] == mac_entry_address or irb["anycast_gw_mac"] == mac_entry_address:
229 | logical_interface = f'{irb["name"]}.{irb["index"]}(R)'
230 | elif match_else:
231 | logical_interface = match_else.group()
232 | else:
233 | logical_interface = ""
234 | return logical_interface
235 |
236 | def _get_active_source(self, mac_entry_destination):
237 | esi_pattern = r'esi:([\dA-Fa-f:]+)'
238 | vtep_pattern = r'vtep:([\dA-Fa-f:.]+)'
239 | esi_match = re.search(esi_pattern, mac_entry_destination)
240 | if esi_match:
241 | return esi_match.group(1)
242 | vtep_match = re.search(vtep_pattern, mac_entry_destination)
243 | if vtep_match:
244 | return vtep_match.group(1)
245 | return ""
246 |
247 | def _populate_mac_table(self, netinst_server_data, data_root):
248 | subinterface_name = self._arguments.get_value_or('interface','name',None)
249 | interface_as_argument = True if subinterface_name and "." not in subinterface_name else False
250 | vlan_value = self._arguments.get_value_or('vlan','value',None)
251 |
252 | for netinst in netinst_server_data.network_instance.items():
253 | if netinst.type != 'mac-vrf':
254 | continue
255 | netinst_data = data_root.network.create(netinst.name)
256 | mac_data = self._fetch_state_mac_table(netinst.name)
257 | mac_data_stats = self._fetch_state_mac_table_stats(netinst.name)
258 | network_interface_data = self._fetch_state_network_interfaces(netinst.name)
259 | irb_interface_name_index_list = self._get_irbs_from_netinstance_data(network_interface_data)
260 | interface_name_index_list = self._get_interface_name_index_from_netinstance_data (network_interface_data)
261 |
262 | for mac_entry in mac_data.get_descendants('/network-instance/bridge-table/mac-table/mac'):
263 | logical_subinterface = self._get_logical_interface(mac_entry.address, mac_entry.destination, irb_interface_name_index_list)
264 | logical_interface = logical_subinterface.split('.')[0] if logical_subinterface else None
265 | vlan = self._find_vlan(interface_name_index_list,logical_subinterface)
266 | # if an interface (without the "".subint") is given as argument we populate the mac table for all its subinterfaces
267 | if subinterface_name is not None and subinterface_name != logical_subinterface:
268 | if not interface_as_argument or subinterface_name != logical_interface:
269 | continue
270 | if vlan_value is not None and vlan_value != vlan:
271 | continue
272 |
273 | mac = netinst_data.ethernet_switching_table.create( vlan, mac_entry.address )
274 | mac.logical_interface = logical_subinterface
275 | mac.svlbnh_venh_index = mac_entry.destination_index
276 | active = False if mac_entry.not_programmed_reason else True
277 | mac.mac_flags = self._get_mac_code(mac_entry.type, active)
278 | mac.active_source = self._get_active_source(mac_entry.destination)
279 | mac.synchronizer.flush_fields(mac)
280 |
281 | for mac_stat_entry in mac_data_stats.get_descendants('/network-instance/bridge-table/statistics'):
282 | mac_stat = netinst_data.statistics.create()
283 | mac_stat.total = mac_stat_entry.total_entries
284 | mac_stat.active = mac_stat_entry.active_entries
285 | mac_stat.failed = mac_stat_entry.failed_entries
286 | mac_stat.synchronizer.flush_fields(mac_stat)
287 |
288 | netinst_data.synchronizer.flush_fields(netinst_data)
289 | netinst_data.synchronizer.flush_children(netinst_data.ethernet_switching_table)
290 | netinst_data.synchronizer.flush_children(netinst_data.statistics)
291 |
292 | def _set_all_formatters(self, data):
293 | data.set_formatter('/Network', NetworkHeaderFormatter())
294 | data.set_formatter('/Network/Statistics', StatisticsFormatter())
295 | data.set_formatter('/Network/Ethernet Switching table',
296 | ColumnFormatter(
297 | ancestor_keys=False,
298 | header_alignment=Alignment.Left,
299 | horizontal_alignment={
300 | 'vlan': Alignment.Left,
301 | 'address': Alignment.Left,
302 | 'mac_flags': Alignment.Left,
303 | 'logical_interface': Alignment.Left,
304 | 'svlbnh_venh_index':Alignment.Left,
305 | 'active_source':Alignment.Left
306 | },
307 | widths={
308 | 'vlan': 9,
309 | 'address': 18,
310 | 'mac_flags': 10,
311 | 'logical_interface': 18,
312 | 'svlbnh_venh_index':12,
313 | 'active_source':30
314 | },
315 | print_on_data=True,
316 | header=False,
317 | borders=Borders.Nothing
318 | )
319 | )
320 |
321 | class NetworkHeaderFormatter(Formatter):
322 | def iter_format(self, entry, max_width):
323 | if entry.ethernet_switching_table.exists():
324 | yield print_line(max_width, character=' ')
325 | yield self._format_macflags_header()
326 | yield print_line(max_width, character=' ')
327 | yield from entry.statistics.iter_format(max_width)
328 | yield f'Routing instance : {entry.name}'
329 | yield from self._format_header()
330 | yield from entry.ethernet_switching_table.iter_format(max_width)
331 |
332 | def _format_macflags_header(self):
333 | MAC_CODES_HEADER = """MAC flags (T - static MAC, D - duplicate MAC, L - locally learned, I - irb interface, E - EVPN
334 | ET - EVPN static, IA - irb interface anycast, P - proxy anti spoof, V - reserved MAC,
335 | C - eth-cfm, IR - irb interface vrrp, F - programming failed, S - programming success)
336 | """
337 | return MAC_CODES_HEADER
338 |
339 | def _format_header(self):
340 | return (
341 | "Vlan MAC MAC Logical Interface SVLBNH/VENH Active Source",
342 | "id address flags interface Index Source "
343 | )
344 |
345 | class StatisticsFormatter(Formatter):
346 | def __init__(self, tag_value_space=0):
347 | super(StatisticsFormatter, self).__init__()
348 | self._tag_value_spacing = tag_value_space
349 |
350 | def iter_format(self, entry, max_width):
351 | yield f'Ethernet switching table : {entry.total:4} Total {entry.active:4} Active {entry.failed:4} Failed'
352 |
353 |
354 | srlinux_suggested_command = """
355 | ------------------------------------------------------------------------------------------------
356 | Try SR Linux command:
357 | -> show network-instance bridge-table mac-table all
358 | -> show interface ethernet-x/y.z | grep Encapsulation
359 | """
360 |
361 |
--------------------------------------------------------------------------------
/juniper/plugins/ethernet_switching_reports.py:
--------------------------------------------------------------------------------
1 | """
2 | CLI Plugin for Junos-like show "ethernet-switching" commands
3 | Author: Miguel Redondo (Michel)
4 | Email: miguel.redondo_ferrero@nokia.com
5 |
6 | Provides the following commands:
7 | - show ethernet-switching table
8 | - show ethernet-switching table instance
9 | - show ethernet-switching table vlan
10 | - show ethernet-switching table interface
11 |
12 | """
13 | from srlinux.mgmt.cli import CliPlugin, ExecuteError, KeyCompleter, MultipleKeyCompleters
14 | from srlinux.syntax import Syntax
15 | from srlinux.schema import FixedSchemaRoot
16 | from srlinux.mgmt.cli.cli_loader import CliLoader
17 |
18 | import sys
19 | import os
20 | import argparse
21 |
22 | # Try potential base directories
23 | potential_paths = [
24 | os.path.expanduser('~/cli'),
25 | '/etc/opt/srlinux/cli'
26 | ]
27 |
28 | # Find the first valid path
29 | import_base = None
30 | for path in potential_paths:
31 | if os.path.exists(path):
32 | import_base = path
33 | break
34 |
35 | if import_base is None:
36 | raise ImportError("Could not find a valid CLI plugin base directory")
37 |
38 | # Construct the import path
39 | import_path = os.path.join(import_base, "eth_switch")
40 |
41 | # Add to Python path if not already present
42 | if import_path not in sys.path:
43 | sys.path.insert(0, import_path)
44 |
45 | from ethernet_switching_table_report import EthernetSwitchingReport
46 |
47 | class Plugin(CliPlugin):
48 | """Junos-like ethernet-switching CLI plugin."""
49 | def load(self, cli: CliLoader, arguments: argparse.Namespace) -> None:
50 | ethernet_switching = cli.show_mode.add_command(Syntax('ethernet-switching', help='Show ethernet switching information'))
51 |
52 | # Add table command as a subcommand
53 | ethernet_switching_table = ethernet_switching.add_command(
54 | Syntax('table', help='Show media access control table'),
55 | callback=self._show_ethernet_switching_table,
56 | schema=EthernetSwitchingReport().get_schema_instance()
57 | )
58 |
59 | # Add 'table' subcommand with network-instance completion
60 | ethernet_switching_table_instance = ethernet_switching_table.add_command(
61 | Syntax('instance', help='Display information for a specified network-instance')
62 | .add_unnamed_argument('name', suggestions=KeyCompleter('/network-instance[name=*]')),
63 | callback=self._show_ethernet_switching_table_instance,
64 | update_location=False,
65 | schema=EthernetSwitchingReport().get_schema_instance()
66 | )
67 |
68 | # Add 'vlan-id' subcommand with vlans completion
69 | ethernet_switching_table_vlanid = ethernet_switching_table.add_command(
70 | Syntax('vlan', help='Display MAC address learned on a specified VLAN')
71 | .add_unnamed_argument('value', suggestions=MultipleKeyCompleters(keycompleters=[KeyCompleter(path="/interface[name=*]/subinterface[index=*]/vlan/encap/single-tagged-range/low-vlan-id[range-low-vlan-id=*]"), KeyCompleter(path="/interface[name=*]/subinterface[index=*]/vlan/encap/single-tagged/vlan-id:")])),
72 | callback=self._show_ethernet_switching_table_vlanid,
73 | update_location=False,
74 | schema=EthernetSwitchingReport().get_schema_instance()
75 | )
76 |
77 | # Add 'interface' subcommand with interface completion
78 | ethernet_switching_table_interface = ethernet_switching_table.add_command(
79 | Syntax('interface', help='Display MAC table for a specified interface')
80 | .add_unnamed_argument('name', suggestions=MultipleKeyCompleters(keycompleters=[KeyCompleter(path="/interface[name=*]"), KeyCompleter(path="/interface[name=*]/subinterface[index=*]/name:")])),
81 | callback=self._show_ethernet_switching_table_interface,
82 | update_location=False,
83 | schema=EthernetSwitchingReport().get_schema_instance()
84 | )
85 |
86 | def _show_ethernet_switching_table(self, state, arguments, output, **_kwargs):
87 | if state.is_intermediate_command:
88 | return
89 | EthernetSwitchingReport()._show_table_instance(state, output, arguments, **_kwargs)
90 |
91 | def _show_ethernet_switching_table_instance(self, state, arguments, output, **_kwargs):
92 | if state.is_intermediate_command:
93 | return
94 | EthernetSwitchingReport()._show_table_instance(state, output, arguments, **_kwargs)
95 |
96 | def _show_ethernet_switching_table_vlanid(self, state, arguments, output, **_kwargs):
97 | if state.is_intermediate_command:
98 | return
99 | EthernetSwitchingReport()._show_table_instance(state, output, arguments, **_kwargs)
100 |
101 | def _show_ethernet_switching_table_interface(self, state, arguments, output, **_kwargs):
102 | if state.is_intermediate_command:
103 | return
104 | EthernetSwitchingReport()._show_table_instance(state, output, arguments, **_kwargs)
105 |
106 |
--------------------------------------------------------------------------------
/lab/README.md:
--------------------------------------------------------------------------------
1 | # EVPN Multihoming Lab
2 |
3 | ## Topology
4 |
5 | 
6 |
7 | # MPLS Lab
8 |
9 | Coming soon!
10 |
--------------------------------------------------------------------------------
/lab/evpn-mh-topology.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/srl-labs/MultiCLI/6e29987c434184e4c30d8c98e48ead4436a5f126/lab/evpn-mh-topology.jpg
--------------------------------------------------------------------------------
/lab/evpn/leaf1-1-startup.cfg:
--------------------------------------------------------------------------------
1 | set / interface ethernet-1/1 description To-Spine
2 | set / interface ethernet-1/1 admin-state enable
3 | set / interface ethernet-1/1 subinterface 0 ipv4 admin-state enable
4 | set / interface ethernet-1/1 subinterface 0 ipv4 address 192.168.10.2/31
5 | set / interface ethernet-1/1 subinterface 0 ipv6 admin-state enable
6 | set / interface ethernet-1/1 subinterface 0 ipv6 address 192:168:10::2/127
7 | set / interface ethernet-1/10 description To-Client1
8 | set / interface ethernet-1/10 admin-state enable
9 | set / interface ethernet-1/10 ethernet aggregate-id lag1
10 | set / interface ethernet-1/11 description To-Client2
11 | set / interface ethernet-1/11 admin-state enable
12 | set / interface ethernet-1/11 ethernet aggregate-id lag2
13 | set / interface irb0 subinterface 0 ipv4 admin-state enable
14 | set / interface irb0 subinterface 0 ipv4 address 10.80.1.254/24 anycast-gw true
15 | set / interface irb0 subinterface 0 ipv4 address 10.80.1.254/24 primary
16 | set / interface irb0 subinterface 0 ipv6 admin-state enable
17 | set / interface irb0 subinterface 0 ipv6 address 10:80:1::254/64 anycast-gw true
18 | set / interface irb0 subinterface 0 ipv6 address 10:80:1::254/64 primary
19 | set / interface irb0 subinterface 0 anycast-gw
20 | set / interface lag1 subinterface 0 type bridged
21 | set / interface lag2 subinterface 0 type bridged
22 | set / interface lag2 subinterface 0 admin-state enable
23 | set / interface system0 subinterface 0 ipv4 admin-state enable
24 | set / interface system0 subinterface 0 ipv4 address 1.1.1.1/32
25 | set / interface system0 subinterface 0 ipv6 admin-state enable
26 | set / interface system0 subinterface 0 ipv6 address 2001::1/128
27 |
28 | set / routing-policy prefix-set underlay-v4 prefix 0.0.0.0/0 mask-length-range 32..32
29 | set / routing-policy prefix-set underlay-v6 prefix ::/0 mask-length-range 128..128
30 | set / routing-policy policy export-underlay-v4 statement local match prefix prefix-set underlay-v4
31 | set / routing-policy policy export-underlay-v4 statement local match protocol local
32 | set / routing-policy policy export-underlay-v4 statement local action policy-result accept
33 | set / routing-policy policy export-underlay-v6 statement local match prefix prefix-set underlay-v6
34 | set / routing-policy policy export-underlay-v6 statement local match protocol local
35 | set / routing-policy policy export-underlay-v6 statement local action policy-result accept
36 |
37 | set / network-instance default type default
38 | set / network-instance default admin-state enable
39 | set / network-instance default interface ethernet-1/1.0
40 | set / network-instance default interface system0.0
41 | set / network-instance default protocols bgp autonomous-system 64501
42 | set / network-instance default protocols bgp router-id 1.1.1.1
43 | set / network-instance default protocols bgp ebgp-default-policy import-reject-all false
44 | set / network-instance default protocols bgp ebgp-default-policy export-reject-all false
45 | set / network-instance default protocols bgp afi-safi ipv4-unicast admin-state enable
46 | set / network-instance default protocols bgp group ebgp peer-as 64500
47 | set / network-instance default protocols bgp group ebgp afi-safi ipv6-unicast admin-state enable
48 | set / network-instance default protocols bgp group evpn peer-as 65500
49 | set / network-instance default protocols bgp group evpn multihop admin-state enable
50 | set / network-instance default protocols bgp group evpn afi-safi evpn admin-state enable
51 | set / network-instance default protocols bgp group evpn afi-safi ipv4-unicast admin-state disable
52 | set / network-instance default protocols bgp group evpn afi-safi ipv6-unicast admin-state disable
53 | set / network-instance default protocols bgp group evpn local-as as-number 65500
54 | set / network-instance default protocols bgp neighbor 10.10.10.10 peer-group evpn
55 | set / network-instance default protocols bgp neighbor 10.10.10.10 transport local-address 1.1.1.1
56 | set / network-instance default protocols bgp neighbor 192.168.10.3 peer-group ebgp
57 | set / network-instance default protocols bgp neighbor 192.168.10.3 export-policy [ export-underlay-v4 ]
58 | set / network-instance default protocols bgp neighbor 192.168.10.3 afi-safi ipv6-unicast admin-state disable
59 | set / network-instance default protocols bgp neighbor 192:168:10::3 peer-group ebgp
60 | set / network-instance default protocols bgp neighbor 192:168:10::3 export-policy [ export-underlay-v6 ]
61 | set / network-instance default protocols bgp neighbor 192:168:10::3 afi-safi ipv4-unicast admin-state disable
62 | set / network-instance default protocols bgp neighbor 2001::10 peer-group evpn
63 | set / network-instance default protocols bgp neighbor 2001::10 transport local-address 2001::1
64 | set / network-instance ip-vrf-1 type ip-vrf
65 | set / network-instance ip-vrf-1 admin-state enable
66 | set / network-instance ip-vrf-1 interface irb0.0
67 | set / network-instance ip-vrf-1 vxlan-interface vxlan24.200
68 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 encapsulation-type vxlan
69 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 vxlan-interface vxlan24.200
70 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 evi 200
71 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 ecmp 2
72 | set / network-instance ip-vrf-1 protocols bgp-vpn bgp-instance 1 route-distinguisher rd 1.1.1.1:200
73 | set / network-instance ip-vrf-1 protocols bgp-vpn bgp-instance 1 route-target export-rt target:65500:200
74 | set / network-instance ip-vrf-1 protocols bgp-vpn bgp-instance 1 route-target import-rt target:65500:200
75 | set / network-instance mac-vrf-1 type mac-vrf
76 | set / network-instance mac-vrf-1 interface irb0.0
77 | set / network-instance mac-vrf-1 interface lag1.0
78 | set / network-instance mac-vrf-1 interface lag2.0
79 | set / network-instance mac-vrf-1 vxlan-interface vxlan13.100
80 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 encapsulation-type vxlan
81 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 vxlan-interface vxlan13.100
82 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 evi 100
83 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 ecmp 2
84 | set / network-instance mac-vrf-1 protocols bgp-vpn bgp-instance 1 route-distinguisher rd 1.1.1.1:100
85 | set / network-instance mac-vrf-1 protocols bgp-vpn bgp-instance 1 route-target export-rt target:65500:100
86 | set / network-instance mac-vrf-1 protocols bgp-vpn bgp-instance 1 route-target import-rt target:65500:100
87 |
88 | set / tunnel-interface vxlan13 vxlan-interface 100 type bridged
89 | set / tunnel-interface vxlan13 vxlan-interface 100 ingress vni 100
90 | set / tunnel-interface vxlan24 vxlan-interface 200 type routed
91 | set / tunnel-interface vxlan24 vxlan-interface 200 ingress vni 200
92 |
93 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 admin-state enable
94 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 esi 11:01:01:01:01:01:01:16:10:50
95 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 multi-homing-mode all-active
96 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 interface lag1
97 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 admin-state enable
98 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 esi 33:03:03:03:03:03:03:80:01:01
99 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 multi-homing-mode all-active
100 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 interface lag2
101 | set / system network-instance protocols bgp-vpn bgp-instance 1
102 |
103 | set / system aaa authorization role plugin superuser true
104 | set / system aaa authorization role plugin services [ cli ]
105 | set / system aaa authentication user auser password auser
106 | set / system aaa authentication user auser role [ plugin ]
107 | set / system aaa authentication user cnxuser password cnxuser
108 | set / system aaa authentication user cnxuser role [ plugin ]
109 | set / system aaa authentication user juser password juser
110 | set / system aaa authentication user juser role [ plugin ]
111 | set / system aaa authentication user nokuser password nokuser
112 | set / system aaa authentication user nokuser role [ plugin ]
113 |
--------------------------------------------------------------------------------
/lab/evpn/leaf1-2-startup.cfg:
--------------------------------------------------------------------------------
1 | set / interface ethernet-1/1
2 | set / interface ethernet-1/2 admin-state enable
3 | set / interface ethernet-1/2 subinterface 0 ipv4 admin-state enable
4 | set / interface ethernet-1/2 subinterface 0 ipv4 address 192.168.10.4/31
5 | set / interface ethernet-1/2 subinterface 0 ipv6 admin-state enable
6 | set / interface ethernet-1/2 subinterface 0 ipv6 address 192:168:10::4/127
7 | set / interface ethernet-1/10 description To-Client2
8 | set / interface ethernet-1/10 admin-state enable
9 | set / interface ethernet-1/10 ethernet aggregate-id lag2
10 | set / interface ethernet-1/11 description To-Client1
11 | set / interface ethernet-1/11 admin-state enable
12 | set / interface ethernet-1/11 ethernet aggregate-id lag1
13 | set / interface irb0 subinterface 0 ipv4 admin-state enable
14 | set / interface irb0 subinterface 0 ipv4 address 10.80.1.254/24 anycast-gw true
15 | set / interface irb0 subinterface 0 ipv4 address 10.80.1.254/24 primary
16 | set / interface irb0 subinterface 0 ipv6 admin-state enable
17 | set / interface irb0 subinterface 0 ipv6 address 10:80:1::254/64 anycast-gw true
18 | set / interface irb0 subinterface 0 ipv6 address 10:80:1::254/64 primary
19 | set / interface irb0 subinterface 0 anycast-gw
20 | set / interface lag1 subinterface 0 type bridged
21 | set / interface lag2 subinterface 0 type bridged
22 | set / interface lag2 subinterface 0 admin-state enable
23 | set / interface system0 subinterface 0 ipv4 admin-state enable
24 | set / interface system0 subinterface 0 ipv4 address 2.2.2.2/32
25 | set / interface system0 subinterface 0 ipv6 admin-state enable
26 | set / interface system0 subinterface 0 ipv6 address 2001::2/128
27 |
28 | set / routing-policy prefix-set underlay-v4 prefix 0.0.0.0/0 mask-length-range 32..32
29 | set / routing-policy prefix-set underlay-v6 prefix ::/0 mask-length-range 128..128
30 | set / routing-policy policy export-underlay-v4 statement local match prefix prefix-set underlay-v4
31 | set / routing-policy policy export-underlay-v4 statement local match protocol local
32 | set / routing-policy policy export-underlay-v4 statement local action policy-result accept
33 | set / routing-policy policy export-underlay-v6 statement local match prefix prefix-set underlay-v6
34 | set / routing-policy policy export-underlay-v6 statement local match protocol local
35 | set / routing-policy policy export-underlay-v6 statement local action policy-result accept
36 |
37 | set / network-instance default type default
38 | set / network-instance default admin-state enable
39 | set / network-instance default interface ethernet-1/2.0
40 | set / network-instance default interface system0.0
41 | set / network-instance default protocols bgp autonomous-system 64501
42 | set / network-instance default protocols bgp router-id 2.2.2.2
43 | set / network-instance default protocols bgp ebgp-default-policy import-reject-all false
44 | set / network-instance default protocols bgp ebgp-default-policy export-reject-all false
45 | set / network-instance default protocols bgp afi-safi ipv4-unicast admin-state enable
46 | set / network-instance default protocols bgp group ebgp peer-as 64500
47 | set / network-instance default protocols bgp group ebgp afi-safi ipv6-unicast admin-state enable
48 | set / network-instance default protocols bgp group evpn peer-as 65500
49 | set / network-instance default protocols bgp group evpn multihop admin-state enable
50 | set / network-instance default protocols bgp group evpn afi-safi evpn admin-state enable
51 | set / network-instance default protocols bgp group evpn afi-safi ipv4-unicast admin-state disable
52 | set / network-instance default protocols bgp group evpn afi-safi ipv6-unicast admin-state disable
53 | set / network-instance default protocols bgp group evpn local-as as-number 65500
54 | set / network-instance default protocols bgp neighbor 10.10.10.10 peer-group evpn
55 | set / network-instance default protocols bgp neighbor 10.10.10.10 transport local-address 2.2.2.2
56 | set / network-instance default protocols bgp neighbor 192.168.10.5 peer-group ebgp
57 | set / network-instance default protocols bgp neighbor 192.168.10.5 export-policy [ export-underlay-v4 ]
58 | set / network-instance default protocols bgp neighbor 192.168.10.5 afi-safi ipv6-unicast admin-state disable
59 | set / network-instance default protocols bgp neighbor 192:168:10::5 peer-group ebgp
60 | set / network-instance default protocols bgp neighbor 192:168:10::5 export-policy [ export-underlay-v6 ]
61 | set / network-instance default protocols bgp neighbor 192:168:10::5 afi-safi ipv4-unicast admin-state disable
62 | set / network-instance default protocols bgp neighbor 2001::10 peer-group evpn
63 | set / network-instance default protocols bgp neighbor 2001::10 transport local-address 2001::2
64 | set / network-instance ip-vrf-1 type ip-vrf
65 | set / network-instance ip-vrf-1 admin-state enable
66 | set / network-instance ip-vrf-1 interface irb0.0
67 | set / network-instance ip-vrf-1 vxlan-interface vxlan24.200
68 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 encapsulation-type vxlan
69 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 vxlan-interface vxlan24.200
70 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 evi 200
71 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 ecmp 2
72 | set / network-instance ip-vrf-1 protocols bgp-vpn bgp-instance 1 route-distinguisher rd 2.2.2.2:200
73 | set / network-instance ip-vrf-1 protocols bgp-vpn bgp-instance 1 route-target export-rt target:65500:200
74 | set / network-instance ip-vrf-1 protocols bgp-vpn bgp-instance 1 route-target import-rt target:65500:200
75 | set / network-instance mac-vrf-1 type mac-vrf
76 | set / network-instance mac-vrf-1 interface irb0.0
77 | set / network-instance mac-vrf-1 interface lag1.0
78 | set / network-instance mac-vrf-1 interface lag2.0
79 | set / network-instance mac-vrf-1 vxlan-interface vxlan13.100
80 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 encapsulation-type vxlan
81 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 vxlan-interface vxlan13.100
82 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 evi 100
83 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 ecmp 2
84 | set / network-instance mac-vrf-1 protocols bgp-vpn bgp-instance 1 route-distinguisher rd 2.2.2.2:100
85 | set / network-instance mac-vrf-1 protocols bgp-vpn bgp-instance 1 route-target export-rt target:65500:100
86 | set / network-instance mac-vrf-1 protocols bgp-vpn bgp-instance 1 route-target import-rt target:65500:100
87 |
88 | set / tunnel-interface vxlan13 vxlan-interface 100 type bridged
89 | set / tunnel-interface vxlan13 vxlan-interface 100 ingress vni 100
90 | set / tunnel-interface vxlan24 vxlan-interface 200 type routed
91 | set / tunnel-interface vxlan24 vxlan-interface 200 ingress vni 200
92 |
93 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 admin-state enable
94 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 esi 11:01:01:01:01:01:01:16:10:50
95 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 multi-homing-mode all-active
96 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 interface lag1
97 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 admin-state enable
98 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 esi 33:03:03:03:03:03:03:80:01:01
99 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 multi-homing-mode all-active
100 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 interface lag2
101 | set / system network-instance protocols bgp-vpn bgp-instance 1
102 |
103 | set / system aaa authorization role plugin superuser true
104 | set / system aaa authorization role plugin services [ cli ]
105 | set / system aaa authentication user auser password auser
106 | set / system aaa authentication user auser role [ plugin ]
107 | set / system aaa authentication user cnxuser password cnxuser
108 | set / system aaa authentication user cnxuser role [ plugin ]
109 | set / system aaa authentication user juser password juser
110 | set / system aaa authentication user juser role [ plugin ]
111 | set / system aaa authentication user nokuser password nokuser
112 | set / system aaa authentication user nokuser role [ plugin ]
113 |
--------------------------------------------------------------------------------
/lab/evpn/leaf2-1-startup.cfg:
--------------------------------------------------------------------------------
1 | set / interface ethernet-1/3 description To-Spine
2 | set / interface ethernet-1/3 admin-state enable
3 | set / interface ethernet-1/3 subinterface 0 ipv4 admin-state enable
4 | set / interface ethernet-1/3 subinterface 0 ipv4 address 192.168.20.2/31
5 | set / interface ethernet-1/3 subinterface 0 ipv6 admin-state enable
6 | set / interface ethernet-1/3 subinterface 0 ipv6 address 192:168:20::2/127
7 | set / interface ethernet-1/10 description To-Client3
8 | set / interface ethernet-1/10 admin-state enable
9 | set / interface ethernet-1/10 ethernet aggregate-id lag1
10 | set / interface ethernet-1/11 description To-Client4
11 | set / interface ethernet-1/11 admin-state enable
12 | set / interface ethernet-1/11 ethernet aggregate-id lag2
13 | set / interface irb0 subinterface 0 ipv4 admin-state enable
14 | set / interface irb0 subinterface 0 ipv4 address 10.90.1.254/24 anycast-gw true
15 | set / interface irb0 subinterface 0 ipv4 address 10.90.1.254/24 primary
16 | set / interface irb0 subinterface 0 ipv6 admin-state enable
17 | set / interface irb0 subinterface 0 ipv6 address 10:90:1::254/64 anycast-gw true
18 | set / interface irb0 subinterface 0 ipv6 address 10:90:1::254/64 primary
19 | set / interface irb0 subinterface 0 anycast-gw
20 | set / interface lag1 subinterface 0 type bridged
21 | set / interface lag2 subinterface 0 type bridged
22 | set / interface lag2 subinterface 0 admin-state enable
23 | set / interface system0 subinterface 0 ipv4 admin-state enable
24 | set / interface system0 subinterface 0 ipv4 address 3.3.3.3/32
25 | set / interface system0 subinterface 0 ipv6 admin-state enable
26 | set / interface system0 subinterface 0 ipv6 address 2001::3/128
27 |
28 | set / routing-policy prefix-set underlay-v4 prefix 0.0.0.0/0 mask-length-range 32..32
29 | set / routing-policy prefix-set underlay-v6 prefix ::/0 mask-length-range 128..128
30 | set / routing-policy policy export-underlay-v4 statement local match prefix prefix-set underlay-v4
31 | set / routing-policy policy export-underlay-v4 statement local match protocol local
32 | set / routing-policy policy export-underlay-v4 statement local action policy-result accept
33 | set / routing-policy policy export-underlay-v6 statement local match prefix prefix-set underlay-v6
34 | set / routing-policy policy export-underlay-v6 statement local match protocol local
35 | set / routing-policy policy export-underlay-v6 statement local action policy-result accept
36 |
37 | set / network-instance default type default
38 | set / network-instance default admin-state enable
39 | set / network-instance default interface ethernet-1/3.0
40 | set / network-instance default interface system0.0
41 | set / network-instance default protocols bgp autonomous-system 64502
42 | set / network-instance default protocols bgp router-id 3.3.3.3
43 | set / network-instance default protocols bgp ebgp-default-policy import-reject-all false
44 | set / network-instance default protocols bgp ebgp-default-policy export-reject-all false
45 | set / network-instance default protocols bgp afi-safi ipv4-unicast admin-state enable
46 | set / network-instance default protocols bgp group ebgp peer-as 64500
47 | set / network-instance default protocols bgp group ebgp afi-safi ipv6-unicast admin-state enable
48 | set / network-instance default protocols bgp group evpn peer-as 65500
49 | set / network-instance default protocols bgp group evpn multihop admin-state enable
50 | set / network-instance default protocols bgp group evpn afi-safi evpn admin-state enable
51 | set / network-instance default protocols bgp group evpn afi-safi ipv4-unicast admin-state disable
52 | set / network-instance default protocols bgp group evpn afi-safi ipv6-unicast admin-state disable
53 | set / network-instance default protocols bgp group evpn local-as as-number 65500
54 | set / network-instance default protocols bgp neighbor 10.10.10.10 peer-group evpn
55 | set / network-instance default protocols bgp neighbor 10.10.10.10 transport local-address 3.3.3.3
56 | set / network-instance default protocols bgp neighbor 192.168.20.3 peer-group ebgp
57 | set / network-instance default protocols bgp neighbor 192.168.20.3 export-policy [ export-underlay-v4 ]
58 | set / network-instance default protocols bgp neighbor 192.168.20.3 afi-safi ipv6-unicast admin-state disable
59 | set / network-instance default protocols bgp neighbor 192:168:20::3 peer-group ebgp
60 | set / network-instance default protocols bgp neighbor 192:168:20::3 export-policy [ export-underlay-v6 ]
61 | set / network-instance default protocols bgp neighbor 192:168:20::3 afi-safi ipv4-unicast admin-state disable
62 | set / network-instance default protocols bgp neighbor 2001::10 peer-group evpn
63 | set / network-instance default protocols bgp neighbor 2001::10 transport local-address 2001::3
64 | set / network-instance ip-vrf-1 type ip-vrf
65 | set / network-instance ip-vrf-1 admin-state enable
66 | set / network-instance ip-vrf-1 interface irb0.0
67 | set / network-instance ip-vrf-1 vxlan-interface vxlan24.200
68 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 encapsulation-type vxlan
69 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 vxlan-interface vxlan24.200
70 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 evi 200
71 | set / network-instance ip-vrf-1 protocols bgp-vpn bgp-instance 1 route-distinguisher rd 3.3.3.3:200
72 | set / network-instance ip-vrf-1 protocols bgp-vpn bgp-instance 1 route-target export-rt target:65500:200
73 | set / network-instance ip-vrf-1 protocols bgp-vpn bgp-instance 1 route-target import-rt target:65500:200
74 | set / network-instance mac-vrf-1 type mac-vrf
75 | set / network-instance mac-vrf-1 interface irb0.0
76 | set / network-instance mac-vrf-1 interface lag1.0
77 | set / network-instance mac-vrf-1 interface lag2.0
78 | set / network-instance mac-vrf-1 vxlan-interface vxlan13.100
79 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 encapsulation-type vxlan
80 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 vxlan-interface vxlan13.100
81 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 evi 100
82 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 ecmp 2
83 | set / network-instance mac-vrf-1 protocols bgp-vpn bgp-instance 1 route-distinguisher rd 2.2.2.2:100
84 | set / network-instance mac-vrf-1 protocols bgp-vpn bgp-instance 1 route-target export-rt target:65500:100
85 | set / network-instance mac-vrf-1 protocols bgp-vpn bgp-instance 1 route-target import-rt target:65500:100
86 |
87 | set / tunnel-interface vxlan13 vxlan-interface 100 type bridged
88 | set / tunnel-interface vxlan13 vxlan-interface 100 ingress vni 100
89 | set / tunnel-interface vxlan24 vxlan-interface 200 type routed
90 | set / tunnel-interface vxlan24 vxlan-interface 200 ingress vni 200
91 |
92 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 admin-state enable
93 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 esi 22:01:01:01:01:01:01:16:10:60
94 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 multi-homing-mode all-active
95 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 interface lag1
96 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 admin-state enable
97 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 esi 44:03:03:03:03:03:03:90:01:01
98 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 multi-homing-mode all-active
99 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 interface lag2
100 | set / system network-instance protocols bgp-vpn bgp-instance 1
101 |
102 | set / system aaa authorization role plugin superuser true
103 | set / system aaa authorization role plugin services [ cli ]
104 | set / system aaa authentication user auser password auser
105 | set / system aaa authentication user auser role [ plugin ]
106 | set / system aaa authentication user cnxuser password cnxuser
107 | set / system aaa authentication user cnxuser role [ plugin ]
108 | set / system aaa authentication user juser password juser
109 | set / system aaa authentication user juser role [ plugin ]
110 | set / system aaa authentication user nokuser password nokuser
111 | set / system aaa authentication user nokuser role [ plugin ]
112 |
--------------------------------------------------------------------------------
/lab/evpn/leaf2-2-startup.cfg:
--------------------------------------------------------------------------------
1 | set / interface ethernet-1/4 description To-Spine
2 | set / interface ethernet-1/4 admin-state enable
3 | set / interface ethernet-1/4 subinterface 0 ipv4 admin-state enable
4 | set / interface ethernet-1/4 subinterface 0 ipv4 address 192.168.20.4/31
5 | set / interface ethernet-1/4 subinterface 0 ipv6 admin-state enable
6 | set / interface ethernet-1/4 subinterface 0 ipv6 address 192:168:20::4/127
7 | set / interface ethernet-1/10 description To-Client4
8 | set / interface ethernet-1/10 admin-state enable
9 | set / interface ethernet-1/10 ethernet aggregate-id lag2
10 | set / interface ethernet-1/11 description To-Client3
11 | set / interface ethernet-1/11 admin-state enable
12 | set / interface ethernet-1/11 ethernet aggregate-id lag1
13 | set / interface irb0 subinterface 0 ipv4 admin-state enable
14 | set / interface irb0 subinterface 0 ipv4 address 10.90.1.254/24 anycast-gw true
15 | set / interface irb0 subinterface 0 ipv4 address 10.90.1.254/24 primary
16 | set / interface irb0 subinterface 0 ipv6 admin-state enable
17 | set / interface irb0 subinterface 0 ipv6 address 10:90:1::254/64 anycast-gw true
18 | set / interface irb0 subinterface 0 ipv6 address 10:90:1::254/64 primary
19 | set / interface irb0 subinterface 0 anycast-gw
20 | set / interface lag1 subinterface 0 type bridged
21 | set / interface lag2 subinterface 0 type bridged
22 | set / interface lag2 subinterface 0 admin-state enable
23 | set / interface system0 subinterface 0 ipv4 admin-state enable
24 | set / interface system0 subinterface 0 ipv4 address 4.4.4.4/32
25 | set / interface system0 subinterface 0 ipv6 admin-state enable
26 | set / interface system0 subinterface 0 ipv6 address 2001::4/128
27 |
28 | set / routing-policy prefix-set underlay-v4 prefix 0.0.0.0/0 mask-length-range 32..32
29 | set / routing-policy prefix-set underlay-v6 prefix ::/0 mask-length-range 128..128
30 | set / routing-policy policy export-underlay-v4 statement local match prefix prefix-set underlay-v4
31 | set / routing-policy policy export-underlay-v4 statement local match protocol local
32 | set / routing-policy policy export-underlay-v4 statement local action policy-result accept
33 | set / routing-policy policy export-underlay-v6 statement local match prefix prefix-set underlay-v6
34 | set / routing-policy policy export-underlay-v6 statement local match protocol local
35 | set / routing-policy policy export-underlay-v6 statement local action policy-result accept
36 |
37 | set / network-instance default type default
38 | set / network-instance default admin-state enable
39 | set / network-instance default interface ethernet-1/4.0
40 | set / network-instance default interface system0.0
41 | set / network-instance default protocols bgp autonomous-system 64502
42 | set / network-instance default protocols bgp router-id 4.4.4.4
43 | set / network-instance default protocols bgp ebgp-default-policy import-reject-all false
44 | set / network-instance default protocols bgp ebgp-default-policy export-reject-all false
45 | set / network-instance default protocols bgp afi-safi ipv4-unicast admin-state enable
46 | set / network-instance default protocols bgp group ebgp peer-as 64500
47 | set / network-instance default protocols bgp group ebgp afi-safi ipv6-unicast admin-state enable
48 | set / network-instance default protocols bgp group evpn peer-as 65500
49 | set / network-instance default protocols bgp group evpn multihop admin-state enable
50 | set / network-instance default protocols bgp group evpn afi-safi evpn admin-state enable
51 | set / network-instance default protocols bgp group evpn afi-safi ipv4-unicast admin-state disable
52 | set / network-instance default protocols bgp group evpn afi-safi ipv6-unicast admin-state disable
53 | set / network-instance default protocols bgp group evpn local-as as-number 65500
54 | set / network-instance default protocols bgp neighbor 10.10.10.10 peer-group evpn
55 | set / network-instance default protocols bgp neighbor 10.10.10.10 transport local-address 4.4.4.4
56 | set / network-instance default protocols bgp neighbor 192.168.20.5 peer-group ebgp
57 | set / network-instance default protocols bgp neighbor 192.168.20.5 export-policy [ export-underlay-v4 ]
58 | set / network-instance default protocols bgp neighbor 192.168.20.5 afi-safi ipv6-unicast admin-state disable
59 | set / network-instance default protocols bgp neighbor 192:168:20::5 peer-group ebgp
60 | set / network-instance default protocols bgp neighbor 192:168:20::5 export-policy [ export-underlay-v6 ]
61 | set / network-instance default protocols bgp neighbor 192:168:20::5 afi-safi ipv4-unicast admin-state disable
62 | set / network-instance default protocols bgp neighbor 2001::10 peer-group evpn
63 | set / network-instance default protocols bgp neighbor 2001::10 transport local-address 2001::4
64 | set / network-instance ip-vrf-1 type ip-vrf
65 | set / network-instance ip-vrf-1 admin-state enable
66 | set / network-instance ip-vrf-1 interface irb0.0
67 | set / network-instance ip-vrf-1 vxlan-interface vxlan24.200
68 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 encapsulation-type vxlan
69 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 vxlan-interface vxlan24.200
70 | set / network-instance ip-vrf-1 protocols bgp-evpn bgp-instance 1 evi 200
71 | set / network-instance ip-vrf-1 protocols bgp-vpn bgp-instance 1 route-distinguisher rd 4.4.4.4:200
72 | set / network-instance ip-vrf-1 protocols bgp-vpn bgp-instance 1 route-target export-rt target:65500:200
73 | set / network-instance ip-vrf-1 protocols bgp-vpn bgp-instance 1 route-target import-rt target:65500:200
74 | set / network-instance mac-vrf-1 type mac-vrf
75 | set / network-instance mac-vrf-1 interface irb0.0
76 | set / network-instance mac-vrf-1 interface lag1.0
77 | set / network-instance mac-vrf-1 interface lag2.0
78 | set / network-instance mac-vrf-1 vxlan-interface vxlan13.100
79 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 encapsulation-type vxlan
80 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 vxlan-interface vxlan13.100
81 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 evi 100
82 | set / network-instance mac-vrf-1 protocols bgp-evpn bgp-instance 1 ecmp 2
83 | set / network-instance mac-vrf-1 protocols bgp-vpn bgp-instance 1 route-distinguisher rd 4.4.4.4:100
84 | set / network-instance mac-vrf-1 protocols bgp-vpn bgp-instance 1 route-target export-rt target:65500:100
85 | set / network-instance mac-vrf-1 protocols bgp-vpn bgp-instance 1 route-target import-rt target:65500:100
86 |
87 | set / tunnel-interface vxlan13 vxlan-interface 100 type bridged
88 | set / tunnel-interface vxlan13 vxlan-interface 100 ingress vni 100
89 | set / tunnel-interface vxlan24 vxlan-interface 200 type routed
90 | set / tunnel-interface vxlan24 vxlan-interface 200 ingress vni 200
91 |
92 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 admin-state enable
93 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 esi 22:01:01:01:01:01:01:16:10:60
94 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 multi-homing-mode all-active
95 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L2 interface lag1
96 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 admin-state enable
97 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 esi 44:03:03:03:03:03:03:90:01:01
98 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 multi-homing-mode all-active
99 | set / system network-instance protocols evpn ethernet-segments bgp-instance 1 ethernet-segment ES-L3 interface lag2
100 | set / system network-instance protocols bgp-vpn bgp-instance 1
101 |
102 | set / system aaa authorization role plugin superuser true
103 | set / system aaa authorization role plugin services [ cli ]
104 | set / system aaa authentication user auser password auser
105 | set / system aaa authentication user auser role [ plugin ]
106 | set / system aaa authentication user cnxuser password cnxuser
107 | set / system aaa authentication user cnxuser role [ plugin ]
108 | set / system aaa authentication user juser password juser
109 | set / system aaa authentication user juser role [ plugin ]
110 | set / system aaa authentication user nokuser password nokuser
111 | set / system aaa authentication user nokuser role [ plugin ]
112 |
--------------------------------------------------------------------------------
/lab/evpn/spine-startup.cfg:
--------------------------------------------------------------------------------
1 | set / interface ethernet-1/1 description To-Leaf1-1
2 | set / interface ethernet-1/1 admin-state enable
3 | set / interface ethernet-1/1 subinterface 0 ipv4 admin-state enable
4 | set / interface ethernet-1/1 subinterface 0 ipv4 address 192.168.10.3/31
5 | set / interface ethernet-1/1 subinterface 0 ipv6 admin-state enable
6 | set / interface ethernet-1/1 subinterface 0 ipv6 address 192:168:10::3/127
7 | set / interface ethernet-1/2 description To-Leaf1-2
8 | set / interface ethernet-1/2 admin-state enable
9 | set / interface ethernet-1/2 subinterface 0 ipv4 admin-state enable
10 | set / interface ethernet-1/2 subinterface 0 ipv4 address 192.168.10.5/31
11 | set / interface ethernet-1/2 subinterface 0 ipv6 admin-state enable
12 | set / interface ethernet-1/2 subinterface 0 ipv6 address 192:168:10::5/127
13 | set / interface ethernet-1/3 description To-Leaf2-1
14 | set / interface ethernet-1/3 admin-state enable
15 | set / interface ethernet-1/3 subinterface 0 ipv4 admin-state enable
16 | set / interface ethernet-1/3 subinterface 0 ipv4 address 192.168.20.3/31
17 | set / interface ethernet-1/3 subinterface 0 ipv6 admin-state enable
18 | set / interface ethernet-1/3 subinterface 0 ipv6 address 192:168:20::3/127
19 | set / interface ethernet-1/4 description To-Leaf2-2
20 | set / interface ethernet-1/4 admin-state enable
21 | set / interface ethernet-1/4 subinterface 0 ipv4 admin-state enable
22 | set / interface ethernet-1/4 subinterface 0 ipv4 address 192.168.20.5/31
23 | set / interface ethernet-1/4 subinterface 0 ipv6 admin-state enable
24 | set / interface ethernet-1/4 subinterface 0 ipv6 address 192:168:20::5/127
25 | set / interface system0 subinterface 0 ipv4 admin-state enable
26 | set / interface system0 subinterface 0 ipv4 address 10.10.10.10/32
27 | set / interface system0 subinterface 0 ipv6 admin-state enable
28 | set / interface system0 subinterface 0 ipv6 address 2001::10/128
29 |
30 | set / routing-policy prefix-set underlay-v4 prefix 0.0.0.0/0 mask-length-range 32..32
31 | set / routing-policy prefix-set underlay-v6 prefix ::/0 mask-length-range 128..128
32 | set / routing-policy policy export-all statement all-routes action policy-result accept
33 | set / routing-policy policy export-all statement all-direct match protocol local
34 | set / routing-policy policy export-all statement all-direct action policy-result accept
35 | set / routing-policy policy export-underlay-v4 statement local match prefix prefix-set underlay-v4
36 | set / routing-policy policy export-underlay-v4 statement local match protocol local
37 | set / routing-policy policy export-underlay-v4 statement local action policy-result accept
38 | set / routing-policy policy export-underlay-v6 statement local match prefix prefix-set underlay-v6
39 | set / routing-policy policy export-underlay-v6 statement local match protocol local
40 | set / routing-policy policy export-underlay-v6 statement local action policy-result accept
41 | set / routing-policy policy import-all statement all-routes action policy-result accept
42 |
43 | set / network-instance default type default
44 | set / network-instance default admin-state enable
45 | set / network-instance default interface ethernet-1/1.0
46 | set / network-instance default interface ethernet-1/2.0
47 | set / network-instance default interface ethernet-1/3.0
48 | set / network-instance default interface ethernet-1/4.0
49 | set / network-instance default interface system0.0
50 | set / network-instance default protocols bgp admin-state enable
51 | set / network-instance default protocols bgp autonomous-system 64500
52 | set / network-instance default protocols bgp router-id 10.10.10.10
53 | set / network-instance default protocols bgp ebgp-default-policy import-reject-all false
54 | set / network-instance default protocols bgp ebgp-default-policy export-reject-all false
55 | set / network-instance default protocols bgp afi-safi evpn evpn next-hop-self-route-reflector true
56 | set / network-instance default protocols bgp afi-safi ipv4-unicast admin-state enable
57 | set / network-instance default protocols bgp route-advertisement rapid-withdrawal true
58 | set / network-instance default protocols bgp route-reflector cluster-id 0.0.0.1
59 | set / network-instance default protocols bgp group ebgp next-hop-self true
60 | set / network-instance default protocols bgp group ebgp export-policy [ export-underlay-v4 export-underlay-v6 ]
61 | set / network-instance default protocols bgp group ebgp afi-safi ipv6-unicast admin-state enable
62 | set / network-instance default protocols bgp group evpn next-hop-self true
63 | set / network-instance default protocols bgp group evpn peer-as 65500
64 | set / network-instance default protocols bgp group evpn multihop admin-state enable
65 | set / network-instance default protocols bgp group evpn afi-safi evpn admin-state enable
66 | set / network-instance default protocols bgp group evpn afi-safi ipv4-unicast admin-state disable
67 | set / network-instance default protocols bgp group evpn afi-safi ipv6-unicast admin-state disable
68 | set / network-instance default protocols bgp group evpn local-as as-number 65500
69 | set / network-instance default protocols bgp neighbor 1.1.1.1 peer-group evpn
70 | set / network-instance default protocols bgp neighbor 1.1.1.1 route-reflector client true
71 | set / network-instance default protocols bgp neighbor 1.1.1.1 route-reflector cluster-id 0.0.0.1
72 | set / network-instance default protocols bgp neighbor 1.1.1.1 transport local-address 10.10.10.10
73 | set / network-instance default protocols bgp neighbor 2.2.2.2 peer-group evpn
74 | set / network-instance default protocols bgp neighbor 2.2.2.2 route-reflector client true
75 | set / network-instance default protocols bgp neighbor 2.2.2.2 route-reflector cluster-id 0.0.0.1
76 | set / network-instance default protocols bgp neighbor 2.2.2.2 transport local-address 10.10.10.10
77 | set / network-instance default protocols bgp neighbor 3.3.3.3 peer-group evpn
78 | set / network-instance default protocols bgp neighbor 3.3.3.3 route-reflector client true
79 | set / network-instance default protocols bgp neighbor 3.3.3.3 route-reflector cluster-id 0.0.0.1
80 | set / network-instance default protocols bgp neighbor 3.3.3.3 transport local-address 10.10.10.10
81 | set / network-instance default protocols bgp neighbor 4.4.4.4 peer-group evpn
82 | set / network-instance default protocols bgp neighbor 4.4.4.4 route-reflector client true
83 | set / network-instance default protocols bgp neighbor 4.4.4.4 route-reflector cluster-id 0.0.0.1
84 | set / network-instance default protocols bgp neighbor 4.4.4.4 transport local-address 10.10.10.10
85 | set / network-instance default protocols bgp neighbor 192.168.10.2 peer-as 64501
86 | set / network-instance default protocols bgp neighbor 192.168.10.2 peer-group ebgp
87 | set / network-instance default protocols bgp neighbor 192.168.10.2 route-reflector client true
88 | set / network-instance default protocols bgp neighbor 192.168.10.2 route-reflector cluster-id 0.0.0.1
89 | set / network-instance default protocols bgp neighbor 192.168.10.4 peer-as 64501
90 | set / network-instance default protocols bgp neighbor 192.168.10.4 peer-group ebgp
91 | set / network-instance default protocols bgp neighbor 192.168.10.4 route-reflector client true
92 | set / network-instance default protocols bgp neighbor 192.168.10.4 route-reflector cluster-id 0.0.0.1
93 | set / network-instance default protocols bgp neighbor 192.168.20.2 peer-as 64502
94 | set / network-instance default protocols bgp neighbor 192.168.20.2 peer-group ebgp
95 | set / network-instance default protocols bgp neighbor 192.168.20.2 route-reflector client true
96 | set / network-instance default protocols bgp neighbor 192.168.20.2 route-reflector cluster-id 0.0.0.1
97 | set / network-instance default protocols bgp neighbor 192.168.20.4 peer-as 64502
98 | set / network-instance default protocols bgp neighbor 192.168.20.4 peer-group ebgp
99 | set / network-instance default protocols bgp neighbor 192.168.20.4 route-reflector client true
100 | set / network-instance default protocols bgp neighbor 192.168.20.4 route-reflector cluster-id 0.0.0.1
101 | set / network-instance default protocols bgp neighbor 192:168:10::2 admin-state enable
102 | set / network-instance default protocols bgp neighbor 192:168:10::2 peer-as 64501
103 | set / network-instance default protocols bgp neighbor 192:168:10::2 peer-group ebgp
104 | set / network-instance default protocols bgp neighbor 192:168:10::2 route-reflector client true
105 | set / network-instance default protocols bgp neighbor 192:168:10::2 route-reflector cluster-id 0.0.0.1
106 | set / network-instance default protocols bgp neighbor 192:168:10::4 admin-state enable
107 | set / network-instance default protocols bgp neighbor 192:168:10::4 peer-as 64501
108 | set / network-instance default protocols bgp neighbor 192:168:10::4 peer-group ebgp
109 | set / network-instance default protocols bgp neighbor 192:168:10::4 route-reflector client true
110 | set / network-instance default protocols bgp neighbor 192:168:10::4 route-reflector cluster-id 0.0.0.1
111 | set / network-instance default protocols bgp neighbor 192:168:20::2 admin-state enable
112 | set / network-instance default protocols bgp neighbor 192:168:20::2 peer-as 64502
113 | set / network-instance default protocols bgp neighbor 192:168:20::2 peer-group ebgp
114 | set / network-instance default protocols bgp neighbor 192:168:20::2 route-reflector client true
115 | set / network-instance default protocols bgp neighbor 192:168:20::2 route-reflector cluster-id 0.0.0.1
116 | set / network-instance default protocols bgp neighbor 192:168:20::4 admin-state enable
117 | set / network-instance default protocols bgp neighbor 192:168:20::4 peer-as 64502
118 | set / network-instance default protocols bgp neighbor 192:168:20::4 peer-group ebgp
119 | set / network-instance default protocols bgp neighbor 192:168:20::4 route-reflector client true
120 | set / network-instance default protocols bgp neighbor 192:168:20::4 route-reflector cluster-id 0.0.0.1
121 | set / network-instance default protocols bgp neighbor 2001::1 admin-state enable
122 | set / network-instance default protocols bgp neighbor 2001::1 peer-group evpn
123 | set / network-instance default protocols bgp neighbor 2001::1 route-reflector client true
124 | set / network-instance default protocols bgp neighbor 2001::1 route-reflector cluster-id 0.0.0.1
125 | set / network-instance default protocols bgp neighbor 2001::1 transport local-address 2001::10
126 | set / network-instance default protocols bgp neighbor 2001::2 admin-state enable
127 | set / network-instance default protocols bgp neighbor 2001::2 peer-group evpn
128 | set / network-instance default protocols bgp neighbor 2001::2 route-reflector client true
129 | set / network-instance default protocols bgp neighbor 2001::2 route-reflector cluster-id 0.0.0.1
130 | set / network-instance default protocols bgp neighbor 2001::2 transport local-address 2001::10
131 | set / network-instance default protocols bgp neighbor 2001::3 admin-state enable
132 | set / network-instance default protocols bgp neighbor 2001::3 peer-group evpn
133 | set / network-instance default protocols bgp neighbor 2001::3 route-reflector client true
134 | set / network-instance default protocols bgp neighbor 2001::3 route-reflector cluster-id 0.0.0.1
135 | set / network-instance default protocols bgp neighbor 2001::3 transport local-address 2001::10
136 | set / network-instance default protocols bgp neighbor 2001::4 admin-state enable
137 | set / network-instance default protocols bgp neighbor 2001::4 peer-group evpn
138 | set / network-instance default protocols bgp neighbor 2001::4 route-reflector client true
139 | set / network-instance default protocols bgp neighbor 2001::4 route-reflector cluster-id 0.0.0.1
140 | set / network-instance default protocols bgp neighbor 2001::4 transport local-address 2001::10
141 |
142 | set / system aaa authorization role plugin superuser true
143 | set / system aaa authorization role plugin services [ cli ]
144 | set / system aaa authentication user auser password auser
145 | set / system aaa authentication user auser role [ plugin ]
146 | set / system aaa authentication user cnxuser password cnxuser
147 | set / system aaa authentication user cnxuser role [ plugin ]
148 | set / system aaa authentication user juser password juser
149 | set / system aaa authentication user juser role [ plugin ]
150 | set / system aaa authentication user nokuser password nokuser
151 | set / system aaa authentication user nokuser role [ plugin ]
152 |
--------------------------------------------------------------------------------
/nokia/README.md:
--------------------------------------------------------------------------------
1 | # Custom CLI Plugins for Nokia SR OS
2 |
3 | The following CLI plugins are available in this repo:
4 |
5 | | Command | Contributor |
6 | |---|---|
7 | | `show router bgp summary` | [giancarlo3g](https://github.com/giancarlo3g) |
8 | | `show service id evpn-mpls` | [zenodhaene](https://github.com/zenodhaene) |
9 | | `show service id vxlan` | [zenodhaene](https://github.com/zenodhaene) |
10 |
11 | ## Testing
12 |
13 | Deploy the EVPN lab. Login to any leaf or spine node using `nokuser/nokuser` and try any of the above commands.
14 |
15 | ## Nokia SROS scripts
16 |
17 | ### `evpn_report.py`
18 |
19 | This script introduces a custom command that allows the user to visualize the EVPN endpoints for both single-homed and multi-homed (ethernet segment) destinations. This is useful for figuring out where an EVPN-enabled service is configured.
20 |
21 | The command works for EVPN services with both VxLAN and MPLS transport tunnels. It requires the `service_report.py` script to also be copied to the `plugins` folder.
22 |
23 | ### Command syntax (EVPN-VxLAN services)
24 |
25 | ```
26 | /show service id evpn-vxlan vxlan destinations
27 | ```
28 |
29 | Where `evpn-vxlan` is an EVPN-enabled service with VxLAN transport tunnels.
30 |
31 |
32 | Example execution
33 |
34 | ```
35 | --{ + running }--[ ]--
36 | A:admin@leaf-3# /show service id evpn-vxlan vxlan destinations
37 |
38 | ===============================================================================
39 | Egress VTEP, VNI (Instance 1)
40 | ===============================================================================
41 | VTEP Address Egress VNI Oper Mcast Num
42 | State MACs
43 | -------------------------------------------------------------------------------
44 | 10.0.0.3 100 Up BUM N/A
45 | 10.0.0.4 100 Up BUM N/A
46 | -------------------------------------------------------------------------------
47 | Number of Egress VTEP, VNI : 2
48 | -------------------------------------------------------------------------------
49 | ===============================================================================
50 |
51 | ===============================================================================
52 | Egress VTEP, VNI (Instance 2)
53 | ===============================================================================
54 | VTEP Address Egress VNI Oper Mcast Num
55 | State MACs
56 | -------------------------------------------------------------------------------
57 | No Matching Entries
58 | ===============================================================================
59 |
60 | ===============================================================================
61 | BGP EVPN-VXLAN Ethernet Segment Dest (Instance 1)
62 | ===============================================================================
63 | Eth SegId Num. Macs Last Update
64 | -------------------------------------------------------------------------------
65 | 00:00:00:00:C0:FF:EE:00:00:01 100 2025-04-02 08:23:37
66 | -------------------------------------------------------------------------------
67 | Number of entries: 1
68 | -------------------------------------------------------------------------------
69 | ===============================================================================
70 |
71 | ===============================================================================
72 | BGP EVPN-VXLAN Ethernet Segment Dest (Instance 2)
73 | ===============================================================================
74 | Eth SegId Num. Macs Last Update
75 | -------------------------------------------------------------------------------
76 | No Matching Entries
77 | ===============================================================================
78 | ```
79 |
80 |
81 |
82 | ### Command syntax (EVPN-MPLS services)
83 |
84 | ```
85 | /show service id srmpls-evpn-vpls evpn-mpls
86 | ```
87 |
88 | Where `srmpls-evpn-vpls` is an EVPN-enabled service with SR-MPLS transport tunnels.
89 |
90 |
91 |
92 | Example execution
93 |
94 | ```
95 | --{ + running }--[ ]--
96 | A:CE-SXR-1# / show service id srmpls-evpn-vpls evpn-mpls
97 |
98 | ===============================================================================
99 | BGP EVPN-MPLS Dest (Instance 1)
100 | ===============================================================================
101 | TEP Address Transpor:Tnl Egr Label Oper Mcast Num
102 | State MACs
103 | -------------------------------------------------------------------------------
104 | 10.0.0.4 sr-isis:20004 103 Up bum N/A
105 | 10.0.1.3 sr-isis:20103 524287 Up bum N/A
106 | 10.0.1.3 sr-isis:20103 524287 Up None 10
107 | 10.0.1.4 sr-isis:20104 524287 Up bum N/A
108 | 10.0.1.4 sr-isis:20104 524287 Up None 10
109 | -------------------------------------------------------------------------------
110 | Number of entries: 5
111 | -------------------------------------------------------------------------------
112 | ===============================================================================
113 |
114 | ===============================================================================
115 | BGP EVPN-MPLS Dest (Instance 2)
116 | ===============================================================================
117 | TEP Address Transpor:Tnl Egr Label Oper Mcast Num
118 | State MACs
119 | -------------------------------------------------------------------------------
120 | No Matching Entries
121 | ===============================================================================
122 |
123 | ===============================================================================
124 | BGP EVPN-MPLS Ethernet Segment Dest (Instance 1)
125 | ===============================================================================
126 | Eth SegId Num. Macs Last Update
127 | -------------------------------------------------------------------------------
128 | 00:00:00:BE:EF:00:00:00:00:03 10 2025-04-02 08:48:34
129 | -------------------------------------------------------------------------------
130 | Number of entries: 1
131 | -------------------------------------------------------------------------------
132 | ===============================================================================
133 |
134 | ===============================================================================
135 | BGP EVPN-MPLS Ethernet Segment Dest (Instance 2)
136 | ===============================================================================
137 | Eth SegId Num. Macs Last Update
138 | -------------------------------------------------------------------------------
139 | No Matching Entries
140 | ===============================================================================
141 | ```
142 |
143 |
--------------------------------------------------------------------------------
/nokia/plugins/service_report.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | from srlinux.location import build_path
5 | from srlinux.mgmt.cli import CliPlugin, KeyCompleter
6 | from srlinux.mgmt.cli.execute_error import ExecuteError
7 | from srlinux.syntax import Syntax
8 |
9 | # Try potential base directories
10 | potential_paths = [
11 | os.path.expanduser('~/cli'),
12 | '/etc/opt/srlinux/cli'
13 | ]
14 |
15 | # Find the first valid path
16 | import_base = None
17 | for path in potential_paths:
18 | if os.path.exists(path):
19 | import_base = path
20 | break
21 |
22 | if import_base is None:
23 | raise ImportError("Could not find a valid CLI plugin base directory")
24 |
25 | # Construct the import path
26 | import_path = os.path.join(import_base, "evpn")
27 |
28 | # Add to Python path if not already present
29 | if import_path not in sys.path:
30 | sys.path.insert(0, import_path)
31 |
32 | from evpn_report import EvpnDestinationReport
33 |
34 |
35 | class Plugin(CliPlugin):
36 | def load(self, cli, **kwargs):
37 | # root command: `/show service`
38 | syntax = Syntax(
39 | name="service",
40 | short_help="SROS MultiCLI service commands",
41 | help="SROS compability commands for services on SRLinux (project MultiCLI)",
42 | )
43 |
44 | service = cli.show_mode.add_command(
45 | syntax
46 | )
47 |
48 | # subcommand: `/show service id `
49 | service_id_syntax = Syntax(
50 | name="id",
51 | short_help="Service ID",
52 | help="ID of the network-instance",
53 | )
54 |
55 | service_id_syntax.add_unnamed_argument(
56 | 'name',
57 | default='*',
58 | suggestions=KeyCompleter(
59 | path="/network-instance[name=*]"
60 | )
61 | )
62 |
63 | service_id = service.add_command(
64 | service_id_syntax
65 | )
66 |
67 | # service_id subcommands:
68 | service_id.add_command(
69 | Syntax(
70 | name="evpn-mpls",
71 | short_help="EVPN MPLS destinations",
72 | help="Information about EVPN MPLS tunnel destinations for a particular service",
73 | ),
74 | update_location=False,
75 | callback=self._evpn_mpls,
76 | schema=EvpnDestinationReport().get_schema()
77 | )
78 |
79 | vxlan = service_id.add_command(
80 | Syntax(
81 | name="vxlan",
82 | short_help="VxLAN commands",
83 | help="Various show commands for VxLAN-enabled services",
84 | )
85 | )
86 |
87 | vxlan.add_command(
88 | Syntax(
89 | name="destinations",
90 | short_help="EVPN VxLAN destinations",
91 | help="Information about EVPN VXLAN tunnel destinations for a particular service",
92 | ),
93 | update_location=False,
94 | callback=self._evpn_vxlan,
95 | schema=EvpnDestinationReport().get_schema()
96 | )
97 |
98 | def _evpn_mpls(self, state, arguments, output, **_kwargs):
99 | if state.is_intermediate_command:
100 | return
101 | self._validate_network_instance(state, arguments)
102 | EvpnDestinationReport().print_mpls(state, arguments, output, **_kwargs)
103 |
104 | def _evpn_vxlan(self, state, arguments, output, **_kwargs):
105 | if state.is_intermediate_command:
106 | return
107 | self._validate_network_instance(state, arguments)
108 | EvpnDestinationReport().print_vxlan(state, arguments, output, **_kwargs)
109 |
110 | def _validate_network_instance(self, state, arguments):
111 | nw_name = arguments.get('id', 'name')
112 | if '*' in nw_name:
113 | return
114 | path = build_path('/network-instance[name={name}]/type', name=nw_name)
115 | server_data = state.server_data_store.get_data(path, recursive=False)
116 | for nw in server_data.network_instance.items():
117 | if nw.type != 'mac-vrf':
118 | raise ExecuteError('service command available for mac-vrf only')
119 |
--------------------------------------------------------------------------------
/nokia/plugins/sros_bgp_report.py:
--------------------------------------------------------------------------------
1 | from modulefinder import Module
2 | from srlinux.location import build_path
3 | from srlinux.mgmt.cli import CliPlugin, RequiredPlugin, KeyCompleter
4 | from srlinux.syntax import Syntax
5 |
6 | ################################################################################
7 | # Importing BgpSummaryFilter class
8 | import sys
9 | import os
10 | # Dynamically find the correct module directory
11 | # Need to add the paths explicitly as the env that script runs is somewhere else
12 | potential_paths = [
13 | os.path.expanduser('~/cli'),
14 | '/etc/opt/srlinux/cli'
15 | ]
16 | # Find the first valid path
17 | import_base = None
18 | for path in potential_paths:
19 | if os.path.exists(path):
20 | import_base = path
21 | break
22 | if import_base is None:
23 | raise ImportError("Could not find a valid CLI plugin base directory")
24 | import_path = os.path.join(import_base, "bgp")
25 | # Add to Python path if not already present
26 | if import_path not in sys.path:
27 | sys.path.insert(0, import_path)
28 | # Import your subcodes
29 | from sros_bgpsummary import BgpSummaryFilter
30 | ################################################################################
31 |
32 | class Plugin(CliPlugin):
33 | '''
34 | Adds bgp routes ipv4 summary show reports.
35 | '''
36 |
37 | def get_required_plugins(self):
38 |
39 | return [
40 | # bgp_reports adds 'show network-instance' so it must be loaded first
41 | # to add our new plugin beneath it.
42 | RequiredPlugin(module='srlinux', plugin='sros_router_report')
43 | ]
44 |
45 | def load(self, cli, **_kwargs):
46 | router = cli.show_mode.root.get_command('router')
47 |
48 | bgp = router.add_command(
49 | Syntax('bgp', help='show bgp information for a network instance'),
50 | update_location=True
51 | )
52 | bgp.add_command(
53 | BgpSummaryFilter().get_syntax(),
54 | update_location=False,
55 | callback=BgpSummaryFilter().print,
56 | schema=BgpSummaryFilter().get_data_schema(),
57 | )
58 |
--------------------------------------------------------------------------------
/nokia/plugins/sros_router_report.py:
--------------------------------------------------------------------------------
1 | from srlinux.location import build_path
2 | from srlinux.mgmt.cli import CliPlugin, KeyCompleter
3 | from srlinux.syntax import Syntax
4 |
5 | class Plugin(CliPlugin):
6 | '''
7 | Adds SROS router show reports.
8 | '''
9 |
10 | def load(self, cli, **_kwargs):
11 | router = cli.show_mode.add_command(
12 | Syntax('router', help="show router information")
13 | .add_unnamed_argument('netinst', default='default', help = 'network instance name', suggestions=KeyCompleter('/network-instance[name=*]')),
14 | )
15 |
--------------------------------------------------------------------------------
/srl-evpn-mh.clab.yml:
--------------------------------------------------------------------------------
1 | name: srl-mh-evpn
2 |
3 | mgmt:
4 | network: srl-mh-evpn-mgmt
5 | ipv4-subnet: 172.20.10.0/24
6 | ipv6-subnet: 2001:172:20:10::/64
7 |
8 | prefix: ""
9 | topology:
10 | defaults:
11 | kind: nokia_srlinux
12 | kinds:
13 | nokia_srlinux:
14 | type: ixrd2l
15 | image: ghcr.io/nokia/srlinux:25.3.1
16 | binds:
17 | - arista:/home/auser/cli
18 | - juniper:/home/juser/cli
19 | - cisco-nx:/home/cnxuser/cli
20 | - nokia:/home/nokuser/cli
21 | linux:
22 | image: ghcr.io/srl-labs/network-multitool
23 | nodes:
24 | leaf1-1:
25 | startup-config: lab/evpn/leaf1-1-startup.cfg
26 | mgmt-ipv4: 172.20.10.2
27 | mgmt-ipv6: 2001:172:20:10::2
28 | leaf1-2:
29 | startup-config: lab/evpn/leaf1-2-startup.cfg
30 | mgmt-ipv4: 172.20.10.3
31 | mgmt-ipv6: 2001:172:20:10::3
32 | leaf2-1:
33 | startup-config: lab/evpn/leaf2-1-startup.cfg
34 | mgmt-ipv4: 172.20.10.4
35 | mgmt-ipv6: 2001:172:20:10::4
36 | leaf2-2:
37 | startup-config: lab/evpn/leaf2-2-startup.cfg
38 | mgmt-ipv4: 172.20.10.5
39 | mgmt-ipv6: 2001:172:20:10::5
40 | spine:
41 | startup-config: lab/evpn/spine-startup.cfg
42 | mgmt-ipv4: 172.20.10.6
43 | mgmt-ipv6: 2001:172:20:10::6
44 | client1:
45 | kind: linux
46 | mgmt-ipv4: 172.20.10.10
47 | mgmt-ipv6: 2001:172:20:10::10
48 | exec:
49 | - ip link add bond0 type bond mode balance-rr
50 | - ip link set eth1 down
51 | - ip link set eth1 master bond0
52 | - ip link set eth2 down
53 | - ip link set eth2 master bond0
54 | - ip link set bond0 up
55 | - ip address add 172.16.10.50/24 dev bond0
56 | - ip -6 address add 172:16:10::50/64 dev bond0
57 | - ip route add 10.90.1.0/24 via 172.16.10.254
58 | - ip route add 10.80.1.0/24 via 172.16.10.254
59 | - ip -6 route add 10:90:1::/64 via 172:16:10::254
60 | - ip -6 route add 10:80:1::/64 via 172:16:10::254
61 | client2:
62 | kind: linux
63 | mgmt-ipv4: 172.20.10.11
64 | mgmt-ipv6: 2001:172:20:10::11
65 | exec:
66 | - ip link add bond0 type bond mode balance-rr
67 | - ip link set eth1 down
68 | - ip link set eth1 master bond0
69 | - ip link set eth2 down
70 | - ip link set eth2 master bond0
71 | - ip link set bond0 up
72 | - ip address add 10.80.1.1/24 dev bond0
73 | - ip -6 address add 10:80:1::1/64 dev bond0
74 | - ip route add 10.90.1.0/24 via 10.80.1.2
75 | - ip route add 172.16.10.0/24 via 10.80.1.2
76 | - ip -6 route add 10:90:1::/64 via 10:80:1::2
77 | client3:
78 | kind: linux
79 | mgmt-ipv4: 172.20.10.12
80 | mgmt-ipv6: 2001:172:20:10::12
81 | exec:
82 | - ip link add bond0 type bond mode balance-rr
83 | - ip link set eth1 down
84 | - ip link set eth1 master bond0
85 | - ip link set eth2 down
86 | - ip link set eth2 master bond0
87 | - ip link set bond0 up
88 | - ip address add 172.16.10.60/24 dev bond0
89 | - ip -6 address add 172:16:10::60/64 dev bond0
90 | - ip route add 10.90.1.0/24 via 172.16.10.253
91 | - ip route add 10.80.1.0/24 via 172.16.10.253
92 | - ip -6 route add 10:90:1::/64 via 172:16:10::253
93 | - ip -6 route add 10:80:1::/64 via 172:16:10::253
94 | client4:
95 | kind: linux
96 | mgmt-ipv4: 172.20.10.13
97 | mgmt-ipv6: 2001:172:20:10::13
98 | exec:
99 | - ip link add bond0 type bond mode balance-rr
100 | - ip link set eth1 down
101 | - ip link set eth1 master bond0
102 | - ip link set eth2 down
103 | - ip link set eth2 master bond0
104 | - ip link set bond0 up
105 | - ip address add 10.90.1.1/24 dev bond0
106 | - ip -6 address add 10:90:1::1/64 dev bond0
107 | - ip route add 10.80.1.0/24 via 10.90.1.2
108 | - ip route add 172.16.10.0/24 via 10.90.1.2
109 | - ip -6 route add 10:80:1::/64 via 10:90:1::2
110 | links:
111 | # spine-leaf links
112 | - endpoints: [ "leaf1-1:e1-1", "spine:e1-1" ]
113 | - endpoints: [ "leaf1-2:e1-2", "spine:e1-2" ]
114 | - endpoints: [ "leaf2-1:e1-3", "spine:e1-3" ]
115 | - endpoints: [ "leaf2-2:e1-4", "spine:e1-4" ]
116 | # leaf-client links
117 | - endpoints: [ "client1:eth1", "leaf1-1:e1-10" ]
118 | - endpoints: [ "client1:eth2", "leaf1-2:e1-11" ]
119 | - endpoints: [ "client2:eth2", "leaf1-1:e1-11" ]
120 | - endpoints: [ "client2:eth1", "leaf1-2:e1-10" ]
121 | - endpoints: [ "client3:eth1", "leaf2-1:e1-10" ]
122 | - endpoints: [ "client3:eth2", "leaf2-2:e1-11" ]
123 | - endpoints: [ "client4:eth2", "leaf2-1:e1-11" ]
124 | - endpoints: [ "client4:eth1", "leaf2-2:e1-10" ]
125 |
--------------------------------------------------------------------------------