Skip to content

Commit a6863c2

Browse files
torbbangkaelemchellt
authored
Cisco vIOS & vIOS-L2 (#2902)
Adds support for Cisco vIOS and vIOSL2 nodes --------- Co-authored-by: Kaelem Chandra <[email protected]> Co-authored-by: Roman Dodin <[email protected]>
1 parent 78bc9a6 commit a6863c2

File tree

12 files changed

+497
-23
lines changed

12 files changed

+497
-23
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ In addition to native containerized NOSes, containerlab can launch traditional v
4848
* [Cisco c8000v](https://containerlab.dev/manual/kinds/vr-c8000v/)
4949
* [Cisco SD-WAN](https://containerlab.dev/manual/kinds/cisco_sdwan/)
5050
* [Cisco CSR 1000v](https://containerlab.dev/manual/kinds/vr-csr)
51+
* [Cisco vIOS](https://containerlab.dev/manual/kinds/cisco_vios/)
5152
* [Cisco ASAv](https://containerlab.dev/manual/kinds/cisco_asav/)
5253
* [Cisco FTDv](https://containerlab.dev/manual/kinds/vr-ftdv/)
5354
* [Dell FTOS10v](https://containerlab.dev/manual/kinds/vr-ftosv)

core/register.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
clabnodesvr_cat9kv "github.com/srl-labs/containerlab/nodes/vr_cat9kv"
4040
clabnodesvr_csr "github.com/srl-labs/containerlab/nodes/vr_csr"
4141
clabnodesvr_freebsd "github.com/srl-labs/containerlab/nodes/vr_freebsd"
42+
clabnodescisco_vios "github.com/srl-labs/containerlab/nodes/cisco_vios"
4243
clabnodesvr_ftdv "github.com/srl-labs/containerlab/nodes/vr_ftdv"
4344
clabnodesvr_ftosv "github.com/srl-labs/containerlab/nodes/vr_ftosv"
4445
clabnodesvr_n9kv "github.com/srl-labs/containerlab/nodes/vr_n9kv"
@@ -82,6 +83,7 @@ func (c *CLab) RegisterNodes() { //nolint:funlen
8283
clabnodesvr_csr.Register(c.Reg)
8384
clabnodesvr_c8000v.Register(c.Reg)
8485
clabnodesvr_freebsd.Register(c.Reg)
86+
clabnodescisco_vios.Register(c.Reg)
8587
clabnodesgeneric_vm.Register(c.Reg)
8688
clabnodesdell_sonic.Register(c.Reg)
8789
clabnodesvr_ftosv.Register(c.Reg)

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ In addition to native containerized NOSes, containerlab can launch traditional v
5252
* [Cisco c8000v](manual/kinds/vr-c8000v.md)
5353
* [Cisco SD-WAN](manual/kinds/cisco_sdwan.md)
5454
* [Cisco CSR 1000v](manual/kinds/vr-csr.md)
55+
* [Cisco vIOS](manual/kinds/cisco_vios.md)
5556
* [Cisco ASAv](manual/kinds/cisco_asav.md)
5657
* [Cisco FTDv](manual/kinds/vr-ftdv.md)
5758
* [Dell FTOS10v](manual/kinds/vr-ftosv.md)

docs/manual/kinds/cisco_vios.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
search:
3+
boost: 4
4+
kind_code_name: cisco_vios
5+
kind_display_name: Cisco vIOS
6+
---
7+
# Cisco vIOS
8+
9+
Cisco vIOS virtualized router/switch is identified with `-{{ kind_code_name }}-` kind in the [topology file](../topo-def-file.md). It is built using [vrnetlab](../vrnetlab.md) project and essentially is a Qemu VM packaged in a docker container format.
10+
11+
The `cisco_vios` kind supports both router (vIOS) and Layer 2 switch (vIOSL2) images. The type is specified using the `type` parameter in the node configuration.
12+
13+
## Node Types
14+
15+
The `cisco_vios` kind supports two node types:
16+
17+
* `router` (default) - Cisco vIOS router image
18+
* `switch` - Cisco vIOSL2 Layer 2 switch image
19+
20+
The node type is specified using the `type` parameter:
21+
22+
```yaml
23+
topology:
24+
nodes:
25+
router1:
26+
kind: cisco_vios
27+
type: router # or omit for default router type
28+
image: vrnetlab/cisco_vios:159-3.M10
29+
30+
switch1:
31+
kind: cisco_vios
32+
type: switch
33+
image: vrnetlab/cisco_vios:L2-20200929
34+
```
35+
36+
## Managing Cisco vIOS nodes
37+
38+
Cisco vIOS node launched with containerlab can be managed via the following interfaces:
39+
40+
=== "bash"
41+
to connect to a `bash` shell of a running Cisco vIOS container:
42+
```bash
43+
docker exec -it <container-name/id> bash
44+
```
45+
=== "CLI"
46+
to connect to the vIOS CLI
47+
```bash
48+
ssh admin@<container-name/id>
49+
```
50+
51+
!!!info
52+
Default user credentials: `admin:admin`
53+
54+
## Interface naming
55+
56+
You can use [interfaces names](../topo-def-file.md#interface-naming) in the topology file like they appear in -{{ kind_display_name }}-.
57+
58+
The interface naming convention is: `GigabitEthernetX` (or `GiX`), where `X` is the port number.
59+
60+
With that naming convention in mind:
61+
62+
* `Gi0` - first data port available
63+
* `Gi1` - second data port, and so on...
64+
65+
The example ports above would be mapped to the following Linux interfaces inside the container running the -{{ kind_display_name }}- VM:
66+
67+
* `eth0` - management interface connected to the containerlab management network (rendered as `GigabitEthernet0/0` in the CLI)
68+
* `eth1` - first data interface, mapped to the first data port of the VM (rendered as `GigabitEthernet0`)
69+
* `eth2+` - second and subsequent data interfaces, mapped to the second and subsequent data ports of the VM (rendered as `GigabitEthernet1` and so on)
70+
71+
When containerlab launches -{{ kind_display_name }}- node the management interface gets assigned an address from the containerlab management network.
72+
73+
Data interfaces `GigabitEthernet0+` need to be configured with IP addressing manually using CLI or other available management interfaces.
74+
75+
## Features and options
76+
77+
### Node configuration
78+
79+
Cisco vIOS nodes come up with a basic configuration where only `admin` user and management interface are provisioned.
80+
81+
#### Startup configuration
82+
83+
It is possible to make vIOS nodes boot up with a user-defined startup-config instead of a built-in one. With a [`startup-config`](../nodes.md#startup-config) property of the node/kind user sets the path to the config file that will be mounted to a container and used as a startup-config:
84+
85+
```yaml
86+
topology:
87+
nodes:
88+
node:
89+
kind: cisco_vios
90+
startup-config: myconfig.txt
91+
```
92+
93+
With this knob containerlab is instructed to take a file `myconfig.txt` from the directory that hosts the topology file, and copy it to the lab directory for that specific node under the `/config/startup-config.cfg` name. Then the directory that hosts the startup-config dir is mounted to the container. This will result in this config being applied at startup by the node.
94+
95+
Configuration is applied after the node is started, thus it can contain partial configuration snippets that you desire to add on top of the default config that a node boots up with.

docs/manual/vrnetlab.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
vr_rns:
33
"0.20.1": >-
4-
New platforms: [Cisco IOL](kinds/cisco_iol.md), [Cisco vIOS](https://github.com/srl-labs/vrnetlab/tree/master/cisco/vios), [Huawei VRP](kinds/huawei_vrp.md)<br/>
4+
New platforms: [Cisco IOL](kinds/cisco_iol.md), [Cisco vIOS](kinds/cisco_vios.md), [Huawei VRP](kinds/huawei_vrp.md)<br/>
55
The vrnetlab version (commit) is now part of the image labels under the `vrnetlab-version` name. This should help you identify what version of vrnetlab is used to build the image.
66
---
77
# VM-based routers integration
@@ -111,6 +111,7 @@ The images that work with containerlab will appear in the supported list as we i
111111
| Cisco XRv | [cisco_xrv](kinds/vr-xrv.md) | [SRL & XRv](../lab-examples/vr-xrv.md) | |
112112
| Cisco XRv9k | [cisco_xrv9k](kinds/vr-xrv9k.md) | [SRL & XRv9k](../lab-examples/vr-xrv9k.md) | |
113113
| Cisco CSR1000v | [cisco_csr](kinds/vr-csr.md) | | |
114+
| Cisco vIOS | [cisco_vios](kinds/cisco_vios.md) | | |
114115
| Cisco Nexus 9000v | [cisco_nexus9kv](kinds/vr-n9kv.md) | | |
115116
| Cisco ASAv | [cisco_asav](kinds/cisco_asav.md) | | |
116117
| Cisco FTDv | [cisco_ftdv](kinds/vr-ftdv.md) | | |

nodes/cisco_vios/cisco-vios.go

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// Copyright 2020 Nokia
2+
// Licensed under the BSD 3-Clause License.
3+
// SPDX-License-Identifier: BSD-3-Clause
4+
5+
package cisco_vios
6+
7+
import (
8+
"bytes"
9+
"context"
10+
_ "embed"
11+
"fmt"
12+
"os"
13+
"path"
14+
"path/filepath"
15+
"regexp"
16+
"strings"
17+
"text/template"
18+
19+
"github.com/charmbracelet/log"
20+
clabconstants "github.com/srl-labs/containerlab/constants"
21+
clabnodes "github.com/srl-labs/containerlab/nodes"
22+
clabtypes "github.com/srl-labs/containerlab/types"
23+
clabutils "github.com/srl-labs/containerlab/utils"
24+
)
25+
26+
const (
27+
typeRouter = "router"
28+
typeSwitch = "switch"
29+
typeL2 = "l2"
30+
31+
scrapliPlatformName = "cisco_ios"
32+
)
33+
34+
var (
35+
kindnames = []string{"cisco_vios"}
36+
defaultCredentials = clabnodes.NewCredentials("admin", "admin")
37+
38+
//go:embed vios.cfg
39+
cfgTemplate string
40+
41+
InterfaceRegexp = regexp.MustCompile(`(?:Gi|GigabitEthernet)\s?(?P<port>\d+)$`)
42+
InterfaceOffset = 0
43+
InterfaceHelp = "GiX or GigabitEthernetX (where X >= 0) or ethX (where X >= 1)"
44+
45+
validTypes = []string{typeRouter, typeSwitch, typeL2}
46+
)
47+
48+
// Register registers the node in the NodeRegistry.
49+
func Register(r *clabnodes.NodeRegistry) {
50+
platformAttrs := &clabnodes.PlatformAttrs{
51+
ScrapliPlatformName: scrapliPlatformName,
52+
}
53+
54+
nrea := clabnodes.NewNodeRegistryEntryAttributes(defaultCredentials, nil, platformAttrs)
55+
56+
r.Register(kindnames, func() clabnodes.Node {
57+
return new(vios)
58+
}, nrea)
59+
}
60+
61+
type vios struct {
62+
clabnodes.VRNode
63+
isL2Node bool
64+
bootCfg string
65+
partialStartupCfg string
66+
}
67+
68+
func (n *vios) Init(cfg *clabtypes.NodeConfig, opts ...clabnodes.NodeOption) error {
69+
// Init VRNode
70+
n.VRNode = *clabnodes.NewVRNode(n, defaultCredentials, scrapliPlatformName)
71+
// set virtualization requirement
72+
n.HostRequirements.VirtRequired = true
73+
74+
n.Cfg = cfg
75+
for _, o := range opts {
76+
o(n)
77+
}
78+
79+
// Validate node type
80+
nodeType := strings.ToLower(n.Cfg.NodeType)
81+
switch nodeType {
82+
case "", typeRouter:
83+
// Default to router type
84+
n.isL2Node = false
85+
case typeSwitch, typeL2:
86+
// L2 switch type
87+
n.isL2Node = true
88+
default:
89+
return fmt.Errorf("invalid node type '%s'. Valid types are: %s",
90+
n.Cfg.NodeType, strings.Join(validTypes, ", "))
91+
}
92+
93+
// env vars are used to set launch.py arguments in vrnetlab container
94+
defEnv := map[string]string{
95+
"CONNECTION_MODE": clabnodes.VrDefConnMode,
96+
"USERNAME": defaultCredentials.GetUsername(),
97+
"PASSWORD": defaultCredentials.GetPassword(),
98+
"DOCKER_NET_V4_ADDR": n.Mgmt.IPv4Subnet,
99+
"DOCKER_NET_V6_ADDR": n.Mgmt.IPv6Subnet,
100+
"CLAB_MGMT_PASSTHROUGH": "true", // force enable mgmt passthru
101+
}
102+
n.Cfg.Env = clabutils.MergeStringMaps(defEnv, n.Cfg.Env)
103+
104+
// mount config dir to support startup-config functionality
105+
n.Cfg.Binds = append(
106+
n.Cfg.Binds,
107+
fmt.Sprint(path.Join(n.Cfg.LabDir, n.ConfigDirName), ":/config"),
108+
)
109+
110+
if n.Cfg.Env["CONNECTION_MODE"] == "macvtap" {
111+
// mount dev dir to enable macvtap
112+
n.Cfg.Binds = append(n.Cfg.Binds, "/dev:/dev")
113+
}
114+
115+
n.Cfg.Cmd = fmt.Sprintf(
116+
"--username %s --password %s --hostname %s --connection-mode %s --trace",
117+
n.Cfg.Env["USERNAME"],
118+
n.Cfg.Env["PASSWORD"],
119+
n.Cfg.ShortName,
120+
n.Cfg.Env["CONNECTION_MODE"],
121+
)
122+
123+
n.InterfaceRegexp = InterfaceRegexp
124+
n.InterfaceOffset = InterfaceOffset
125+
n.InterfaceHelp = InterfaceHelp
126+
127+
return nil
128+
}
129+
130+
func (n *vios) PreDeploy(ctx context.Context, params *clabnodes.PreDeployParams) error {
131+
clabutils.CreateDirectory(n.Cfg.LabDir, clabconstants.PermissionsOpen)
132+
_, err := n.LoadOrGenerateCertificate(params.Cert, params.TopologyName)
133+
if err != nil {
134+
return err
135+
}
136+
137+
configDir := filepath.Join(n.Cfg.LabDir, n.ConfigDirName)
138+
clabutils.CreateDirectory(configDir, clabconstants.PermissionsOpen)
139+
140+
n.bootCfg = cfgTemplate
141+
142+
if n.Cfg.StartupConfig != "" {
143+
cfg, err := os.ReadFile(n.Cfg.StartupConfig)
144+
if err != nil {
145+
return err
146+
}
147+
148+
if clabutils.IsPartialConfigFile(n.Cfg.StartupConfig) {
149+
n.partialStartupCfg = string(cfg)
150+
} else {
151+
n.bootCfg = string(cfg)
152+
}
153+
}
154+
155+
return nil
156+
}
157+
158+
func (n *vios) PostDeploy(ctx context.Context, _ *clabnodes.PostDeployParams) error {
159+
log.Info("Running postdeploy actions", "kind", n.Cfg.Kind, "node", n.Cfg.ShortName)
160+
return n.genBootConfig()
161+
}
162+
163+
func (n *vios) genBootConfig() error {
164+
tplData := ViosTemplateData{
165+
Hostname: n.Cfg.ShortName,
166+
Username: defaultCredentials.GetUsername(),
167+
Password: defaultCredentials.GetPassword(),
168+
IsL2Node: n.isL2Node,
169+
MgmtIPv4Addr: n.Cfg.MgmtIPv4Address,
170+
MgmtIPv4SubnetMask: clabutils.CIDRToDDN(n.Cfg.MgmtIPv4PrefixLength),
171+
MgmtIPv4GW: n.Cfg.MgmtIPv4Gateway,
172+
MgmtIPv6Addr: n.Cfg.MgmtIPv6Address,
173+
MgmtIPv6PrefixLen: n.Cfg.MgmtIPv6PrefixLength,
174+
MgmtIPv6GW: n.Cfg.MgmtIPv6Gateway,
175+
PartialCfg: n.partialStartupCfg,
176+
}
177+
178+
viosCfgTpl, err := template.New("vios-config").Funcs(clabutils.CreateFuncs()).Parse(n.bootCfg)
179+
if err != nil {
180+
return fmt.Errorf("failed to parse cfg template for node %q: %w", n.Cfg.ShortName, err)
181+
}
182+
183+
buf := new(bytes.Buffer)
184+
err = viosCfgTpl.Execute(buf, tplData)
185+
if err != nil {
186+
return fmt.Errorf("failed to execute cfg template for node %q: %w", n.Cfg.ShortName, err)
187+
}
188+
189+
configDir := filepath.Join(n.Cfg.LabDir, n.ConfigDirName)
190+
dstCfg := filepath.Join(configDir, n.StartupCfgFName)
191+
err = clabutils.CreateFile(dstCfg, buf.String())
192+
if err != nil {
193+
return fmt.Errorf("failed to write cfg file for node %q: %w", n.Cfg.ShortName, err)
194+
}
195+
196+
return nil
197+
}
198+
199+
// Stores the vars exposed in the config template
200+
type ViosTemplateData struct {
201+
Hostname string
202+
Username string
203+
Password string
204+
IsL2Node bool
205+
MgmtIPv4Addr string
206+
MgmtIPv4SubnetMask string
207+
MgmtIPv4GW string
208+
MgmtIPv6Addr string
209+
MgmtIPv6PrefixLen int
210+
MgmtIPv6GW string
211+
PartialCfg string
212+
}

0 commit comments

Comments
 (0)