mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-31 13:03:02 +02:00
Add a new packet python-statd
Actually a part of statd, but had to make a separarate package to get it to work. Statd helper-scripts are now pre-compiled instead of doing it in runtime. Fixes #379
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2023 The KernelKit Authors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of copyright holders nor the names of
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,4 @@
|
||||
from .cli_pretty import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+624
@@ -0,0 +1,624 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import argparse
|
||||
import sys
|
||||
import re
|
||||
|
||||
class Pad:
|
||||
iface = 16
|
||||
proto = 11
|
||||
state = 12
|
||||
data = 41
|
||||
|
||||
class PadMdb:
|
||||
bridge = 7
|
||||
vlan = 6
|
||||
group = 20
|
||||
ports = 20
|
||||
|
||||
class PadRoute:
|
||||
prefix = 30
|
||||
protocol = 10
|
||||
next_hop = 30
|
||||
pref = 8
|
||||
|
||||
class PadSoftware:
|
||||
name = 10
|
||||
date = 25
|
||||
hash = 64
|
||||
state = 10
|
||||
version = 23
|
||||
|
||||
|
||||
class PadUsbPort:
|
||||
title = 30
|
||||
name = 20
|
||||
state = 10
|
||||
|
||||
class Decore():
|
||||
@staticmethod
|
||||
def decorate(sgr, txt, restore="0"):
|
||||
return f"\033[{sgr}m{txt}\033[{restore}m"
|
||||
|
||||
@staticmethod
|
||||
def invert(txt):
|
||||
return Decore.decorate("7", txt)
|
||||
|
||||
@staticmethod
|
||||
def red(txt):
|
||||
return Decore.decorate("31", txt, "39")
|
||||
|
||||
@staticmethod
|
||||
def green(txt):
|
||||
return Decore.decorate("32", txt, "39")
|
||||
|
||||
@staticmethod
|
||||
def yellow(txt):
|
||||
return Decore.decorate("33", txt, "39")
|
||||
|
||||
@staticmethod
|
||||
def underline(txt):
|
||||
return Decore.decorate("4", txt, "24")
|
||||
|
||||
def get_json_data(default, indata, *args):
|
||||
data = indata
|
||||
for arg in args:
|
||||
if arg in data:
|
||||
data = data.get(arg)
|
||||
else:
|
||||
return default
|
||||
|
||||
return data
|
||||
|
||||
def remove_yang_prefix(key):
|
||||
parts = key.split(":", 1)
|
||||
if len(parts) > 1:
|
||||
return parts[1]
|
||||
return key
|
||||
|
||||
class Route:
|
||||
def __init__(self,data,ip):
|
||||
self.data = data
|
||||
self.prefix = data.get(f'ietf-{ip}-unicast-routing:destination-prefix', '')
|
||||
self.protocol = data.get('source-protocol','')[14:]
|
||||
self.pref = data.get('route-preference','')
|
||||
self.next_hop = []
|
||||
next_hop_list=get_json_data(None, self.data, 'next-hop', 'next-hop-list')
|
||||
if next_hop_list:
|
||||
for nh in next_hop_list["next-hop"]:
|
||||
if(nh.get(f"ietf-{ip}-unicast-routing:address")):
|
||||
self.next_hop.append(nh[f"ietf-{ip}-unicast-routing:address"])
|
||||
elif(nh.get("outgoing-interface")):
|
||||
self.next_hop.append(nh["outgoing-interface"])
|
||||
else:
|
||||
self.next_hop.append("unspecified")
|
||||
else:
|
||||
interface = get_json_data(None, self.data, 'next-hop', 'outgoing-interface')
|
||||
address = get_json_data(None, self.data, 'next-hop', f'ietf-{ip}-unicast-routing:next-hop-address')
|
||||
special = get_json_data(None, self.data, 'next-hop', 'special-next-hop')
|
||||
|
||||
if address:
|
||||
self.next_hop.append(address)
|
||||
elif interface:
|
||||
self.next_hop.append(interface)
|
||||
elif special:
|
||||
self.next_hop.append(special)
|
||||
else:
|
||||
self.next_hop.append("unspecified")
|
||||
def print(self):
|
||||
row = f"{self.prefix:<{PadRoute.prefix}}"
|
||||
row += f"{self.next_hop[0]:<{PadRoute.next_hop}}"
|
||||
row += f"{self.pref:>{PadRoute.pref}} "
|
||||
row += f"{self.protocol:<{PadRoute.protocol}}"
|
||||
print(row)
|
||||
for nh in self.next_hop[1:]:
|
||||
row = f"{'':<{PadRoute.prefix}}"
|
||||
row += f"{nh:<{PadRoute.next_hop}}"
|
||||
print(row)
|
||||
|
||||
class Software:
|
||||
"""Software bundle class """
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.name = data.get('bootname', '')
|
||||
self.size = data.get('size', '')
|
||||
self.type = data.get('class', '')
|
||||
self.hash = data.get('sha256', '')
|
||||
self.state = data.get('state', '')
|
||||
self.version = get_json_data('', self.data, 'bundle', 'version')
|
||||
self.date = get_json_data('', self.data, 'installed', 'datetime')
|
||||
|
||||
def is_rootfs(self):
|
||||
"""True if bundle type is 'rootfs'"""
|
||||
return self.type == "rootfs"
|
||||
|
||||
def print(self):
|
||||
"""Brief information about one bundle"""
|
||||
row = f"{self.name:<{PadSoftware.name}}"
|
||||
row += f"{self.state:<{PadSoftware.state}}"
|
||||
row += f"{self.version:<{PadSoftware.version}}"
|
||||
row += f"{self.date:<{PadSoftware.date}}"
|
||||
print(row)
|
||||
|
||||
def detail(self):
|
||||
"""Detailed information about one bundle"""
|
||||
print(f"Name : {self.name}")
|
||||
print(f"State : {self.state}")
|
||||
print(f"Version : {self.version}")
|
||||
print(f"Size : {self.size}")
|
||||
print(f"SHA-256 : {self.hash}")
|
||||
print(f"Installed : {self.date}")
|
||||
|
||||
class USBport:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.name = data.get('name', '')
|
||||
self.state = get_json_data('', self.data, 'state', 'admin-state')
|
||||
|
||||
def print(self):
|
||||
#print(self.name)
|
||||
row = f"{self.name:<{PadUsbPort.name}}"
|
||||
row += f"{self.state:<{PadUsbPort.state}}"
|
||||
print(row)
|
||||
|
||||
class Iface:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.name = data.get('name', '')
|
||||
self.type = data.get('type', '')
|
||||
self.index = data.get('if-index', '')
|
||||
self.oper_status = data.get('oper-status', '')
|
||||
self.autoneg = get_json_data('unknown', self.data, 'ieee802-ethernet-interface:ethernet',
|
||||
'auto-negotiation', 'enable')
|
||||
self.duplex = get_json_data('', self.data,'ieee802-ethernet-interface:ethernet','duplex')
|
||||
self.speed = get_json_data('', self.data, 'ieee802-ethernet-interface:ethernet', 'speed')
|
||||
self.phys_address = data.get('phys-address', '')
|
||||
|
||||
self.br_mdb = get_json_data({}, self.data, 'infix-interfaces:bridge', 'multicast-filters')
|
||||
self.br_vlans = get_json_data({}, self.data, 'infix-interfaces:bridge', 'vlans', "vlan")
|
||||
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')
|
||||
|
||||
if data.get('statistics'):
|
||||
self.in_octets = data.get('statistics').get('in-octets', '')
|
||||
self.out_octets = data.get('statistics').get('out-octets', '')
|
||||
else:
|
||||
self.in_octets = ''
|
||||
self.out_octets = ''
|
||||
|
||||
if self.data.get('ietf-ip:ipv4'):
|
||||
self.mtu = self.data.get('ietf-ip:ipv4').get('mtu', '')
|
||||
self.ipv4_addr = self.data.get('ietf-ip:ipv4').get('address', '')
|
||||
else:
|
||||
self.mtu = ''
|
||||
self.ipv4_addr = []
|
||||
|
||||
if self.data.get('ietf-ip:ipv6'):
|
||||
self.ipv6_addr = self.data.get('ietf-ip:ipv6').get('address', '')
|
||||
else:
|
||||
self.ipv6_addr = []
|
||||
|
||||
|
||||
if self.data.get('infix-interfaces:vlan'):
|
||||
self.lower_if = self.data.get('infix-interfaces:vlan', None).get('lower-layer-if',None)
|
||||
else:
|
||||
self.lower_if = ''
|
||||
|
||||
def is_vlan(self):
|
||||
return self.type == "infix-if-type:vlan"
|
||||
|
||||
def is_bridge(self):
|
||||
return self.type == "infix-if-type:bridge"
|
||||
|
||||
def is_veth(self):
|
||||
return self.data['type'] == "infix-if-type:veth"
|
||||
|
||||
def oper(self, detail=False):
|
||||
"""Remap in brief overview to fit column widths."""
|
||||
if not detail and self.oper_status == "lower-layer-down":
|
||||
return "lower-down"
|
||||
return self.oper_status
|
||||
|
||||
def pr_name(self, pipe=""):
|
||||
print(f"{pipe}{self.name:<{Pad.iface - len(pipe)}}", end="")
|
||||
|
||||
def pr_proto_ipv4(self, pipe=''):
|
||||
for addr in self.ipv4_addr:
|
||||
origin = f"({addr['origin']})" if addr.get('origin') else ""
|
||||
|
||||
row = f"{pipe:<{Pad.iface}}"
|
||||
row += f"{'ipv4':<{Pad.proto}}"
|
||||
row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']} {origin}"
|
||||
print(row)
|
||||
|
||||
def pr_proto_ipv6(self, pipe=''):
|
||||
for addr in self.ipv6_addr:
|
||||
origin = f"({addr['origin']})" if addr.get('origin') else ""
|
||||
|
||||
row = f"{pipe:<{Pad.iface}}"
|
||||
row += f"{'ipv6':<{Pad.proto}}"
|
||||
row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']} {origin}"
|
||||
print(row)
|
||||
|
||||
def pr_proto_eth(self, pipe=''):
|
||||
row = ""
|
||||
if len(pipe) > 0:
|
||||
row = f"{pipe:<{Pad.iface}}"
|
||||
|
||||
row += f"{'ethernet':<{Pad.proto}}"
|
||||
dec = Decore.green if self.oper() == "up" else Decore.red
|
||||
row += dec(f"{self.oper().upper():<{Pad.state}}")
|
||||
row += f"{self.phys_address:<{Pad.data}}"
|
||||
print(row)
|
||||
|
||||
def pr_proto_br(self, br_vlans):
|
||||
data_str = ""
|
||||
|
||||
row = f"{'bridge':<{Pad.proto}}"
|
||||
|
||||
if self.oper() == "up":
|
||||
dec = Decore.green if self.stp_state == "forwarding" else Decore.yellow
|
||||
row += dec(f"{self.stp_state.upper():<{Pad.state}}")
|
||||
else:
|
||||
row += Decore.red(f"{self.oper().upper():<{Pad.state}}")
|
||||
|
||||
for vlan in br_vlans:
|
||||
if 'tagged' in vlan:
|
||||
for tagged in vlan['tagged']:
|
||||
if tagged == self.name:
|
||||
if data_str:
|
||||
data_str += f",{vlan['vid']}t"
|
||||
else:
|
||||
data_str += f"vlan:{vlan['vid']}t"
|
||||
if 'untagged' in vlan:
|
||||
for untagged in vlan['untagged']:
|
||||
if untagged == self.name:
|
||||
if data_str:
|
||||
data_str += f",{vlan['vid']}u"
|
||||
else:
|
||||
data_str += f"vlan:{vlan['vid']}u"
|
||||
if self.pvid:
|
||||
data_str += f" pvid:{self.pvid}"
|
||||
|
||||
if data_str:
|
||||
row += f"{data_str:<{Pad.data}}"
|
||||
|
||||
print(row)
|
||||
|
||||
def pr_bridge(self, _ifaces):
|
||||
self.pr_name(pipe="")
|
||||
self.pr_proto_br(self.br_vlans)
|
||||
|
||||
lowers = []
|
||||
for _iface in [Iface(data) for data in _ifaces]:
|
||||
if _iface.bridge and _iface.bridge == self.name:
|
||||
lowers.append(_iface)
|
||||
|
||||
if lowers:
|
||||
self.pr_proto_eth(pipe='│')
|
||||
self.pr_proto_ipv4(pipe='│')
|
||||
self.pr_proto_ipv6(pipe='│')
|
||||
else:
|
||||
self.pr_proto_eth(pipe=' ')
|
||||
self.pr_proto_ipv4()
|
||||
self.pr_proto_ipv6()
|
||||
|
||||
for i, lower in enumerate(lowers):
|
||||
pipe = '└ ' if (i == len(lowers) -1) else '├ '
|
||||
lower.pr_name(pipe)
|
||||
lower.pr_proto_br(self.br_vlans)
|
||||
|
||||
def pr_veth(self, _ifaces):
|
||||
self.pr_name(pipe="")
|
||||
self.pr_proto_eth()
|
||||
|
||||
if self.lower_if:
|
||||
row = f"{'':<{Pad.iface}}"
|
||||
row += f"{'veth':<{Pad.proto}}"
|
||||
row += f"{'':<{Pad.state}}"
|
||||
row += f"peer:{self.lower_if}"
|
||||
print(row)
|
||||
|
||||
self.pr_proto_ipv4()
|
||||
self.pr_proto_ipv6()
|
||||
|
||||
def pr_vlan(self, _ifaces):
|
||||
self.pr_name(pipe="")
|
||||
self.pr_proto_eth()
|
||||
|
||||
if self.lower_if:
|
||||
self.pr_proto_ipv4(pipe='│')
|
||||
self.pr_proto_ipv6(pipe='│')
|
||||
else:
|
||||
self.pr_proto_ipv4()
|
||||
self.pr_proto_ipv6()
|
||||
return
|
||||
|
||||
parent = find_iface(_ifaces, self.lower_if)
|
||||
if not parent:
|
||||
print(f"Error, didn't find parent interface for vlan {self.name}")
|
||||
sys.exit(1)
|
||||
parent.pr_name(pipe='└ ')
|
||||
parent.pr_proto_eth()
|
||||
|
||||
def pr_iface(self):
|
||||
print(f"{'name':<{20}}: {self.name}")
|
||||
print(f"{'index':<{20}}: {self.index}")
|
||||
if self.mtu:
|
||||
print(f"{'mtu':<{20}}: {self.mtu}")
|
||||
if self.oper():
|
||||
print(f"{'operational status':<{20}}: {self.oper(detail=True)}")
|
||||
|
||||
if self.lower_if:
|
||||
print(f"{'lower-layer-if':<{20}}: {self.lower_if}")
|
||||
|
||||
if self.autoneg != 'unknown':
|
||||
val = "on" if self.autoneg else "off"
|
||||
print(f"{'auto-negotiation':<{20}}: {val}")
|
||||
|
||||
if self.duplex:
|
||||
print(f"{'duplex':<{20}}: {self.duplex}")
|
||||
|
||||
if self.speed:
|
||||
mbs = float(self.speed) * 1000
|
||||
print(f"{'speed':<{20}}: {int(mbs)}")
|
||||
|
||||
if self.phys_address:
|
||||
print(f"{'physical address':<{20}}: {self.phys_address}")
|
||||
|
||||
if self.ipv4_addr:
|
||||
first = True
|
||||
for addr in self.ipv4_addr:
|
||||
origin = f"({addr['origin']})" if addr.get('origin') else ""
|
||||
key = 'ipv4 addresses' if first else ''
|
||||
colon = ':' if first else ' '
|
||||
row = f"{key:<{20}}{colon} "
|
||||
row += f"{addr['ip']}/{addr['prefix-length']} {origin}"
|
||||
print(row)
|
||||
first = False
|
||||
else:
|
||||
print(f"{'ipv4 addresses':<{20}}:")
|
||||
|
||||
if self.ipv6_addr:
|
||||
first = True
|
||||
for addr in self.ipv6_addr:
|
||||
origin = f"({addr['origin']})" if addr.get('origin') else ""
|
||||
key = 'ipv6 addresses' if first else ''
|
||||
colon = ':' if first else ' '
|
||||
row = f"{key:<{20}}{colon} "
|
||||
row += f"{addr['ip']}/{addr['prefix-length']} {origin}"
|
||||
print(row)
|
||||
first = False
|
||||
else:
|
||||
print(f"{'ipv6 addresses':<{20}}:")
|
||||
|
||||
if self.in_octets and self.out_octets:
|
||||
print(f"{'in-octets':<{20}}: {self.in_octets}")
|
||||
print(f"{'out-octets':<{20}}: {self.out_octets}")
|
||||
|
||||
frame = get_json_data([], self.data,'ieee802-ethernet-interface:ethernet',
|
||||
'statistics', 'frame')
|
||||
if frame:
|
||||
print("")
|
||||
for key, val in frame.items():
|
||||
key = remove_yang_prefix(key)
|
||||
print(f"eth-{key:<{25}}: {val}")
|
||||
|
||||
def pr_mdb(self, bridge):
|
||||
for group in self.br_mdb.get("multicast-filter", {}):
|
||||
row = f"{bridge:<{PadMdb.bridge}}"
|
||||
row += f"{'':<{PadMdb.vlan}}"
|
||||
row += f"{group['group']:<{PadMdb.group}}"
|
||||
if (group.get("ports")):
|
||||
ports = ", ".join(port_dict["port"] for port_dict in group["ports"])
|
||||
else:
|
||||
ports = ""
|
||||
row += f"{ports}"
|
||||
print(row)
|
||||
|
||||
def pr_vlans_mdb(self, bridge):
|
||||
for vlan in self.br_vlans:
|
||||
filters=vlan.get("multicast-filters", {})
|
||||
for group in filters.get("multicast-filter", []):
|
||||
row = f"{bridge:<{PadMdb.bridge}}"
|
||||
row += f"{vlan['vid']:<{PadMdb.vlan}}"
|
||||
row += f"{group['group']:<{PadMdb.group}}"
|
||||
if (group.get("ports")):
|
||||
ports = ", ".join(port_dict["port"] for port_dict in group["ports"])
|
||||
else:
|
||||
ports = ""
|
||||
row += f"{ports}"
|
||||
print(row)
|
||||
|
||||
def find_iface(_ifaces, name):
|
||||
for _iface in [Iface(data) for data in _ifaces]:
|
||||
if _iface.name == name:
|
||||
return _iface
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def version_sort(iface):
|
||||
return [int(x) if x.isdigit() else x for x in re.split(r'(\d+)', iface['name'])]
|
||||
|
||||
def pr_interface_list(json):
|
||||
hdr = (f"{'INTERFACE':<{Pad.iface}}"
|
||||
f"{'PROTOCOL':<{Pad.proto}}"
|
||||
f"{'STATE':<{Pad.state}}"
|
||||
f"{'DATA':<{Pad.data}}")
|
||||
|
||||
print(Decore.invert(hdr))
|
||||
|
||||
ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"], key=version_sort)
|
||||
|
||||
for iface in [Iface(data) for data in ifaces]:
|
||||
if iface.is_bridge():
|
||||
iface.pr_bridge(ifaces)
|
||||
continue
|
||||
|
||||
if iface.is_veth():
|
||||
iface.pr_veth(ifaces)
|
||||
continue
|
||||
|
||||
if iface.is_vlan():
|
||||
iface.pr_vlan(ifaces)
|
||||
continue
|
||||
|
||||
# These interfaces are printed by there parent, such as bridge
|
||||
if iface.lower_if:
|
||||
continue
|
||||
if iface.bridge:
|
||||
continue
|
||||
|
||||
iface.pr_name()
|
||||
iface.pr_proto_eth()
|
||||
iface.pr_proto_ipv4()
|
||||
iface.pr_proto_ipv6()
|
||||
|
||||
def show_interfaces(json, name):
|
||||
if name:
|
||||
if not json.get("ietf-interfaces:interfaces"):
|
||||
print(f"No interface data found for \"{name}\"")
|
||||
sys.exit(1)
|
||||
iface = find_iface(json["ietf-interfaces:interfaces"]["interface"], name)
|
||||
if not iface:
|
||||
print(f"Interface \"{name}\" not found")
|
||||
sys.exit(1)
|
||||
else:
|
||||
iface.pr_iface()
|
||||
else:
|
||||
if not json.get("ietf-interfaces:interfaces"):
|
||||
print(f"Error, top level \"ietf-interfaces:interfaces\" missing")
|
||||
sys.exit(1)
|
||||
pr_interface_list(json)
|
||||
|
||||
def show_bridge_mdb(json):
|
||||
header_printed = False
|
||||
if not json.get("ietf-interfaces:interfaces"):
|
||||
print(f"Error, top level \"ietf-interfaces:interface\" missing")
|
||||
sys.exit(1)
|
||||
ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"], key=version_sort)
|
||||
for iface in [Iface(data) for data in ifaces]:
|
||||
if iface.type != "infix-if-type:bridge":
|
||||
continue
|
||||
if not header_printed:
|
||||
hdr = (f"{'BRIDGE':<{PadMdb.bridge}}"
|
||||
f"{'VID':<{PadMdb.vlan}}"
|
||||
f"{'GROUP':<{PadMdb.group}}"
|
||||
f"{'PORTS':<{PadMdb.ports}}")
|
||||
print(Decore.invert(hdr))
|
||||
header_printed = True
|
||||
iface.pr_mdb(iface.name)
|
||||
iface.pr_vlans_mdb(iface.name)
|
||||
|
||||
def show_routing_table(json, ip):
|
||||
if not json.get("ietf-routing:routing"):
|
||||
print(f"Error, top level \"ietf-routing:routing\" missing")
|
||||
sys.exit(1)
|
||||
hdr = (f"{'PREFIX':<{PadRoute.prefix}}"
|
||||
f"{'NEXT-HOP':<{PadRoute.next_hop}}"
|
||||
f"{'PREF':>{PadRoute.pref}} "
|
||||
f"{'PROTOCOL':<{PadRoute.protocol}}")
|
||||
|
||||
print(Decore.invert(hdr))
|
||||
for rib in get_json_data({}, json, 'ietf-routing:routing','ribs', 'rib'):
|
||||
if rib["name"] != ip:
|
||||
continue;
|
||||
routes = get_json_data(None, rib, "routes", "route")
|
||||
if routes:
|
||||
for r in routes:
|
||||
route = Route(r, ip)
|
||||
route.print()
|
||||
|
||||
def find_slot(_slots, name):
|
||||
for _slot in [Software(data) for data in _slots]:
|
||||
if _slot.name == name:
|
||||
return _slot
|
||||
|
||||
return False
|
||||
|
||||
def show_software(json, name):
|
||||
if not json.get("ietf-system:system-state", "infix-system:software"):
|
||||
print("Error, cannot find infix-system:software")
|
||||
sys.exit(1)
|
||||
|
||||
slots = get_json_data({}, json, 'ietf-system:system-state', 'infix-system:software', 'slot')
|
||||
if name:
|
||||
slot = find_slot(slots, name)
|
||||
if slot:
|
||||
slot.detail()
|
||||
else:
|
||||
hdr = (f"{'NAME':<{PadSoftware.name}}"
|
||||
f"{'STATE':<{PadSoftware.state}}"
|
||||
f"{'VERSION':<{PadSoftware.version}}"
|
||||
f"{'DATE':<{PadSoftware.date}}")
|
||||
print(Decore.invert(hdr))
|
||||
for _s in slots:
|
||||
slot = Software(_s)
|
||||
if slot.is_rootfs():
|
||||
slot.print()
|
||||
|
||||
def show_hardware(json):
|
||||
if not json.get("ietf-hardware:hardware"):
|
||||
print(f"Error, top level \"ietf-hardware:component\" missing")
|
||||
sys.exit(1)
|
||||
|
||||
hdr = (f"{'USB PORTS':<{PadUsbPort.title}}")
|
||||
print(Decore.invert(hdr))
|
||||
hdr = (f"{'NAME':<{PadUsbPort.name}}"
|
||||
f"{'STATE':<{PadUsbPort.state}}")
|
||||
print(Decore.invert(hdr))
|
||||
|
||||
components = get_json_data({}, json, "ietf-hardware:hardware", "component")
|
||||
|
||||
for component in components:
|
||||
if component.get("class") == "infix-hardware:usb":
|
||||
port = USBport(component)
|
||||
port.print()
|
||||
|
||||
def main():
|
||||
try:
|
||||
json_data = json.load(sys.stdin)
|
||||
except json.JSONDecodeError:
|
||||
print("Error, invalid JSON input")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print("Error, unexpected error parsing JSON")
|
||||
sys.exit(1)
|
||||
|
||||
parser = argparse.ArgumentParser(description="JSON CLI Pretty Printer")
|
||||
subparsers = parser.add_subparsers(dest='command', help='Commands')
|
||||
|
||||
parser_show_routing_table = subparsers.add_parser('show-routing-table', help='Show the routing table')
|
||||
parser_show_routing_table.add_argument('-i', '--ip', required=True, help='IPv4 or IPv6 address')
|
||||
|
||||
parser_show_interfaces = subparsers.add_parser('show-interfaces', help='Show interfaces')
|
||||
parser_show_interfaces.add_argument('-n', '--name', help='Interface name')
|
||||
|
||||
parser_show_bridge_mdb = subparsers.add_parser('show-bridge-mdb', help='Show bridge MDB')
|
||||
|
||||
parser_show_software = subparsers.add_parser('show-software', help='Show software versions')
|
||||
parser_show_software.add_argument('-n', '--name', help='Slotname')
|
||||
|
||||
parser_show_routing_table = subparsers.add_parser('show-hardware', help='Show USB ports')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "show-interfaces":
|
||||
show_interfaces(json_data, args.name)
|
||||
elif args.command == "show-routing-table":
|
||||
show_routing_table(json_data, args.ip)
|
||||
elif args.command == "show-software":
|
||||
show_software(json_data, args.name)
|
||||
elif args.command == "show-bridge-mdb":
|
||||
show_bridge_mdb(json_data)
|
||||
elif args.command == "show-hardware":
|
||||
show_hardware(json_data)
|
||||
else:
|
||||
print(f"Error, unknown command {args.command}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,4 @@
|
||||
from .ospf_status import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/python3
|
||||
# This script is used to transform the output from the show ip ospf commands and order
|
||||
# them to match the ietf-ospf YANG model. For example, interfaces is ordered under
|
||||
# area but FRR has areas in interfaces.
|
||||
#
|
||||
# This makes the parsing for the operational parts of YANG model more easy
|
||||
#
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
def main():
|
||||
iface_out=subprocess.check_output("vtysh -c 'show ip ospf interface json'", shell=True)
|
||||
ospf_out=subprocess.check_output("vtysh -c 'show ip ospf json'", shell=True)
|
||||
neighbor_out=subprocess.check_output("vtysh -c 'show ip ospf neighbor detail json'", shell=True)
|
||||
interfaces=json.loads(iface_out)
|
||||
ospf=json.loads(ospf_out)
|
||||
neighbors=json.loads(neighbor_out)
|
||||
|
||||
for ifname,iface in interfaces["interfaces"].items():
|
||||
iface["name"] = ifname
|
||||
iface["neighbors"] = []
|
||||
for area_id in ospf["areas"]:
|
||||
area_type=""
|
||||
|
||||
stub=False
|
||||
if("NSSA" in iface["area"]):
|
||||
iface_area_id = iface["area"][:-7]
|
||||
area_type = "nssa-area"
|
||||
elif("Stub" in iface["area"]):
|
||||
iface_area_id = iface["area"][:-7]
|
||||
iface["areaId"] = iface["area"][:-7]
|
||||
area_type = "stub-area"
|
||||
else:
|
||||
iface_area_id = iface["area"]
|
||||
area_type = "normal-area"
|
||||
|
||||
if(iface_area_id != area_id):
|
||||
continue
|
||||
|
||||
ospf["areas"][area_id]["area-type"] = area_type
|
||||
iface["area"] = iface_area_id
|
||||
|
||||
for nbrAddress,nbrDatas in neighbors["neighbors"].items():
|
||||
for nbrData in nbrDatas:
|
||||
nbrIfname=nbrData["ifaceName"].split(":")[0]
|
||||
if(("NSSA" in nbrData.get("areaId", {})) or ("Stub" in nbrData.get("areaId", {}))):
|
||||
nbrData["areaId"] = nbrData["areaId"][:-7]
|
||||
|
||||
if ((nbrIfname != ifname) and (area_id != nbrData.get("areaId"))):
|
||||
#print(f'Continute {ifname} {nbrData.get("areaId")}')
|
||||
continue
|
||||
nbrData["neighborIp"] = nbrAddress
|
||||
iface["neighbors"].append(nbrData)
|
||||
|
||||
if(not ospf["areas"][area_id].get("interfaces", None)):
|
||||
ospf["areas"][area_id]["interfaces"] = []
|
||||
ospf["areas"][area_id]["interfaces"].append(iface)
|
||||
|
||||
print(json.dumps(ospf))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,23 @@
|
||||
[tool.poetry]
|
||||
name = "infix-yang-tools"
|
||||
version = "1.0"
|
||||
description = "Linux to infix-YANG"
|
||||
license = "MIT"
|
||||
packages = [
|
||||
{ include = "yanger/*.py" },
|
||||
{ include = "cli_pretty/*.py" },
|
||||
{ include = "ospf_status/*.py" }
|
||||
]
|
||||
authors = [
|
||||
"KernelKit developers"
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
|
||||
[tool.poetry.scripts]
|
||||
yanger = "yanger:main"
|
||||
cli-pretty = "cli_pretty:main"
|
||||
ospf-status = "ospf_status:main"
|
||||
@@ -0,0 +1,4 @@
|
||||
from .yanger import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+896
@@ -0,0 +1,896 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import json
|
||||
import sys # (built-in module)
|
||||
import os
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
TESTPATH = ""
|
||||
|
||||
def json_get_yang_type(iface_in):
|
||||
if iface_in['link_type'] == "loopback":
|
||||
return "infix-if-type:loopback"
|
||||
|
||||
if iface_in['link_type'] != "ether":
|
||||
return "infix-if-type:other"
|
||||
|
||||
if 'parentbus' in iface_in and iface_in['parentbus'] == "virtio":
|
||||
return "infix-if-type:etherlike"
|
||||
|
||||
if not 'linkinfo' in iface_in:
|
||||
return "infix-if-type:ethernet"
|
||||
|
||||
if not 'info_kind' in iface_in['linkinfo']:
|
||||
return "infix-if-type:ethernet"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] == "veth":
|
||||
return "infix-if-type:veth"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] == "vlan":
|
||||
return "infix-if-type:vlan"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] == "bridge":
|
||||
return "infix-if-type:bridge"
|
||||
|
||||
if iface_in['linkinfo']['info_kind'] == "dsa":
|
||||
return "infix-if-type:ethernet"
|
||||
|
||||
# Fallback
|
||||
return "infix-if-type:ethernet"
|
||||
|
||||
def json_get_yang_origin(addr):
|
||||
"""Translate kernel IP address origin to YANG"""
|
||||
xlate = {
|
||||
"kernel_ll": "link-layer",
|
||||
"kernel_ra": "link-layer",
|
||||
"static": "static",
|
||||
"dhcp": "dhcp",
|
||||
"random": "random",
|
||||
}
|
||||
proto = addr['protocol']
|
||||
|
||||
if proto in ("kernel_ll", "kernel_ra"):
|
||||
if "stable-privacy" in addr:
|
||||
return "random"
|
||||
|
||||
return xlate.get(proto, "other")
|
||||
|
||||
def getitem(data, key):
|
||||
"""Get sub-object from an object"""
|
||||
while key:
|
||||
data = data[key[0]]
|
||||
key = key[1:]
|
||||
|
||||
return data
|
||||
|
||||
def get_proc_value(procfile):
|
||||
"""Return contents of /proc file, or None"""
|
||||
try:
|
||||
with open(procfile, 'r') as file:
|
||||
data = file.read().strip()
|
||||
return data
|
||||
except FileNotFoundError:
|
||||
# This is considered OK
|
||||
return None
|
||||
except IOError:
|
||||
print(f"Error: reading from {procfile}", file=sys.stderr)
|
||||
|
||||
def lookup(obj, *keys):
|
||||
"""This function returns a value from a nested json object"""
|
||||
curr = obj
|
||||
for key in keys:
|
||||
if isinstance(curr, dict) and key in curr:
|
||||
curr = curr[key]
|
||||
else:
|
||||
return None
|
||||
return curr
|
||||
|
||||
def insert(obj, *path_and_value):
|
||||
""""This function inserts a value into a nested json object"""
|
||||
if len(path_and_value) < 2:
|
||||
raise ValueError("Error: insert() takes at least two args")
|
||||
|
||||
*path, value = path_and_value
|
||||
|
||||
curr = obj
|
||||
for key in path[:-1]:
|
||||
if key not in curr or not isinstance(curr[key], dict):
|
||||
curr[key] = {}
|
||||
curr = curr[key]
|
||||
|
||||
curr[path[-1]] = value
|
||||
|
||||
def run_cmd(cmd, testfile):
|
||||
"""Run a command (array of args) and return an array of lines"""
|
||||
|
||||
if TESTPATH and testfile:
|
||||
cmd = ['cat', os.path.join(TESTPATH, testfile)]
|
||||
|
||||
try:
|
||||
output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL, text=True)
|
||||
return output.splitlines()
|
||||
except subprocess.CalledProcessError:
|
||||
print("Error: command returned error", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def run_json_cmd(cmd, testfile):
|
||||
"""Run a command (array of args) that returns JSON text output and return JSON"""
|
||||
|
||||
if TESTPATH and testfile:
|
||||
cmd = ['cat', os.path.join(TESTPATH, testfile)]
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, text=True)
|
||||
output = result.stdout
|
||||
data = json.loads(output)
|
||||
except subprocess.CalledProcessError as err:
|
||||
data = {}
|
||||
except json.JSONDecodeError as err:
|
||||
print(f"Error: parsing JSON output: {err.msg}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return data
|
||||
|
||||
def iface_is_dsa(iface_in):
|
||||
"""Check if interface is a DSA/intra-switch port"""
|
||||
if not "linkinfo" in iface_in:
|
||||
return False
|
||||
if not "info_kind" in iface_in['linkinfo']:
|
||||
return False
|
||||
if iface_in['linkinfo']['info_kind'] != "dsa":
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_vpd_vendor_extensions(data):
|
||||
vendor_extensions=[]
|
||||
for ext in data:
|
||||
vendor_extension = {}
|
||||
vendor_extension["iana-enterprise-number"] = ext[0]
|
||||
vendor_extension["extension-data"] = ext[1]
|
||||
vendor_extensions.append(vendor_extension)
|
||||
return vendor_extensions
|
||||
|
||||
def get_vpd_data(vpd):
|
||||
component={}
|
||||
component["name"]=vpd.get("board")
|
||||
component["infix-hardware:vpd-data"] = {}
|
||||
|
||||
if vpd.get("data"):
|
||||
component["class"] = "infix-hardware:vpd"
|
||||
if vpd["data"].get("manufacture-date"):
|
||||
component["mfg-date"]=datetime.strptime(vpd["data"]["manufacture-date"],"%m/%d/%Y %H:%M:%S").strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
if vpd["data"].get("manufacter"):
|
||||
component["mfg-name"]=vpd["data"]["manufacturer"]
|
||||
if vpd["data"].get("product-name"):
|
||||
component["model-name"]=vpd["data"]["product-name"]
|
||||
if vpd["data"].get("serial-number"):
|
||||
component["serial-num"]=vpd["data"]["serial-number"]
|
||||
|
||||
# Set VPD-data entrys
|
||||
for k,v in vpd["data"].items():
|
||||
if vpd["data"].get(k):
|
||||
if k != "vendor-extension":
|
||||
component["infix-hardware:vpd-data"][k] = v
|
||||
else:
|
||||
vendor_extensions=get_vpd_vendor_extensions(v)
|
||||
component["infix-hardware:vpd-data"]["infix-hardware:vendor-extension"] = vendor_extensions
|
||||
return component
|
||||
|
||||
def get_usb_ports(usb_ports):
|
||||
ports=[]
|
||||
names=[]
|
||||
for usb_port in usb_ports:
|
||||
port={}
|
||||
if usb_port.get("path"):
|
||||
if usb_port["name"] in names:
|
||||
continue
|
||||
|
||||
path = usb_port["path"]
|
||||
if os.path.basename(path) == "authorized_default":
|
||||
if os.path.exists(path):
|
||||
with open(path, "r") as f:
|
||||
names.append(usb_port["name"])
|
||||
data=int(f.readline().strip())
|
||||
enabled="unlocked" if data == 1 else "locked"
|
||||
port["state"] = {}
|
||||
port["state"]["admin-state"] = enabled
|
||||
port["name"] = usb_port["name"]
|
||||
port["class"] = "infix-hardware:usb"
|
||||
port["state"]["oper-state"] = "enabled"
|
||||
ports.append(port)
|
||||
|
||||
return ports
|
||||
|
||||
|
||||
def add_hardware(hw_out):
|
||||
data = run_json_cmd(['cat', "/run/system.json"], "system.json")
|
||||
components=[]
|
||||
for _,vpd in data.get("vpd").items():
|
||||
component=get_vpd_data(vpd)
|
||||
components.append(component)
|
||||
if data.get("usb-ports", None):
|
||||
components.extend(get_usb_ports(data["usb-ports"]))
|
||||
insert(hw_out, "component", components)
|
||||
|
||||
def get_routes(routes, proto, data):
|
||||
"""Populate routes"""
|
||||
out={}
|
||||
out["route"] = []
|
||||
|
||||
if proto == "ipv4":
|
||||
default = "0.0.0.0/0"
|
||||
host_prefix_length="32"
|
||||
else:
|
||||
default = "::/0"
|
||||
host_prefix_length="128"
|
||||
|
||||
for entry in data:
|
||||
new = {}
|
||||
if entry['dst'] == "default":
|
||||
entry['dst'] = default
|
||||
if entry['dst'].find('/') == -1:
|
||||
entry['dst'] = entry['dst'] + "/" + host_prefix_length
|
||||
new[f'ietf-{proto}-unicast-routing:destination-prefix'] = entry['dst']
|
||||
new['source-protocol'] = "infix-routing:" + entry['protocol']
|
||||
if entry.get("metric"):
|
||||
new['route-preference'] = entry['metric']
|
||||
else:
|
||||
new['route-preference'] = 0
|
||||
|
||||
if entry.get('nexthops'):
|
||||
next_hops = []
|
||||
for hop in entry.get('nexthops'):
|
||||
next_hop = {}
|
||||
if hop.get("dev"):
|
||||
next_hop['outgoing-interface'] = hop['dev']
|
||||
if hop.get("gateway"):
|
||||
next_hop[f'ietf-{proto}-unicast-routing:address'] = hop['gateway']
|
||||
next_hops.append(next_hop)
|
||||
|
||||
insert(new, 'next-hop', 'next-hop-list', 'next-hop', next_hops)
|
||||
else:
|
||||
next_hop = {}
|
||||
if entry['type'] == "blackhole":
|
||||
next_hop['special-next-hop'] = "blackhole"
|
||||
if entry['type'] == "unreachable":
|
||||
next_hop['special-next-hop'] = "unreachable"
|
||||
if entry['type'] == "unicast":
|
||||
if entry.get("dev"):
|
||||
next_hop['outgoing-interface'] = entry['dev']
|
||||
if entry.get("gateway"):
|
||||
next_hop[f'ietf-{proto}-unicast-routing:next-hop-address'] = entry['gateway']
|
||||
|
||||
new['next-hop'] = next_hop
|
||||
|
||||
out['route'].append(new)
|
||||
|
||||
insert(routes, 'routes', out)
|
||||
|
||||
def add_ipv4_route(routes):
|
||||
"""Fetch IPv4 routes from kernel and populate tree"""
|
||||
data = run_json_cmd(['ip', '-4', '-s', '-d', '-j', 'route'], "ip-4-route.json")
|
||||
get_routes(routes, "ipv4", data)
|
||||
|
||||
def add_ipv6_route(routes):
|
||||
"""Fetch IPv6 routes from kernel and populate tree"""
|
||||
data = run_json_cmd(['ip', '-6', '-s', '-d', '-j', 'route'], "ip-6-route.json")
|
||||
get_routes(routes, "ipv6", data)
|
||||
|
||||
def frr_to_ietf_neighbor_state(state):
|
||||
"""Fetch OSPF neighbor state from Frr"""
|
||||
state = state.split("/")[0]
|
||||
if state == "TwoWay":
|
||||
return "2-way"
|
||||
return state.lower()
|
||||
|
||||
def add_ospf_routes(ospf):
|
||||
"""Fetch OSPF routes from Frr"""
|
||||
cmd = ['vtysh', '-c', "show ip ospf rout json"]
|
||||
data = run_json_cmd(cmd, "")
|
||||
routes=[]
|
||||
|
||||
for prefix,info in data.items():
|
||||
if prefix.find("/") == -1: # Ignore router IDs
|
||||
continue
|
||||
|
||||
route={}
|
||||
route["prefix"] = prefix
|
||||
|
||||
nexthops=[]
|
||||
routetype=info["routeType"].split(" ")
|
||||
|
||||
if len(routetype) > 1:
|
||||
if routetype[1] == "E1":
|
||||
route["route-type"] = "external-1"
|
||||
elif routetype[1] == "E2":
|
||||
route["route-type"] = "external-2"
|
||||
elif routetype[1] == "IA":
|
||||
route["route-type"] = "inter-area"
|
||||
elif routetype[0] == "N":
|
||||
route["route-type"] = "intra-area"
|
||||
|
||||
for hop in info["nexthops"]:
|
||||
nexthop={}
|
||||
if hop["ip"] != " ":
|
||||
nexthop["next-hop"] = hop["ip"]
|
||||
else:
|
||||
nexthop["outgoing-interface"] = hop["directlyAttachedTo"]
|
||||
nexthops.append(nexthop)
|
||||
|
||||
route["next-hops"] = {}
|
||||
route["next-hops"]["next-hop"] = nexthops
|
||||
routes.append(route)
|
||||
|
||||
insert(ospf, "ietf-ospf:local-rib", "ietf-ospf:route", routes)
|
||||
|
||||
def add_ospf(ospf):
|
||||
"""Populate OSPF status"""
|
||||
|
||||
cmd = ['/usr/libexec/statd/ospf-status']
|
||||
data = run_json_cmd(cmd, "")
|
||||
|
||||
ospf["ietf-ospf:router-id"] = data["routerId"]
|
||||
ospf["ietf-ospf:address-family"] = "ipv4"
|
||||
areas=[]
|
||||
|
||||
for area_id,values in data["areas"].items():
|
||||
area={}
|
||||
area["ietf-ospf:area-id"] = area_id
|
||||
area["ietf-ospf:interfaces"] = {}
|
||||
if values.get("area-type"):
|
||||
area["ietf-ospf:area-type"] = values["area-type"]
|
||||
interfaces=[]
|
||||
for iface in values.get("interfaces", {}):
|
||||
interface={}
|
||||
interface["ietf-ospf:neighbors"] = {}
|
||||
interface["name"] = iface["name"]
|
||||
|
||||
if iface.get("drId"):
|
||||
interface["dr-router-id"] = iface["drId"]
|
||||
if iface.get("drAddress"):
|
||||
interface["dr-ip-addr"] = iface["drAddress"]
|
||||
if iface.get("bdrId"):
|
||||
interface["bdr-router-id"] = iface["bdrId"]
|
||||
if iface.get("bdrAddress"):
|
||||
interface["bdr-ip-addr"] = iface["bdrAddress"]
|
||||
|
||||
if iface.get("timerPassiveIface"):
|
||||
interface["passive"] = True
|
||||
else:
|
||||
interface["passive"] = False
|
||||
|
||||
interface["enabled"] = iface["ospfEnabled"]
|
||||
if iface["networkType"] == "POINTOPOINT":
|
||||
interface["interface-type"] = "point-to-point"
|
||||
if iface["networkType"] == "BROADCAST":
|
||||
interface["interface-type"] = "broadcast"
|
||||
|
||||
if iface.get("state"):
|
||||
# Wev've never seen "DependUpon", and has no entry in
|
||||
# the YANG model, but is listed before down in Frr
|
||||
xlate = {
|
||||
"DependUpon": "down",
|
||||
"Down": "down",
|
||||
"Waiting": "waiting",
|
||||
"Loopback": "loopback",
|
||||
"Point-To-Point": "point-to-point",
|
||||
"DROther": "dr-other",
|
||||
"Backup": "bdr",
|
||||
"DR": "dr"
|
||||
}
|
||||
val = xlate.get(iface["state"], "unknown")
|
||||
interface["state"] = val
|
||||
|
||||
neighbors = []
|
||||
for neigh in iface["neighbors"]:
|
||||
neighbor={}
|
||||
neighbor["neighbor-router-id"] = neigh["neighborIp"]
|
||||
neighbor["address"] = neigh["ifaceAddress"]
|
||||
neighbor["dr-router-id"] = neigh["routerDesignatedId"]
|
||||
neighbor["bdr-router-id"] = neigh["routerDesignatedBackupId"]
|
||||
neighbor["dead-timer"] = neigh["routerDeadIntervalTimerDueMsec"]
|
||||
neighbor["state"]=frr_to_ietf_neighbor_state(neigh["nbrState"])
|
||||
neighbors.append(neighbor)
|
||||
|
||||
interface["ietf-ospf:neighbors"] = {}
|
||||
interface["ietf-ospf:neighbors"]["ietf-ospf:neighbor"] = neighbors
|
||||
interfaces.append(interface)
|
||||
|
||||
area["ietf-ospf:interfaces"]["ietf-ospf:interface"] = interfaces
|
||||
areas.append(area)
|
||||
|
||||
insert(ospf, "ietf-ospf:areas", "area", areas)
|
||||
add_ospf_routes(ospf)
|
||||
|
||||
def get_bridge_port_pvid(ifname):
|
||||
data = run_json_cmd(['bridge', '-j', 'vlan', 'show', 'dev', ifname],
|
||||
f"bridge-vlan-show-dev-{ifname}.json")
|
||||
if len(data) != 1:
|
||||
return None
|
||||
|
||||
iface = data[0]
|
||||
|
||||
for vlan in iface['vlans']:
|
||||
if 'flags' in vlan and 'PVID' in vlan['flags']:
|
||||
return vlan['vlan']
|
||||
|
||||
return None
|
||||
|
||||
def get_bridge_port_stp_state(ifname):
|
||||
data = run_json_cmd(['bridge', '-j', 'link', 'show', 'dev', ifname],
|
||||
f"bridge-link-show-dev-{ifname}.json")
|
||||
if len(data) != 1:
|
||||
return None
|
||||
|
||||
iface = data[0]
|
||||
|
||||
states = [ 'disabled', 'listening', 'learning', 'forwarding', 'blocking' ]
|
||||
if 'state' in iface and iface['state'] in states:
|
||||
return iface['state']
|
||||
|
||||
return None
|
||||
|
||||
def container_inspect(name, key):
|
||||
"""Call podman inspect {name}, return object at {path} or None"""
|
||||
cmd = ['podman', 'inspect', name]
|
||||
raw = run_json_cmd(cmd, "")
|
||||
|
||||
return getitem(raw[0], key)
|
||||
|
||||
def add_container(containers):
|
||||
"""In container-state we list *all* containers, not just ones manged in configuration."""
|
||||
cmd = ['podman', 'ps', '-a', '--format=json']
|
||||
|
||||
raw = run_json_cmd(cmd, "")
|
||||
for entry in raw:
|
||||
running = entry["State"] == "running"
|
||||
|
||||
container = {
|
||||
"name": entry["Names"][0],
|
||||
"id": entry["Id"],
|
||||
"image": entry["Image"],
|
||||
"image-id": entry["ImageID"],
|
||||
"running": running,
|
||||
"status": entry["Status"]
|
||||
}
|
||||
|
||||
# Bonus information, may not be available
|
||||
if entry["Command"]:
|
||||
container["command"] = " ".join(entry["Command"])
|
||||
|
||||
# The 'podman ps' command lists ports even in host mode, but
|
||||
# that's not applicable, so skip networks and port forwardings
|
||||
networks = container_inspect(container["name"], ("NetworkSettings", "Networks"))
|
||||
if "host" in networks:
|
||||
container["network"] = { "host": True }
|
||||
else:
|
||||
container["network"] = {
|
||||
"interface": [],
|
||||
"publish": []
|
||||
}
|
||||
|
||||
if entry["Networks"]:
|
||||
for net in entry["Networks"]:
|
||||
container["network"]["interface"].append({ "name": net })
|
||||
|
||||
if running and entry["Ports"]:
|
||||
for port in entry["Ports"]:
|
||||
addr = ""
|
||||
if port["host_ip"]:
|
||||
addr = f"{port['host_ip']}:"
|
||||
|
||||
pub = f"{addr}{port['host_port']}->{port['container_port']}/{port['protocol']}"
|
||||
container["network"]["publish"].append(pub)
|
||||
|
||||
containers.append(container)
|
||||
|
||||
def get_brport_multicast(ifname):
|
||||
data=run_json_cmd(['mctl', 'show', 'igmp', 'json'], "bridge-mdb.json")
|
||||
multicast={}
|
||||
if ifname in data.get('fast-leave-ports', []):
|
||||
multicast["fast-leave"] = True
|
||||
else:
|
||||
multicast["fast-leave"] = False
|
||||
if ifname in data.get('multicast-router-ports', []):
|
||||
multicast["router"] = "permanent";
|
||||
else:
|
||||
multicast["router"] = "auto";
|
||||
return multicast
|
||||
|
||||
def add_ip_link(ifname, iface_out):
|
||||
"""Fetch interface link information from kernel"""
|
||||
data = run_json_cmd(['ip', '-s', '-d', '-j', 'link', 'show', 'dev', ifname],
|
||||
f"ip-link-show-dev-{ifname}.json")
|
||||
if len(data) != 1:
|
||||
print("Error: expected ip link output to be array with length 1", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
iface_in = data[0]
|
||||
|
||||
if 'ifname' in iface_in:
|
||||
iface_out['name'] = iface_in['ifname']
|
||||
|
||||
if 'ifindex' in iface_in:
|
||||
iface_out['if-index'] = iface_in['ifindex']
|
||||
|
||||
if 'address' in iface_in:
|
||||
iface_out['phys-address'] = iface_in['address']
|
||||
|
||||
if 'master' in iface_in:
|
||||
insert(iface_out, "infix-interfaces:bridge-port", "bridge", iface_in['master'])
|
||||
|
||||
pvid = get_bridge_port_pvid(ifname)
|
||||
if pvid is not None:
|
||||
insert(iface_out, "infix-interfaces:bridge-port", "pvid", pvid)
|
||||
|
||||
stp_state = get_bridge_port_stp_state(ifname)
|
||||
if stp_state is not None:
|
||||
insert(iface_out, "infix-interfaces:bridge-port", "stp-state", stp_state)
|
||||
|
||||
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 'operstate' in iface_in:
|
||||
xlate = {
|
||||
"DOWN": "down",
|
||||
"UP": "up",
|
||||
"DORMANT": "dormant",
|
||||
"TESTING": "testing",
|
||||
"LOWERLAYERDOWN": "lower-layer-down",
|
||||
"NOTPRESENT": "not-present"
|
||||
}
|
||||
val = xlate.get(iface_in['operstate'], "unknown")
|
||||
iface_out['oper-status'] = val
|
||||
|
||||
if 'link_type' in iface_in:
|
||||
val = json_get_yang_type(iface_in)
|
||||
iface_out['type'] = val
|
||||
|
||||
val = lookup(iface_in, "stats64", "rx", "bytes")
|
||||
if val is not None:
|
||||
insert(iface_out, "statistics", "out-octets", str(val))
|
||||
|
||||
val = lookup(iface_in, "stats64", "tx", "bytes")
|
||||
if val is not None:
|
||||
insert(iface_out, "statistics", "in-octets", str(val))
|
||||
|
||||
def add_ip_addr(ifname, iface_out):
|
||||
"""Fetch interface address information from kernel"""
|
||||
|
||||
data = run_json_cmd(['ip', '-j', 'addr', 'show', 'dev', ifname],
|
||||
f"ip-addr-show-dev-{ifname}.json")
|
||||
if len(data) != 1:
|
||||
print("Error: expected ip addr output to be array with length 1", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
iface_in = data[0]
|
||||
|
||||
if 'mtu' in iface_in and ifname != "lo":
|
||||
insert(iface_out, "ietf-ip:ipv4", "mtu", iface_in['mtu'])
|
||||
|
||||
# We avoid importing os to check if the file exists (for performance)
|
||||
val = get_proc_value(f"/proc/sys/net/ipv6/conf/{ifname}/mtu")
|
||||
if val is not None:
|
||||
insert(iface_out, "ietf-ip:ipv6", "mtu", int(val))
|
||||
|
||||
if 'addr_info' in iface_in:
|
||||
inet = []
|
||||
inet6 = []
|
||||
|
||||
for addr in iface_in['addr_info']:
|
||||
new = {}
|
||||
|
||||
if not 'family' in addr:
|
||||
print("Error: 'family' missing from 'addr_info'", file=sys.stderr)
|
||||
continue
|
||||
|
||||
if 'local' in addr:
|
||||
new['ip'] = addr['local']
|
||||
if 'prefixlen' in addr:
|
||||
new['prefix-length'] = addr['prefixlen']
|
||||
if 'protocol' in addr:
|
||||
new['origin'] = json_get_yang_origin(addr)
|
||||
|
||||
if addr['family'] == "inet":
|
||||
inet.append(new)
|
||||
elif addr['family'] == "inet6":
|
||||
inet6.append(new)
|
||||
else:
|
||||
print("Error: invalid 'family' in 'addr_info'", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
insert(iface_out, "ietf-ip:ipv4", "address", inet)
|
||||
insert(iface_out, "ietf-ip:ipv6", "address", inet6)
|
||||
|
||||
def add_ethtool_groups(ifname, iface_out):
|
||||
"""Fetch interface counters from kernel"""
|
||||
|
||||
data = run_json_cmd(['ethtool', '--json', '-S', ifname, '--all-groups'],
|
||||
f"ethtool-groups-{ifname}.json")
|
||||
if len(data) != 1:
|
||||
print("Error: expected ethtool groups output to be array with length 1", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
iface_in = data[0]
|
||||
|
||||
# TODO: room for improvement here, the "frame" creation could be more dynamic.
|
||||
if "eth-mac" in iface_in or "rmon" in iface_in:
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "statistics", "frame", {})
|
||||
frame = iface_out['ieee802-ethernet-interface:ethernet']['statistics']['frame']
|
||||
|
||||
if "eth-mac" in iface_in:
|
||||
mac_in = iface_in['eth-mac']
|
||||
|
||||
if "FramesTransmittedOK" in mac_in:
|
||||
frame['out-frames'] = str(mac_in['FramesTransmittedOK'])
|
||||
if "MulticastFramesXmittedOK" in mac_in:
|
||||
frame['out-multicast-frames'] = str(mac_in['MulticastFramesXmittedOK'])
|
||||
if "BroadcastFramesXmittedOK" in mac_in:
|
||||
frame['out-broadcast-frames'] = str(mac_in['BroadcastFramesXmittedOK'])
|
||||
if "FramesReceivedOK" in mac_in:
|
||||
frame['in-frames'] = str(mac_in['FramesReceivedOK'])
|
||||
if "MulticastFramesReceivedOK" in mac_in:
|
||||
frame['in-multicast-frames'] = str(mac_in['MulticastFramesReceivedOK'])
|
||||
if "BroadcastFramesReceivedOK" in mac_in:
|
||||
frame['in-broadcast-frames'] = str(mac_in['BroadcastFramesReceivedOK'])
|
||||
if "FrameCheckSequenceErrors" in mac_in:
|
||||
frame['in-error-fcs-frames'] = str(mac_in['FrameCheckSequenceErrors'])
|
||||
if "FramesLostDueToIntMACRcvError" in mac_in:
|
||||
frame['in-error-mac-internal-frames'] = str(mac_in['FramesLostDueToIntMACRcvError'])
|
||||
|
||||
if "OctetsTransmittedOK" in mac_in:
|
||||
frame['infix-ethernet-interface:out-good-octets'] = str(mac_in['OctetsTransmittedOK'])
|
||||
if "OctetsReceivedOK" in mac_in:
|
||||
frame['infix-ethernet-interface:in-good-octets'] = str(mac_in['OctetsReceivedOK'])
|
||||
|
||||
tot = 0
|
||||
found = False
|
||||
if "FramesReceivedOK" in mac_in:
|
||||
tot += mac_in['FramesReceivedOK']
|
||||
found = True
|
||||
if "FrameCheckSequenceErrors" in mac_in:
|
||||
tot += mac_in['FrameCheckSequenceErrors']
|
||||
found = True
|
||||
if "FramesLostDueToIntMACRcvError" in mac_in:
|
||||
tot += mac_in['FramesLostDueToIntMACRcvError']
|
||||
found = True
|
||||
if "AlignmentErrors" in mac_in:
|
||||
tot += mac_in['AlignmentErrors']
|
||||
found = True
|
||||
if "etherStatsOversizePkts" in mac_in:
|
||||
tot += mac_in['etherStatsOversizePkts']
|
||||
found = True
|
||||
if "etherStatsJabbers" in mac_in:
|
||||
tot += mac_in['etherStatsJabbers']
|
||||
found = True
|
||||
if found:
|
||||
frame['in-total-frames'] = str(tot)
|
||||
|
||||
if "rmon" in iface_in:
|
||||
rmon_in = iface_in['rmon']
|
||||
|
||||
if "undersize_pkts" in rmon_in:
|
||||
frame['in-error-undersize-frames'] = str(rmon_in['undersize_pkts'])
|
||||
|
||||
tot = 0
|
||||
found = False
|
||||
if "etherStatsJabbers" in rmon_in:
|
||||
tot += rmon_in['etherStatsJabbers']
|
||||
found = True
|
||||
if "etherStatsOversizePkts" in rmon_in:
|
||||
tot += rmon_in['etherStatsOversizePkts']
|
||||
found = True
|
||||
if found:
|
||||
frame['in-error-oversize-frames'] = str(tot)
|
||||
|
||||
def add_ethtool_std(ifname, iface_out):
|
||||
"""Fetch interface speed/duplex/autoneg from kernel"""
|
||||
keys = ['Speed', 'Duplex', 'Auto-negotiation']
|
||||
result = {}
|
||||
|
||||
lines = run_cmd(['ethtool', ifname], f"ethtool-{ifname}.txt")
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
key = line.split(':', 1)[0].strip()
|
||||
if key in keys:
|
||||
key, value = line.split(':', 1)
|
||||
result[key.strip()] = value.strip()
|
||||
|
||||
if "Auto-negotiation" in result:
|
||||
if result['Auto-negotiation'] == "on":
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "auto-negotiation", "enable", True)
|
||||
else:
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "auto-negotiation", "enable", False)
|
||||
|
||||
if "Duplex" in result:
|
||||
if result['Duplex'] == "Half":
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "duplex", "half")
|
||||
elif result['Duplex'] == "Full":
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "duplex", "full")
|
||||
else:
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "duplex", "unknown")
|
||||
|
||||
if "Speed" in result and result['Speed'] != "Unknown!":
|
||||
# Avoid importing re (performance)
|
||||
num = ''.join(filter(str.isdigit, result['Speed']))
|
||||
if num:
|
||||
num = round((int(num) / 1000), 3)
|
||||
insert(iface_out, "ieee802-ethernet-interface:ethernet", "speed", str(num))
|
||||
|
||||
def get_querier_data(querier_data):
|
||||
multicast={}
|
||||
|
||||
if not querier_data:
|
||||
multicast["snooping"] = False
|
||||
return multicast
|
||||
multicast["snooping"] = True
|
||||
if(querier_data.get("query-interval")):
|
||||
multicast["query-interval"] = querier_data["query-interval"]
|
||||
|
||||
return multicast
|
||||
|
||||
def get_multicast_filters(filters):
|
||||
multicast_filters=[]
|
||||
for f in filters:
|
||||
multicast_filter={}
|
||||
multicast_filter["group"] = f["group"]
|
||||
multicast_filter["ports"] = []
|
||||
for p in f["ports"]:
|
||||
port={}
|
||||
port["port"] = p
|
||||
multicast_filter["ports"].append(port)
|
||||
multicast_filters.append(multicast_filter)
|
||||
return multicast_filters
|
||||
|
||||
def add_mdb_to_bridge(brname, iface_out, mc_status):
|
||||
filters = [entry for entry in mc_status.get("multicast-groups", []) if entry.get('vid') == None and entry.get('bridge') == brname]
|
||||
querier = next((querier for querier in mc_status.get('multicast-queriers', []) if (querier.get("interface", "") == brname) and (querier.get("vid") is None)), None)
|
||||
multicast = get_querier_data(querier)
|
||||
multicast_filters = get_multicast_filters(filters)
|
||||
insert(iface_out, "infix-interfaces:bridge", "multicast", multicast)
|
||||
insert(iface_out, "infix-interfaces:bridge", "multicast-filters", "multicast-filter", multicast_filters)
|
||||
|
||||
# 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:
|
||||
if d['vid'] == vid:
|
||||
if key in d:
|
||||
d[key].append(val)
|
||||
else:
|
||||
d[key] = [val]
|
||||
return
|
||||
|
||||
vlans.append({'vid': vid, "multicast-filters": {"multicast-filter": multicast_filter}, "multicast": multicast, key: [val]})
|
||||
|
||||
def add_vlans_to_bridge(brname, iface_out, mc_status):
|
||||
slaves = [] # Contains all interfaces that has this bridge as 'master'
|
||||
for iface in run_json_cmd(['bridge', '-j', 'link'], "bridge-link.json"):
|
||||
if "master" in iface and iface['master'] == brname:
|
||||
slaves.append(iface['ifname'])
|
||||
|
||||
vlans = [] # Contains all vlans and slaves belonging to this bridge
|
||||
for iface in run_json_cmd(['bridge', '-j', 'vlan'], "bridge-vlan.json"):
|
||||
if iface['ifname'] not in slaves and iface['ifname'] != brname:
|
||||
continue
|
||||
for vlan in iface['vlans']:
|
||||
querier = next((querier for querier in mc_status.get('multicast-queriers', []) if (querier.get("vid") == vlan['vlan'])), None)
|
||||
filters = [entry for entry in mc_status.get("multicast-groups", []) if (entry.get("vid") == vlan["vlan"])]
|
||||
if 'flags' in vlan and 'Egress Untagged' in vlan['flags']:
|
||||
_add_vlan_iface(vlans, get_multicast_filters(filters), get_querier_data(querier), vlan['vlan'], 'untagged', iface['ifname'])
|
||||
else:
|
||||
_add_vlan_iface(vlans, get_multicast_filters(filters), get_querier_data(querier), vlan['vlan'], 'tagged', iface['ifname'])
|
||||
|
||||
insert(iface_out, "infix-interfaces:bridge", "vlans", "vlan", vlans)
|
||||
|
||||
def main():
|
||||
global TESTPATH
|
||||
|
||||
parser = argparse.ArgumentParser(description="YANG data creator")
|
||||
parser.add_argument("model", help="YANG Model")
|
||||
parser.add_argument("-p", "--param", default=None, help="Model dependant parameter")
|
||||
parser.add_argument("-t", "--test", default=None, help="Test data base path")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.test:
|
||||
TESTPATH = args.test
|
||||
else:
|
||||
TESTPATH = ""
|
||||
|
||||
if args.model == 'ietf-interfaces':
|
||||
# For now, we handle each interface separately, as this is how it's
|
||||
# currently implemented in sysrepo. I.e sysrepo will subscribe to
|
||||
# each individual interface and query it for YANG data.
|
||||
|
||||
if not args.param:
|
||||
print("usage: yanger ietf-interfaces -p INTERFACE", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
yang_data = {
|
||||
"ietf-interfaces:interfaces": {
|
||||
"interface": [{}]
|
||||
}
|
||||
}
|
||||
|
||||
ifname = args.param
|
||||
iface_out = yang_data['ietf-interfaces:interfaces']['interface'][0]
|
||||
|
||||
add_ip_link(ifname, iface_out, )
|
||||
add_ip_addr(ifname, iface_out)
|
||||
|
||||
if 'type' in iface_out and iface_out['type'] == "infix-if-type:ethernet":
|
||||
add_ethtool_groups(ifname, iface_out)
|
||||
add_ethtool_std(ifname, iface_out)
|
||||
|
||||
if 'type' in iface_out and iface_out['type'] == "infix-if-type:bridge":
|
||||
mc_status = run_json_cmd(['mctl', '-p', 'show', 'igmp', 'json'], "igmp-status.json")
|
||||
|
||||
add_vlans_to_bridge(ifname, iface_out, mc_status)
|
||||
add_mdb_to_bridge(ifname, iface_out, mc_status)
|
||||
|
||||
elif args.model == 'ietf-routing':
|
||||
yang_data = {
|
||||
"ietf-routing:routing": {
|
||||
"ribs": {
|
||||
"rib": [{
|
||||
"name": "ipv4",
|
||||
"address-family": "ipv4"
|
||||
},
|
||||
{
|
||||
"name": "ipv6",
|
||||
"address-family": "ipv6"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipv4routes = yang_data['ietf-routing:routing']['ribs']['rib'][0]
|
||||
ipv6routes = yang_data['ietf-routing:routing']['ribs']['rib'][1]
|
||||
add_ipv4_route(ipv4routes)
|
||||
add_ipv6_route(ipv6routes)
|
||||
|
||||
elif args.model == 'ietf-ospf':
|
||||
yang_data = {
|
||||
"ietf-routing:routing": {
|
||||
"control-plane-protocols": {
|
||||
"control-plane-protocol": [
|
||||
{
|
||||
"type": "ietf-ospf:ospfv2",
|
||||
"name": "default",
|
||||
"ietf-ospf:ospf": {
|
||||
"ietf-ospf:areas":
|
||||
{
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
add_ospf(yang_data['ietf-routing:routing']['control-plane-protocols']['control-plane-protocol'][0]["ietf-ospf:ospf"])
|
||||
|
||||
elif args.model == 'ietf-hardware':
|
||||
yang_data = {
|
||||
"ietf-hardware:hardware": {
|
||||
}
|
||||
}
|
||||
add_hardware(yang_data["ietf-hardware:hardware"])
|
||||
|
||||
elif args.model == 'infix-containers':
|
||||
yang_data = {
|
||||
"infix-containers:containers": {
|
||||
"container": []
|
||||
}
|
||||
}
|
||||
add_container(yang_data['infix-containers:containers']['container'])
|
||||
|
||||
else:
|
||||
print(f"Unsupported model {args.model}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(json.dumps(yang_data, indent=2))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user