Merge pull request #783 from rical/cli-add-show-iface-cont-sup

This commit is contained in:
Joachim Wiberg
2024-10-31 20:22:04 +01:00
committed by GitHub
14 changed files with 763 additions and 14 deletions
+9 -1
View File
@@ -23,6 +23,10 @@ submodule infix-if-container {
Ensures a container interface can never be a bridge port, or
LAG member, at the same time.";
revision 2024-10-29 {
description "Add read only container list to container-network";
reference "internal";
}
revision 2024-01-15 {
description "Initial revision.";
reference "internal";
@@ -64,7 +68,11 @@ submodule infix-if-container {
base container-network;
}
}
leaf-list containers {
type string;
config false;
description "List of containers using this interface";
}
list subnet {
description "Static IP ranges to hand out addresses to containers from.
+1 -1
View File
@@ -361,7 +361,7 @@
<ACTION sym="script">
if [ -n "$KLISH_PARAM_name" ]; then
sysrepocfg -f json -X -d operational -x \
"/ietf-interfaces:interfaces/interface[name='$KLISH_PARAM_name']" | \
"/ietf-interfaces:interfaces/interface[name=\"$KLISH_PARAM_name\"]" | \
/usr/libexec/statd/cli-pretty "show-interfaces" -n "$KLISH_PARAM_name"
else
sysrepocfg -f json -X -d operational -m ietf-interfaces | \
+24
View File
@@ -80,6 +80,10 @@ class Decore():
def underline(txt):
return Decore.decorate("4", txt, "24")
@staticmethod
def gray_bg(txt):
return Decore.decorate("100", txt)
def datetime_now():
if UNIT_TEST:
return datetime(2023, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
@@ -273,6 +277,7 @@ class Iface:
self.bridge = get_json_data('', self.data, 'infix-interfaces:bridge-port', 'bridge')
self.pvid = get_json_data('', self.data, 'infix-interfaces:bridge-port', 'pvid')
self.stp_state = get_json_data('', self.data, 'infix-interfaces:bridge-port', 'stp-state')
self.containers = get_json_data('', self.data, 'infix-interfaces:container-network', 'containers')
if data.get('statistics'):
self.in_octets = data.get('statistics').get('in-octets', '')
@@ -302,6 +307,10 @@ class Iface:
def is_vlan(self):
return self.type == "infix-if-type:vlan"
def is_in_container(self):
# Return negative if cointainer isn't set or is an empty list
return getattr(self, 'containers', None)
def is_bridge(self):
return self.type == "infix-if-type:bridge"
@@ -436,7 +445,18 @@ class Iface:
parent.pr_name(pipe='')
parent.pr_proto_eth()
def pr_container(self):
row = f"{self.name:<{Pad.iface}}"
row += f"{'container':<{Pad.proto}}"
row += f"{'':<{Pad.state}}"
row += f"{', ' . join(self.containers):<{Pad.data}}"
print(Decore.gray_bg(row))
def pr_iface(self):
if self.is_in_container():
print(Decore.gray_bg(f"{'owned by container':<{20}}: {', ' . join(self.containers)}"))
print(f"{'name':<{20}}: {self.name}")
print(f"{'index':<{20}}: {self.index}")
if self.mtu:
@@ -564,6 +584,10 @@ def pr_interface_list(json):
if iface.name == "lo":
continue
if iface.is_in_container():
iface.pr_container()
continue
if iface.is_bridge():
iface.pr_bridge(ifaces)
continue
+81 -12
View File
@@ -629,18 +629,52 @@ def get_brport_multicast(ifname):
def get_ip_link():
"""Fetch interface link information from kernel"""
return run_json_cmd(['ip', '-s', '-d', '-j', 'link', 'show'],
f"ip-link-show.json")
"ip-link-show.json")
def netns_get_ip_link(netns):
"""Fetch interface link information from within a network namespace"""
return run_json_cmd(['ip', 'netns', 'exec', netns, 'ip', '-s', '-d', '-j', 'link', 'show'],
f"netns-{netns}-ip-link-show.json")
def get_ip_addr():
"""Fetch interface address information from kernel"""
return run_json_cmd(['ip', '-j', 'addr', 'show'],
f"ip-addr-show.json")
"ip-addr-show.json")
def netns_get_ip_addr(netns):
"""Fetch interface address information from within a network namespace"""
return run_json_cmd(['ip', 'netns', 'exec', netns, 'ip', '-j', 'addr', 'show'],
f"netns-{netns}-ip-addr-show.json")
def get_netns_list():
"""Fetch a list of network namespaces"""
return run_json_cmd(['ip', '-j', 'netns', 'list'],
"netns-list.json")
def netns_find_ifname(ifname):
"""Find which network namespace owns ifname (if any)"""
for netns in get_netns_list():
for iface in netns_get_ip_link(netns['name']):
if 'ifalias' in iface and iface['ifalias'] == ifname:
return netns['name']
return None
def netns_ifindex_to_ifname(ifindex):
"""Look through all network namespaces for an interface index and return its name"""
for netns in get_netns_list():
for iface in netns_get_ip_link(netns['name']):
if iface['ifindex'] == ifindex:
if 'ifalias' in iface:
return iface['ifalias']
if 'ifname' in iface:
return iface['ifname']
return None
return None
def add_ip_link(ifname, iface_in, iface_out):
if 'ifname' in iface_in:
iface_out['name'] = iface_in['ifname']
iface_out['name'] = ifname
if 'ifindex' in iface_in:
iface_out['if-index'] = iface_in['ifindex']
@@ -664,17 +698,17 @@ def add_ip_link(ifname, iface_in, iface_out):
multicast = get_brport_multicast(ifname)
insert(iface_out, "infix-interfaces:bridge-port", "multicast", multicast)
if 'link' in iface_in and not iface_is_dsa(iface_in):
insert(iface_out, "infix-interfaces:vlan", "lower-layer-if", iface_in['link'])
if not iface_is_dsa(iface_in):
if 'link' in iface_in:
insert(iface_out, "infix-interfaces:vlan", "lower-layer-if", iface_in['link'])
elif 'link_index' in iface_in:
# 'link_index' is the only reference we have if the link iface is in a namespace
lower = netns_ifindex_to_ifname(iface_in['link_index'])
if lower:
insert(iface_out, "infix-interfaces:vlan", "lower-layer-if", lower)
if 'flags' in iface_in:
admin_xlate = {
"UP": "up",
"DOWN": "down"
}
admin_status = admin_xlate.get("UP" if "UP" in iface_in['flags'] else "DOWN", "testing")
iface_out['admin-status'] = admin_status
iface_out['admin-status'] = "up" if "UP" in iface_in['flags'] else "down"
if 'operstate' in iface_in:
xlate = {
@@ -891,6 +925,39 @@ def add_mdb_to_bridge(brname, iface_out, mc_status):
insert(iface_out, "infix-interfaces:bridge", "multicast", multicast)
insert(iface_out, "infix-interfaces:bridge", "multicast-filters", "multicast-filter", multicast_filters)
def add_container_ifaces(yang_ifaces):
"""Add all podman interfaces with limited data"""
interfaces={}
try:
containers = run_json_cmd(['podman', 'ps', '--format', 'json'], "podman-ps.json", default=[])
except Exception as e:
logging.error(f"Error, unable to run podman: {e}")
return
for container in containers:
name = container.get('Names', ['Unknown'])[0]
networks = container.get('Networks', [])
for network in networks:
if not network in interfaces:
interfaces[network] = []
if name not in interfaces[network]:
interfaces[network].append(name)
for ifname, containers in interfaces.items():
iface_out = {}
iface_out['name'] = ifname
iface_out['type'] = "infix-if-type:other" # Fallback
insert(iface_out, "infix-interfaces:container-network", "containers", containers)
netns = netns_find_ifname(ifname)
if netns is not None:
ip_link_data = netns_get_ip_link(netns)
ip_link_data = next((d for d in ip_link_data if d.get('ifalias') == ifname), None)
add_ip_link(ifname, ip_link_data, iface_out)
yang_ifaces.append(iface_out)
# Helper function to add tagged/untagged interfaces to a vlan dict in a list
def _add_vlan_iface(vlans, multicast_filter, multicast, vid, key, val):
for d in vlans:
@@ -968,6 +1035,8 @@ def add_interface(ifname, yang_ifaces):
addr = next((d for d in ip_addr_data if d.get('ifname') == link["ifname"]), None)
_add_interface(link["ifname"], link, addr, yang_ifaces)
add_container_ifaces(yang_ifaces)
def main():
global TESTPATH
global logger
@@ -6,5 +6,11 @@ br0 bridge  vlan:40u,50t
br1 bridge  
│ ethernet UP 02:00:00:00:00:02
└ e2 bridge FORWARDING vlan:30u pvid:30
e2 container system 
e3 ethernet UP 02:00:00:00:00:03
e4 ethernet DOWN 02:00:00:00:00:04
veth0b container system 
veth0j ethernet UP b2:82:e3:ce:d5:9e
veth peer:veth0k
ipv4 192.168.1.1/24 (static)
veth0k container system2 
@@ -625,5 +625,37 @@
"collisions": 0
}
}
},
{
"ifindex": 9,
"link_index": 8,
"ifname": "veth0j",
"flags": [
"BROADCAST",
"MULTICAST",
"UP",
"LOWER_UP"
],
"mtu": 1500,
"qdisc": "noqueue",
"operstate": "UP",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "b2:82:e3:ce:d5:9e",
"broadcast": "ff:ff:ff:ff:ff:ff",
"link_netnsid": 1,
"addr_info": [
{
"family": "inet",
"local": "192.168.1.1",
"prefixlen": 24,
"scope": "global",
"protocol": "static",
"label": "veth0j",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
}
]
}
]
@@ -618,5 +618,61 @@
"collisions": 0
}
}
},
{
"ifindex": 9,
"link_index": 8,
"ifname": "veth0j",
"flags": [
"BROADCAST",
"MULTICAST",
"UP",
"LOWER_UP"
],
"mtu": 1500,
"qdisc": "noqueue",
"operstate": "UP",
"linkmode": "DEFAULT",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "b2:82:e3:ce:d5:9e",
"broadcast": "ff:ff:ff:ff:ff:ff",
"link_netnsid": 1,
"promiscuity": 0,
"allmulti": 0,
"min_mtu": 68,
"max_mtu": 65535,
"linkinfo": {
"info_kind": "veth"
},
"inet6_addr_gen_mode": "none",
"num_tx_queues": 1,
"num_rx_queues": 1,
"gso_max_size": 65536,
"gso_max_segs": 65535,
"tso_max_size": 524280,
"tso_max_segs": 65535,
"gro_max_size": 65536,
"gso_ipv4_max_size": 65536,
"gro_ipv4_max_size": 65536,
"stats64": {
"rx": {
"bytes": 1006,
"packets": 13,
"errors": 0,
"dropped": 0,
"over_errors": 0,
"multicast": 0
},
"tx": {
"bytes": 18668,
"packets": 50,
"errors": 0,
"dropped": 0,
"carrier_errors": 0,
"collisions": 0
}
}
}
]
@@ -0,0 +1,119 @@
[
{
"ifindex": 1,
"ifname": "lo",
"flags": [
"LOOPBACK",
"UP",
"LOWER_UP"
],
"mtu": 65536,
"qdisc": "noqueue",
"operstate": "UNKNOWN",
"group": "default",
"txqlen": 1000,
"link_type": "loopback",
"address": "00:00:00:00:00:00",
"broadcast": "00:00:00:00:00:00",
"addr_info": [
{
"family": "inet",
"local": "127.0.0.1",
"prefixlen": 8,
"scope": "host",
"label": "lo",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
},
{
"family": "inet6",
"local": "::1",
"prefixlen": 128,
"scope": "host",
"protocol": "kernel_lo",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
}
]
},
{
"ifindex": 3,
"ifname": "eth0",
"flags": [
"BROADCAST",
"MULTICAST",
"UP",
"LOWER_UP"
],
"mtu": 1500,
"qdisc": "pfifo_fast",
"operstate": "UP",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "02:00:00:00:00:02",
"broadcast": "ff:ff:ff:ff:ff:ff",
"addr_info": [
{
"family": "inet",
"local": "172.0.0.1",
"prefixlen": 24,
"broadcast": "172.0.0.255",
"scope": "global",
"label": "eth0",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
},
{
"family": "inet6",
"local": "fe80::ff:fe00:2",
"prefixlen": 64,
"scope": "link",
"protocol": "kernel_ll",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
}
]
},
{
"ifindex": 6,
"link_index": 7,
"ifname": "eth1",
"flags": [
"BROADCAST",
"MULTICAST",
"UP",
"LOWER_UP"
],
"mtu": 1500,
"qdisc": "noqueue",
"operstate": "UP",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "62:64:85:4c:7c:87",
"broadcast": "ff:ff:ff:ff:ff:ff",
"link_netnsid": 0,
"addr_info": [
{
"family": "inet",
"local": "192.168.0.2",
"prefixlen": 24,
"broadcast": "192.168.0.255",
"scope": "global",
"label": "eth1",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
},
{
"family": "inet6",
"local": "fe80::6064:85ff:fe4c:7c87",
"prefixlen": 64,
"scope": "link",
"protocol": "kernel_ll",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
}
]
}
]
@@ -0,0 +1,163 @@
[
{
"ifindex": 1,
"ifname": "lo",
"flags": [
"LOOPBACK",
"UP",
"LOWER_UP"
],
"mtu": 65536,
"qdisc": "noqueue",
"operstate": "UNKNOWN",
"linkmode": "DEFAULT",
"group": "default",
"txqlen": 1000,
"link_type": "loopback",
"address": "00:00:00:00:00:00",
"broadcast": "00:00:00:00:00:00",
"promiscuity": 0,
"allmulti": 0,
"min_mtu": 0,
"max_mtu": 0,
"inet6_addr_gen_mode": "eui64",
"num_tx_queues": 1,
"num_rx_queues": 1,
"gso_max_size": 65536,
"gso_max_segs": 65535,
"tso_max_size": 524280,
"tso_max_segs": 65535,
"gro_max_size": 65536,
"gso_ipv4_max_size": 65536,
"gro_ipv4_max_size": 65536,
"stats64": {
"rx": {
"bytes": 0,
"packets": 0,
"errors": 0,
"dropped": 0,
"over_errors": 0,
"multicast": 0
},
"tx": {
"bytes": 0,
"packets": 0,
"errors": 0,
"dropped": 0,
"carrier_errors": 0,
"collisions": 0
}
}
},
{
"ifindex": 3,
"ifname": "eth0",
"flags": [
"BROADCAST",
"MULTICAST",
"UP",
"LOWER_UP"
],
"mtu": 1500,
"qdisc": "pfifo_fast",
"operstate": "UP",
"linkmode": "DEFAULT",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "02:00:00:00:00:02",
"broadcast": "ff:ff:ff:ff:ff:ff",
"promiscuity": 0,
"allmulti": 0,
"min_mtu": 68,
"max_mtu": 65535,
"inet6_addr_gen_mode": "eui64",
"num_tx_queues": 1,
"num_rx_queues": 1,
"gso_max_size": 65536,
"gso_max_segs": 65535,
"tso_max_size": 65536,
"tso_max_segs": 65535,
"gro_max_size": 65536,
"gso_ipv4_max_size": 65536,
"gro_ipv4_max_size": 65536,
"parentbus": "virtio",
"parentdev": "virtio3",
"ifalias": "e2",
"stats64": {
"rx": {
"bytes": 0,
"packets": 0,
"errors": 0,
"dropped": 0,
"over_errors": 0,
"multicast": 0
},
"tx": {
"bytes": 1006,
"packets": 13,
"errors": 0,
"dropped": 0,
"carrier_errors": 0,
"collisions": 0
}
}
},
{
"ifindex": 6,
"link_index": 7,
"ifname": "eth1",
"flags": [
"BROADCAST",
"MULTICAST",
"UP",
"LOWER_UP"
],
"mtu": 1500,
"qdisc": "noqueue",
"operstate": "UP",
"linkmode": "DEFAULT",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "62:64:85:4c:7c:87",
"broadcast": "ff:ff:ff:ff:ff:ff",
"link_netnsid": 0,
"promiscuity": 0,
"allmulti": 0,
"min_mtu": 68,
"max_mtu": 65535,
"linkinfo": {
"info_kind": "veth"
},
"inet6_addr_gen_mode": "eui64",
"num_tx_queues": 1,
"num_rx_queues": 1,
"gso_max_size": 65536,
"gso_max_segs": 65535,
"tso_max_size": 524280,
"tso_max_segs": 65535,
"gro_max_size": 65536,
"gso_ipv4_max_size": 65536,
"gro_ipv4_max_size": 65536,
"ifalias": "veth0b",
"stats64": {
"rx": {
"bytes": 17643,
"packets": 44,
"errors": 0,
"dropped": 24,
"over_errors": 0,
"multicast": 0
},
"tx": {
"bytes": 1006,
"packets": 13,
"errors": 0,
"dropped": 0,
"carrier_errors": 0,
"collisions": 0
}
}
}
]
@@ -0,0 +1,80 @@
[
{
"ifindex": 1,
"ifname": "lo",
"flags": [
"LOOPBACK",
"UP",
"LOWER_UP"
],
"mtu": 65536,
"qdisc": "noqueue",
"operstate": "UNKNOWN",
"group": "default",
"txqlen": 1000,
"link_type": "loopback",
"address": "00:00:00:00:00:00",
"broadcast": "00:00:00:00:00:00",
"addr_info": [
{
"family": "inet",
"local": "127.0.0.1",
"prefixlen": 8,
"scope": "host",
"label": "lo",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
},
{
"family": "inet6",
"local": "::1",
"prefixlen": 128,
"scope": "host",
"protocol": "kernel_lo",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
}
]
},
{
"ifindex": 8,
"link_index": 9,
"ifname": "eth0",
"flags": [
"BROADCAST",
"MULTICAST",
"UP",
"LOWER_UP"
],
"mtu": 1500,
"qdisc": "noqueue",
"operstate": "UP",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "c2:1b:78:88:e2:b8",
"broadcast": "ff:ff:ff:ff:ff:ff",
"link_netnsid": 0,
"addr_info": [
{
"family": "inet",
"local": "192.168.1.2",
"prefixlen": 24,
"broadcast": "192.168.1.255",
"scope": "global",
"label": "eth0",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
},
{
"family": "inet6",
"local": "fe80::c01b:78ff:fe88:e2b8",
"prefixlen": 64,
"scope": "link",
"protocol": "kernel_ll",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
}
]
}
]
@@ -0,0 +1,109 @@
[
{
"ifindex": 1,
"ifname": "lo",
"flags": [
"LOOPBACK",
"UP",
"LOWER_UP"
],
"mtu": 65536,
"qdisc": "noqueue",
"operstate": "UNKNOWN",
"linkmode": "DEFAULT",
"group": "default",
"txqlen": 1000,
"link_type": "loopback",
"address": "00:00:00:00:00:00",
"broadcast": "00:00:00:00:00:00",
"promiscuity": 0,
"allmulti": 0,
"min_mtu": 0,
"max_mtu": 0,
"inet6_addr_gen_mode": "eui64",
"num_tx_queues": 1,
"num_rx_queues": 1,
"gso_max_size": 65536,
"gso_max_segs": 65535,
"tso_max_size": 524280,
"tso_max_segs": 65535,
"gro_max_size": 65536,
"gso_ipv4_max_size": 65536,
"gro_ipv4_max_size": 65536,
"stats64": {
"rx": {
"bytes": 0,
"packets": 0,
"errors": 0,
"dropped": 0,
"over_errors": 0,
"multicast": 0
},
"tx": {
"bytes": 0,
"packets": 0,
"errors": 0,
"dropped": 0,
"carrier_errors": 0,
"collisions": 0
}
}
},
{
"ifindex": 8,
"link_index": 9,
"ifname": "eth0",
"flags": [
"BROADCAST",
"MULTICAST",
"UP",
"LOWER_UP"
],
"mtu": 1500,
"qdisc": "noqueue",
"operstate": "UP",
"linkmode": "DEFAULT",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "c2:1b:78:88:e2:b8",
"broadcast": "ff:ff:ff:ff:ff:ff",
"link_netnsid": 0,
"promiscuity": 0,
"allmulti": 0,
"min_mtu": 68,
"max_mtu": 65535,
"linkinfo": {
"info_kind": "veth"
},
"inet6_addr_gen_mode": "eui64",
"num_tx_queues": 1,
"num_rx_queues": 1,
"gso_max_size": 65536,
"gso_max_segs": 65535,
"tso_max_size": 524280,
"tso_max_segs": 65535,
"gro_max_size": 65536,
"gso_ipv4_max_size": 65536,
"gro_ipv4_max_size": 65536,
"ifalias": "veth0k",
"stats64": {
"rx": {
"bytes": 15197,
"packets": 37,
"errors": 0,
"dropped": 22,
"over_errors": 0,
"multicast": 0
},
"tx": {
"bytes": 1006,
"packets": 13,
"errors": 0,
"dropped": 0,
"carrier_errors": 0,
"collisions": 0
}
}
}
]
@@ -0,0 +1,10 @@
[
{
"name": "57ff63cb",
"id": 0
},
{
"name": "db5ad90e",
"id": 1
}
]
@@ -0,0 +1,73 @@
[
{
"AutoRemove": false,
"Command": null,
"CreatedAt": "25 minutes ago",
"Exited": false,
"ExitedAt": 1730216567,
"ExitCode": 0,
"Id": "cf054e1bc3ff7d7d6fec0cc9a0435f0c96f166e54bc9e9d92149ab53ffa60321",
"Image": "ghcr.io/kernelkit/curios:edge",
"ImageID": "cee712df3c4ea28bd3f03ca50d46e53788b1526812f4dac8d9133870eb5c49c0",
"IsInfra": false,
"Labels": {
"org.opencontainers.image.title": "curiOS",
"org.opencontainers.image.url": "https://github.com/kernelkit/curiOS"
},
"Mounts": [],
"Names": [
"system2"
],
"Namespaces": {
},
"Networks": [
"veth0k"
],
"Pid": 3934,
"Pod": "",
"PodName": "",
"Ports": null,
"Size": null,
"StartedAt": 1730216629,
"State": "running",
"Status": "Up 3 minutes",
"Created": 1730215318
},
{
"AutoRemove": false,
"Command": null,
"CreatedAt": "22 minutes ago",
"Exited": false,
"ExitedAt": 1730216567,
"ExitCode": 0,
"Id": "400172de2290a387bd10de7c2995a0876169a4b69679b862b474c645308dcea1",
"Image": "ghcr.io/kernelkit/curios:edge",
"ImageID": "cee712df3c4ea28bd3f03ca50d46e53788b1526812f4dac8d9133870eb5c49c0",
"IsInfra": false,
"Labels": {
"org.opencontainers.image.title": "curiOS",
"org.opencontainers.image.url": "https://github.com/kernelkit/curiOS"
},
"Mounts": [],
"Names": [
"system"
],
"Namespaces": {
},
"Networks": [
"e2",
"veth0b"
],
"Pid": 3933,
"Pod": "",
"PodName": "",
"Ports": null,
"Size": null,
"StartedAt": 1730216629,
"State": "running",
"Status": "Up 3 minutes",
"Created": 1730215507
}
]