Skip to content

Commit 1c85607

Browse files
authored
Add per-node libvirt port forwarding (#2592)
* Move the clab-specific port forwarding functions into the common provider module * Use the same functions to build clab- and libvirt ports list * Use the node libvirt.ports list when building forwarding ports in the Vagrantfile * Update libvirt documentation, adding the fun fact that vagrant-libvirt module sets up SSH port forwarding (not a NAT rule) * Add platform integration tests. The 'clab' test can be tested with a host script, I'm clueless about testing the SSH port forwarding Also: * Fix libvirt import into virtualbox provider module (the imported class name must start with an underscore) * Update the libvirt port forwarding transformation test
1 parent a9c7d71 commit 1c85607

File tree

12 files changed

+112
-27
lines changed

12 files changed

+112
-27
lines changed

docs/labs/libvirt.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,18 @@ You can change the IPv4 address of a device's management interface with the **mg
211211
(libvirt-port-forwarding)=
212212
### Port Forwarding
213213

214-
*netlab* supports *vagrant-libvirt* port forwarding -- mapping of TCP ports on VM management IP address to ports on the host. You can use port forwarding to access the lab devices via the host's external IP address without exposing the management network to the outside world.
214+
*netlab* supports *vagrant-libvirt* port forwarding -- SSH forwarding of TCP ports on the VM management IP address to ports on the host (see *vagrant-libvirt documentation* for more details). Thus, you can access the lab devices via the host's external IP address without exposing the management network to the outside world.
215215

216-
Port forwarding is turned off by default and can be enabled by configuring the **defaults.providers.libvirt.forwarded** dictionary. Dictionary keys are TCP port names (`ssh`, `http`, `https`, or `netconf`), and dictionary values are the start values of host ports. *netlab* assigns a unique host port to every VM's forwarded port based on the start value and VM node ID.
216+
Port forwarding is turned off by default. It can be configured for a single node with the **libvirt.ports** node parameter -- a list of port mappings in the `host_port:vm_port` format. For example, the following topology maps the SSH port on node R1 to the host port 2001:
217+
218+
```
219+
nodes:
220+
r1:
221+
libvirt.ports:
222+
- 2001:22
223+
```
224+
225+
Port forwarding can also be enabled for all libvirt nodes with the **defaults.providers.libvirt.forwarded** dictionary. Dictionary keys are TCP port names (`ssh`, `http`, `https`, or `netconf`), and dictionary values are the start values of host ports. *netlab* assigns a unique host port to every VM's forwarded port based on the start value and VM node ID, and adds the port mapping to the node **libvirt.ports** list.
217226

218227
For example, when given the following topology...
219228

netsim/providers/__init__.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from ..utils.callback import Callback
1919
from ..augment import devices,links
20-
from ..data import get_box,get_empty_box,filemaps
20+
from ..data import get_box,get_empty_box,append_to_list,filemaps
2121
from ..utils import files as _files
2222
from ..utils import templates,log,strings
2323
from ..outputs.ansible import get_host_addresses
@@ -364,9 +364,9 @@ def select_topology(topology: Box, provider: str) -> Box:
364364
return topology
365365

366366
"""
367-
get_forwarded_ports -- build a list of forwarded ports for the specified node
367+
get_forwarded_ports -- build a list of default provider forwarded ports for the specified node
368368
"""
369-
def get_forwarded_ports(node: Box, topology: Box) -> list:
369+
def get_provider_forwarded_ports(node: Box, topology: Box) -> list:
370370
p = devices.get_provider(node,topology.defaults)
371371
fmap = topology.defaults.providers[p].get('forwarded',{}) # Provider-specific forwarded ports
372372
if not fmap: # No forwarded ports?
@@ -382,6 +382,15 @@ def get_forwarded_ports(node: Box, topology: Box) -> list:
382382

383383
return node_fp
384384

385+
def node_add_forwarded_ports(node: Box, fplist: list, topology: Box) -> None:
386+
if not fplist:
387+
return
388+
389+
p = devices.get_provider(node,topology.defaults)
390+
for port_map in fplist: # Iterate over forwarded port mappings
391+
port_map_string = f'{port_map[0]}:{port_map[1]}' # Build the provider-compatible map entry
392+
append_to_list(node[p],'ports',port_map_string) # ... and add it to the list of forwarded ports
393+
385394
"""
386395
validate_images -- check the images used by individual nodes against provider image repo
387396
"""

netsim/providers/clab.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pathlib
88
import argparse
99

10-
from . import _Provider,get_forwarded_ports,validate_mgmt_ip
10+
from . import _Provider,get_provider_forwarded_ports,node_add_forwarded_ports,validate_mgmt_ip
1111
from ..utils import log, strings, linuxbridge
1212
from ..data import filemaps, get_empty_box, append_to_list
1313
from ..data.types import must_be_dict
@@ -87,16 +87,6 @@ def destroy_ovs_bridge( brname: str ) -> bool:
8787

8888
GENERATED_CONFIG_PATH = "clab_files"
8989

90-
def add_forwarded_ports(node: Box, fplist: list) -> None:
91-
if not fplist:
92-
return
93-
94-
node.clab.ports = node.clab.ports or [] # Make sure the list of forwarded ports is a list
95-
for port_map in fplist: # Iterate over forwarded port mappings
96-
port_map_string = f'{port_map[0]}:{port_map[1]}' # Build the containerlab-compatible map entry
97-
if not port_map_string in node.clab.ports: # ... and add it to the list of forwarded ports
98-
node.clab.ports.append(port_map_string) # ... if the user didn't do it manually
99-
10090
'''
10191
normalize_clab_filemaps: convert clab templates and file binds into host:target lists
10292
'''
@@ -176,9 +166,9 @@ class Containerlab(_Provider):
176166

177167
def augment_node_data(self, node: Box, topology: Box) -> None:
178168
node.hostname = self.get_node_name(node.name,topology)
179-
node_fp = get_forwarded_ports(node,topology)
169+
node_fp = get_provider_forwarded_ports(node,topology)
180170
if node_fp:
181-
add_forwarded_ports(node,node_fp)
171+
node_add_forwarded_ports(node,node_fp,topology)
182172

183173
def node_post_transform(self, node: Box, topology: Box) -> None:
184174
add_daemon_filemaps(node,topology)

netsim/providers/libvirt.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from ..data import types,get_empty_box,get_box
1717
from ..utils import log,strings,linuxbridge
1818
from ..utils import files as _files
19-
from . import _Provider,validate_mgmt_ip
19+
from . import _Provider,validate_mgmt_ip,node_add_forwarded_ports,get_provider_forwarded_ports
2020
from ..augment import devices
2121
from ..augment.links import get_link_by_index
2222
from ..cli import is_dry_run,external_commands
@@ -292,6 +292,14 @@ def pre_transform(self, topology: Box) -> None:
292292
if not 'bridge' in l:
293293
l.bridge = "%s_%d" % (topology.name[0:10],l.linkindex)
294294

295+
"""
296+
Add default provider forwarded ports to node data
297+
"""
298+
def augment_node_data(self, node: Box, topology: Box) -> None:
299+
node_fp = get_provider_forwarded_ports(node,topology)
300+
if node_fp:
301+
node_add_forwarded_ports(node,node_fp,topology)
302+
295303
def node_post_transform(self, node: Box, topology: Box) -> None:
296304
if node.get('_set_ifindex'):
297305
pad_node_interfaces(node,topology)

netsim/providers/libvirt.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ attributes:
3434
nic_adapter_count: int
3535
image: str
3636
uuid: str
37+
ports: list
3738
link:
3839
permanent: bool
3940
public: { type: str, valid_values: [ bridge, vepa, passthrough, private ], true_value: bridge }

netsim/providers/virtualbox.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import typing
66

77
from . import _Provider
8-
from .libvirt import Libvirt
8+
from .libvirt import Libvirt as _Libvirt
99
from box import Box
1010

1111
class Virtualbox(_Provider):

netsim/templates/provider/libvirt/define-domain.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
{% if 'uuid' in n.libvirt %}
4040
domain.uuid = '{{ n.libvirt.uuid }}'
4141
{% endif %}
42+
{% include 'forwarded-ports.j2' %}
4243
{% endif %}
4344
end
4445

@@ -50,5 +51,4 @@
5051
{% endif %}
5152

5253
{% endfor %}
53-
{% include 'forwarded-ports.j2' %}
5454
end

netsim/templates/provider/libvirt/forwarded-ports.j2

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22
Forwarded Libvirt Ports
33
#}
44

5-
{% if defaults.providers.libvirt.forwarded is defined %}
6-
{{ name }}.vm.provider :libvirt do |libvirt|
7-
libvirt.forward_ssh_port = true
8-
end
9-
{% for port,base in defaults.providers.libvirt.forwarded.items() %}
5+
{% if n.libvirt.ports is defined %}
6+
{% for p_fwd in n.libvirt.ports %}
7+
{% set p_list = p_fwd.split(':') %}
108
{{ name }}.vm.network "forwarded_port",
11-
guest: {{ defaults.ports[port] }}, host: {{ base + n.id }}, id: "{{ port }}"
9+
guest: {{ p_list[1] }}, host: {{ p_list[0] }}, host_ip: "0.0.0.0", gateway_ports: true
1210
{% endfor %}
1311
{% endif %}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
status=0
3+
for port in 2001 2002; do
4+
echo "Checking SSH port forwarding on port $port"
5+
if ! (sshpass -p admin \
6+
ssh -p $port \
7+
-o UserKnownHostsFile=/dev/null \
8+
-o StrictHostKeyChecking=no \
9+
-o LogLevel=ERROR \
10+
admin@127.0.0.1 show ver | grep EOS >/dev/null && echo "Forwarding on port $port works"); then
11+
echo "Forwarding on port $port failed"
12+
status=1
13+
fi
14+
done
15+
16+
echo "Checking HTTP port forwarding on 8080"
17+
if ! (curl -s http://localhost:8080 >/dev/null && echo "HTTP port forwarding works"); then
18+
echo "HTTP forwarding to R1 failed"
19+
status=1
20+
fi
21+
22+
exit $status
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
message: |
2+
This lab sets up forwarded SSH and HTTP ports for two EOS devices. It
3+
tests the container port forwarding functionality
4+
5+
defaults.device: eos
6+
provider: clab
7+
plugin: [ files ]
8+
9+
defaults.providers.clab.forwarded:
10+
ssh: 2000
11+
12+
nodes:
13+
r1:
14+
clab.ports:
15+
- 8080:80
16+
config.inline: |
17+
management http-server
18+
protocol http
19+
r2:
20+
21+
links: [ r1-r2 ]

0 commit comments

Comments
 (0)