Skip to content

Commit 024b51c

Browse files
committed
statd: add support for ip forwarding operational state
This patch unlocks "routing interfaces" support in the ietf-routing yang model. An array of interface with IP forwarding enabled. Note, because of #515 we skip IPv6 forwarding for now. This will in the near future be handled by a per-interface force_forwarding sysctl flag. The 'show interface [ifname]' admin-exec command has been extended with a Flags field for a quick overview of which interfaces have forwarding currently enabled. Fixes #647 Signed-off-by: Joachim Wiberg <[email protected]>
1 parent 1cbc9a4 commit 024b51c

File tree

4 files changed

+70
-8
lines changed

4 files changed

+70
-8
lines changed

src/confd/yang/confd/infix-routing.yang

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,6 @@ module infix-routing {
111111
}
112112

113113
/* General routing */
114-
deviation "/rt:routing/rt:interfaces" {
115-
description "Initial limitation";
116-
deviate not-supported;
117-
}
118114

119115
deviation "/rt:routing/rt:router-id" {
120116
description "Set in OSPF";

src/show/show.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ def interface(args: List[str]) -> None:
9595
print("No interface data retrieved.")
9696
return
9797

98+
# Also fetch routing interface list for forwarding indication
99+
routing_data = run_sysrepocfg("/ietf-routing:routing")
100+
if routing_data:
101+
# Merge routing data into the main data structure
102+
data.update(routing_data)
103+
98104
if RAW_OUTPUT:
99105
print(json.dumps(data, indent=2))
100106
return

src/statd/python/cli_pretty/cli_pretty.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ def compress_interface_list(interfaces):
9898

9999

100100
class Pad:
101+
flags = 2
101102
iface = 16
102103
proto = 11
103104
state = 12
@@ -803,6 +804,9 @@ def print_stats(self):
803804

804805

805806
class Iface:
807+
# Class variable to hold routing-enabled interfaces for the current display session
808+
_routing_ifaces = set()
809+
806810
def __init__(self, data):
807811
self.data = data
808812
self.name = data.get('name', '')
@@ -915,13 +919,16 @@ def oper(self, detail=False):
915919
return self.oper_status
916920

917921
def pr_name(self, pipe=""):
922+
flag = "⇅" if self.name in Iface._routing_ifaces else " "
923+
print(f"{flag:<{Pad.flags}}", end="")
918924
print(f"{pipe}{self.name:<{Pad.iface - len(pipe)}}", end="")
919925

920926
def pr_proto_ipv4(self, pipe=''):
921927
for addr in self.ipv4_addr:
922928
origin = f"({addr['origin']})" if addr.get('origin') else ""
923929

924-
row = f"{pipe:<{Pad.iface}}"
930+
row = f"{'':<{Pad.flags}}"
931+
row += f"{pipe:<{Pad.iface}}"
925932
row += f"{'ipv4':<{Pad.proto}}"
926933
row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']} {origin}"
927934
print(row)
@@ -930,15 +937,17 @@ def pr_proto_ipv6(self, pipe=''):
930937
for addr in self.ipv6_addr:
931938
origin = f"({addr['origin']})" if addr.get('origin') else ""
932939

933-
row = f"{pipe:<{Pad.iface}}"
940+
row = f"{'':<{Pad.flags}}"
941+
row += f"{pipe:<{Pad.iface}}"
934942
row += f"{'ipv6':<{Pad.proto}}"
935943
row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']} {origin}"
936944
print(row)
937945

938946
def _pr_proto_common(self, name, phys_address, pipe=''):
939947
row = ""
940948
if len(pipe) > 0:
941-
row = f"{pipe:<{Pad.iface}}"
949+
row = f"{'':<{Pad.flags}}"
950+
row += f"{pipe:<{Pad.iface}}"
942951

943952
row += f"{name:<{Pad.proto}}"
944953
dec = Decore.green if self.oper() == "up" else Decore.red
@@ -1184,6 +1193,11 @@ def pr_vlan(self, _ifaces):
11841193
parent.pr_proto_eth()
11851194

11861195
def pr_container(self):
1196+
# Add ⇅ flag for interfaces with IP forwarding enabled
1197+
flag = "⇅" if self.name in Iface._routing_ifaces else " "
1198+
# Flag column is outside the gray background
1199+
print(f"{flag:<{Pad.flags}}", end="")
1200+
11871201
row = f"{self.name:<{Pad.iface}}"
11881202
row += f"{'container':<{Pad.proto}}"
11891203
row += f"{'':<{Pad.state}}"
@@ -1204,6 +1218,9 @@ def pr_iface(self):
12041218
if self.oper():
12051219
print(f"{'operational status':<{20}}: {self.oper(detail=True)}")
12061220

1221+
forwarding = "enabled" if self.name in Iface._routing_ifaces else "disabled"
1222+
print(f"{'ip forwarding':<{20}}: {forwarding}")
1223+
12071224
if self.lower_if:
12081225
print(f"{'lower-layer-if':<{20}}: {self.lower_if}")
12091226

@@ -1407,13 +1424,21 @@ def print_interface(iface):
14071424

14081425

14091426
def pr_interface_list(json):
1410-
hdr = (f"{'INTERFACE':<{Pad.iface}}"
1427+
hdr = (f"{'⚑':<{Pad.flags}}"
1428+
f"{'INTERFACE':<{Pad.iface}}"
14111429
f"{'PROTOCOL':<{Pad.proto}}"
14121430
f"{'STATE':<{Pad.state}}"
14131431
f"{'DATA':<{Pad.data}}")
14141432

14151433
print(Decore.invert(hdr))
14161434

1435+
# Set class variable with routing-enabled interfaces (with IP forwarding)
1436+
if "ietf-routing:routing" in json:
1437+
routing_data = json["ietf-routing:routing"].get("interfaces", {})
1438+
Iface._routing_ifaces = set(routing_data.get("interface", []))
1439+
else:
1440+
Iface._routing_ifaces = set()
1441+
14171442
ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"],
14181443
key=ifname_sort)
14191444
iface = find_iface(ifaces, "lo")
@@ -1472,6 +1497,13 @@ def pr_interface_list(json):
14721497

14731498

14741499
def show_interfaces(json, name):
1500+
# Set class variable with routing-enabled interfaces (with IP forwarding)
1501+
if "ietf-routing:routing" in json:
1502+
routing_data = json["ietf-routing:routing"].get("interfaces", {})
1503+
Iface._routing_ifaces = set(routing_data.get("interface", []))
1504+
else:
1505+
Iface._routing_ifaces = set()
1506+
14751507
if name:
14761508
if not json.get("ietf-interfaces:interfaces"):
14771509
print(f"No interface data found for \"{name}\"")

src/statd/python/yanger/ietf_routing.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,37 @@ def add_protocol(routes, proto):
124124
insert(routes, 'routes', out)
125125

126126

127+
def get_routing_interfaces():
128+
"""Get list of interfaces with IPv4 forwarding enabled"""
129+
import json
130+
131+
# Get all interfaces
132+
links_json = HOST.run(tuple(['ip', '-j', 'link', 'show']), default="[]")
133+
links = json.loads(links_json)
134+
135+
routing_ifaces = []
136+
for link in links:
137+
ifname = link.get('ifname')
138+
if not ifname:
139+
continue
140+
141+
# Check if IPv4 forwarding is enabled
142+
# Note: We only check IPv4 forwarding. IPv6 forwarding behaves differently
143+
# and will be handled separately when Linux 6.17+ force_forwarding is available.
144+
ipv4_fwd = HOST.run(tuple(['sysctl', '-n', f'net.ipv4.conf.{ifname}.forwarding']), default="0").strip()
145+
146+
if ipv4_fwd == "1":
147+
routing_ifaces.append(ifname)
148+
149+
return routing_ifaces
150+
151+
127152
def operational():
128153
out = {
129154
"ietf-routing:routing": {
155+
"interfaces": {
156+
"interface": get_routing_interfaces()
157+
},
130158
"ribs": {
131159
"rib": [{
132160
"name": "ipv4",

0 commit comments

Comments
 (0)