mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-01 21:33:02 +02:00
cli: add container support to show interfaces
Make show interfaces container aware. This is done by looking though all podman containers for interfaces and looking up there info from network namespaces on the main system / hypervisor. Interfaces controlled by containers are clearly marked in the "show interfaces" output, with a single gray row. Telling the user that they belong to one or more named containers. The user can then run "show interface name NAME" on interfaces owned by containers, which provided some additional info, such as mac address and stat counters. The patch also add support for printing veth peers which are owned by containers. Lastly, the patch also adds test cases for this functionality. Signed-off-by: Richard Alpe <richard@bit42.se>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,8 +698,14 @@ 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:
|
||||
iface_out['admin-status'] = "up" if "UP" in iface_in['flags'] else "down"
|
||||
@@ -885,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:
|
||||
@@ -962,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
|
||||
|
||||
Reference in New Issue
Block a user