Skip to content

Commit f705c45

Browse files
Validate interface name length in CLI (sonic-net#3580)
This PR is accompanied by sonic-net/sonic-swss-common#931 and sonic-net/sonic-buildimage#20108 What I did Validate interface name length does not exceed the limitation of IFNAMSIZ to align to kernel restrictions. How I did it Add validation checks in the relevant config functions for the following interface types: vxlan vlan vrf loopback subinterface portchannel How to verify it UT tests added Previous command output (if the output of a command-line utility has changed) New command output (if the output of a command-line utility has changed)
1 parent 3d78cb1 commit f705c45

File tree

8 files changed

+84
-46
lines changed

8 files changed

+84
-46
lines changed

config/main.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
from sonic_yang_cfg_generator import SonicYangCfgDbGenerator
3535
from utilities_common import util_base
3636
from swsscommon import swsscommon
37-
from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector, ConfigDBPipeConnector
37+
from swsscommon.swsscommon import SonicV2Connector, ConfigDBConnector, ConfigDBPipeConnector, \
38+
isInterfaceNameValid, IFACE_NAME_MAX_LEN
3839
from utilities_common.db import Db
3940
from utilities_common.intf_filter import parse_interface_in_filter
4041
from utilities_common import bgp_util
@@ -106,7 +107,6 @@
106107

107108
CFG_PORTCHANNEL_PREFIX = "PortChannel"
108109
CFG_PORTCHANNEL_PREFIX_LEN = 11
109-
CFG_PORTCHANNEL_NAME_TOTAL_LEN_MAX = 15
110110
CFG_PORTCHANNEL_MAX_VAL = 9999
111111
CFG_PORTCHANNEL_NO="<0-9999>"
112112

@@ -439,7 +439,7 @@ def is_portchannel_name_valid(portchannel_name):
439439
if (portchannel_name[CFG_PORTCHANNEL_PREFIX_LEN:].isdigit() is False or
440440
int(portchannel_name[CFG_PORTCHANNEL_PREFIX_LEN:]) > CFG_PORTCHANNEL_MAX_VAL) :
441441
return False
442-
if len(portchannel_name) > CFG_PORTCHANNEL_NAME_TOTAL_LEN_MAX:
442+
if not isInterfaceNameValid(portchannel_name):
443443
return False
444444
return True
445445

@@ -2484,8 +2484,9 @@ def add_portchannel(ctx, portchannel_name, min_links, fallback, fast_rate):
24842484
db = ValidatedConfigDBConnector(ctx.obj['db'])
24852485
if ADHOC_VALIDATION:
24862486
if is_portchannel_name_valid(portchannel_name) != True:
2487-
ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}'"
2488-
.format(portchannel_name, CFG_PORTCHANNEL_PREFIX, CFG_PORTCHANNEL_NO))
2487+
ctx.fail("{} is invalid!, name should have prefix '{}' and suffix '{}' "
2488+
"and its length should not exceed {} characters"
2489+
.format(portchannel_name, CFG_PORTCHANNEL_PREFIX, CFG_PORTCHANNEL_NO, IFACE_NAME_MAX_LEN))
24892490
if is_portchannel_present_in_db(db, portchannel_name):
24902491
ctx.fail("{} already exists!".format(portchannel_name)) # TODO: MISSING CONSTRAINT IN YANG MODEL
24912492

@@ -6881,8 +6882,8 @@ def add_vrf(ctx, vrf_name):
68816882
config_db = ValidatedConfigDBConnector(ctx.obj['config_db'])
68826883
if not vrf_name.startswith("Vrf") and not (vrf_name == 'mgmt') and not (vrf_name == 'management'):
68836884
ctx.fail("'vrf_name' must begin with 'Vrf' or named 'mgmt'/'management' in case of ManagementVRF.")
6884-
if len(vrf_name) > 15:
6885-
ctx.fail("'vrf_name' is too long!")
6885+
if not isInterfaceNameValid(vrf_name):
6886+
ctx.fail("'vrf_name' length should not exceed {} characters".format(IFACE_NAME_MAX_LEN))
68866887
if is_vrf_exists(config_db, vrf_name):
68876888
ctx.fail("VRF {} already exists!".format(vrf_name))
68886889
elif (vrf_name == 'mgmt' or vrf_name == 'management'):
@@ -6901,8 +6902,8 @@ def del_vrf(ctx, vrf_name):
69016902
config_db = ValidatedConfigDBConnector(ctx.obj['config_db'])
69026903
if not vrf_name.startswith("Vrf") and not (vrf_name == 'mgmt') and not (vrf_name == 'management'):
69036904
ctx.fail("'vrf_name' must begin with 'Vrf' or named 'mgmt'/'management' in case of ManagementVRF.")
6904-
if len(vrf_name) > 15:
6905-
ctx.fail("'vrf_name' is too long!")
6905+
if not isInterfaceNameValid(vrf_name):
6906+
ctx.fail("'vrf_name' length should not exceed {} characters".format((IFACE_NAME_MAX_LEN)))
69066907
syslog_table = config_db.get_table("SYSLOG_SERVER")
69076908
syslog_vrf_dev = "mgmt" if vrf_name == "management" else vrf_name
69086909
for syslog_entry, syslog_data in syslog_table.items():
@@ -7932,8 +7933,8 @@ def add_loopback(ctx, loopback_name):
79327933
config_db = ValidatedConfigDBConnector(ctx.obj['db'])
79337934
if ADHOC_VALIDATION:
79347935
if is_loopback_name_valid(loopback_name) is False:
7935-
ctx.fail("{} is invalid, name should have prefix '{}' and suffix '{}' "
7936-
.format(loopback_name, CFG_LOOPBACK_PREFIX, CFG_LOOPBACK_NO))
7936+
ctx.fail("{} is invalid, name should have prefix '{}' and suffix '{}' and should not exceed {} characters"
7937+
.format(loopback_name, CFG_LOOPBACK_PREFIX, CFG_LOOPBACK_NO, IFACE_NAME_MAX_LEN))
79377938

79387939
lo_intfs = [k for k, v in config_db.get_table('LOOPBACK_INTERFACE').items() if type(k) != tuple]
79397940
if loopback_name in lo_intfs:
@@ -8680,6 +8681,8 @@ def add_subinterface(ctx, subinterface_name, vid):
86808681

86818682
if interface_alias is None:
86828683
ctx.fail("{} invalid subinterface".format(interface_alias))
8684+
if not isInterfaceNameValid(interface_alias):
8685+
ctx.fail("Subinterface name length should not exceed {} characters".format(IFACE_NAME_MAX_LEN))
86838686

86848687
if interface_alias.startswith("Po") is True:
86858688
intf_table_name = CFG_PORTCHANNEL_PREFIX

config/vxlan.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from jsonpatch import JsonPatchConflict
55
from .validated_config_db_connector import ValidatedConfigDBConnector
6+
from swsscommon.swsscommon import isInterfaceNameValid, IFACE_NAME_MAX_LEN
67

78
ADHOC_VALIDATION = True
89
#
@@ -24,6 +25,8 @@ def add_vxlan(db, vxlan_name, src_ip):
2425
if ADHOC_VALIDATION:
2526
if not clicommon.is_ipaddress(src_ip):
2627
ctx.fail("{} invalid src ip address".format(src_ip))
28+
if not isInterfaceNameValid(vxlan_name):
29+
ctx.fail("'vxlan_name' length should not exceed {} characters".format(IFACE_NAME_MAX_LEN))
2730

2831
vxlan_keys = db.cfgdb.get_keys('VXLAN_TUNNEL')
2932
if not vxlan_keys:
@@ -317,4 +320,3 @@ def del_vxlan_map_range(db, vxlan_name, vlan_start, vlan_end, vni_start):
317320
config_db.set_entry('VXLAN_TUNNEL_MAP', mapname, None)
318321
except JsonPatchConflict as e:
319322
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
320-

tests/config_test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2847,6 +2847,13 @@ def test_add_loopback_with_invalid_name_adhoc_validation(self):
28472847
assert result.exit_code != 0
28482848
assert "Error: Loopbax1 is invalid, name should have prefix 'Loopback' and suffix '<0-999>'" in result.output
28492849

2850+
result = runner.invoke(config.config.commands["loopback"].commands["add"], ["Loopback0000"], obj=obj)
2851+
print(result.exit_code)
2852+
print(result.output)
2853+
assert result.exit_code != 0
2854+
assert "Error: Loopback0000 is invalid, name should have prefix 'Loopback' and suffix '<0-999>' and " \
2855+
"should not exceed 15 characters" in result.output
2856+
28502857
def test_del_nonexistent_loopback_adhoc_validation(self):
28512858
config.ADHOC_VALIDATION = True
28522859
runner = CliRunner()

tests/portchannel_test.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def test_add_portchannel_with_invalid_name_yang_validation(self):
3434
print(result.output)
3535
assert result.exit_code != 0
3636
assert "Error: PortChan005 is invalid!, name should have prefix 'PortChannel' and suffix '<0-9999>'" in result.output
37-
37+
3838
def test_add_portchannel_with_invalid_name_adhoc_validation(self):
3939
config.ADHOC_VALIDATION = True
4040
runner = CliRunner()
@@ -46,7 +46,15 @@ def test_add_portchannel_with_invalid_name_adhoc_validation(self):
4646
print(result.exit_code)
4747
print(result.output)
4848
assert result.exit_code != 0
49-
assert "Error: PortChan005 is invalid!, name should have prefix 'PortChannel' and suffix '<0-9999>'" in result.output
49+
assert "Error: PortChan005 is invalid!, name should have prefix 'PortChannel' and suffix '<0-9999>' " \
50+
"and its length should not exceed 15 characters" in result.output
51+
52+
result = runner.invoke(config.config.commands["portchannel"].commands["add"], ["PortChanl00000"], obj=obj)
53+
print(result.exit_code)
54+
print(result.output)
55+
assert result.exit_code != 0
56+
assert "Error: PortChanl00000 is invalid!, name should have prefix 'PortChannel' and suffix '<0-9999>' and " \
57+
"its length should not exceed 15 characters" in result.output
5058

5159
@patch("config.validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry", mock.Mock(side_effect=JsonPatchConflict))
5260
@patch("validated_config_db_connector.device_info.is_yang_config_validation_enabled", mock.Mock(return_value=True))
@@ -119,7 +127,7 @@ def test_add_portchannel_with_invalid_fast_rate(self, fast_rate):
119127
runner = CliRunner()
120128
db = Db()
121129
obj = {'db':db.cfgdb}
122-
130+
123131
# add a portchannel with invalid fats rate
124132
result = runner.invoke(config.config.commands["portchannel"].commands["add"], ["PortChannel0005", "--fast-rate", fast_rate], obj=obj)
125133
print(result.exit_code)

tests/subintf_test.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def test_add_del_subintf_short_name(self):
2424
runner = CliRunner()
2525
db = Db()
2626
obj = {'db':db.cfgdb}
27-
27+
2828
result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Eth0.102", "1002"], obj=obj)
2929
print(result.exit_code, result.output)
3030
assert result.exit_code == 0
@@ -53,35 +53,23 @@ def test_add_del_subintf_with_long_name(self):
5353
runner = CliRunner()
5454
db = Db()
5555
obj = {'db':db.cfgdb}
56-
56+
5757
result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Ethernet0.102"], obj=obj)
5858
print(result.exit_code, result.output)
5959
assert result.exit_code == 0
6060
assert ('Ethernet0.102') in db.cfgdb.get_table('VLAN_SUB_INTERFACE')
6161
assert db.cfgdb.get_table('VLAN_SUB_INTERFACE')['Ethernet0.102']['admin_status'] == 'up'
6262

63-
result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["PortChannel0004.104"], obj=obj)
64-
print(result.exit_code, result.output)
65-
assert result.exit_code == 0
66-
assert ('PortChannel0004.104') in db.cfgdb.get_table('VLAN_SUB_INTERFACE')
67-
assert db.cfgdb.get_table('VLAN_SUB_INTERFACE')['PortChannel0004.104']['admin_status'] == 'up'
68-
6963
result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["Ethernet0.102"], obj=obj)
7064
print(result.exit_code, result.output)
7165
assert result.exit_code == 0
7266
assert ('Ethernet0.102') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')
7367

74-
result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["PortChannel0004.104"], obj=obj)
75-
print(result.exit_code, result.output)
76-
assert result.exit_code == 0
77-
assert ('PortChannel0004.104') not in db.cfgdb.get_table('VLAN_SUB_INTERFACE')
78-
79-
8068
def test_add_existing_subintf_again(self):
8169
runner = CliRunner()
8270
db = Db()
8371
obj = {'db':db.cfgdb}
84-
72+
8573
result = runner.invoke(config.config.commands["subinterface"].commands["add"], ["Ethernet0.102"], obj=obj)
8674
print(result.exit_code, result.output)
8775
assert result.exit_code == 0
@@ -104,7 +92,7 @@ def test_delete_non_existing_subintf(self):
10492
runner = CliRunner()
10593
db = Db()
10694
obj = {'db':db.cfgdb}
107-
95+
10896
result = runner.invoke(config.config.commands["subinterface"].commands["del"], ["Ethernet0.102"], obj=obj)
10997
print(result.exit_code, result.output)
11098
assert result.exit_code != 0

tests/vlan_test.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,15 @@ def test_config_vlan_del_member_with_invalid_port(self):
574574
assert result.exit_code != 0
575575
assert "Error: Invalid VLAN ID 4097 (2-4094)" in result.output
576576

577+
def test_config_vlan_add_member_with_invalid_long_name(self):
578+
runner = CliRunner()
579+
result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"],
580+
["123456789012", "Ethernet4"])
581+
print(result.exit_code)
582+
print(result.output)
583+
assert result.exit_code != 0
584+
assert "Error: Invalid VLAN ID 123456789012 (2-4094)" in result.output
585+
577586
def test_config_vlan_add_member_with_nonexist_vlanid(self):
578587
runner = CliRunner()
579588
result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1001", "Ethernet4"])

tests/vrf_test.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def test_vrf_show(self):
4141
Loopback0
4242
Po0002.101
4343
"""
44-
44+
4545
result = runner.invoke(show.cli.commands['vrf'], [], obj=db)
4646
dbconnector.dedicated_dbs = {}
4747
assert result.exit_code == 0
@@ -65,7 +65,7 @@ def test_vrf_bind_unbind(self):
6565
Loopback0
6666
Po0002.101
6767
"""
68-
68+
6969
result = runner.invoke(show.cli.commands['vrf'], [], obj=db)
7070
dbconnector.dedicated_dbs = {}
7171
assert result.exit_code == 0
@@ -81,7 +81,7 @@ def test_vrf_bind_unbind(self):
8181
assert result.exit_code == 0
8282
assert 'Ethernet4' not in db.cfgdb.get_table('INTERFACE')
8383
assert result.output == expected_output_unbind
84-
84+
8585
expected_output_unbind = "Interface Loopback0 IP disabled and address(es) removed due to unbinding VRF.\n"
8686

8787
result = runner.invoke(config.config.commands["interface"].commands["vrf"].commands["unbind"], ["Loopback0"], obj=vrf_obj)
@@ -108,7 +108,7 @@ def test_vrf_bind_unbind(self):
108108
assert result.exit_code == 0
109109
assert 'PortChannel002' not in db.cfgdb.get_table('PORTCHANNEL_INTERFACE')
110110
assert result.output == expected_output_unbind
111-
111+
112112
vrf_obj = {'config_db':db.cfgdb, 'namespace':DEFAULT_NAMESPACE}
113113
state_db = SonicV2Connector(use_unix_socket_path=True, namespace='')
114114
state_db.connect(state_db.STATE_DB, False)
@@ -203,7 +203,7 @@ def test_vrf_bind_unbind(self):
203203
Loopback0
204204
Po0002.101
205205
"""
206-
206+
207207
result = runner.invoke(show.cli.commands['vrf'], [], obj=db)
208208
dbconnector.dedicated_dbs = {}
209209
assert result.exit_code == 0
@@ -213,24 +213,24 @@ def test_vrf_add_del(self):
213213
runner = CliRunner()
214214
db = Db()
215215
vrf_obj = {'config_db':db.cfgdb, 'namespace':db.db.namespace}
216-
216+
217217
result = runner.invoke(config.config.commands["vrf"].commands["add"], ["Vrf100"], obj=vrf_obj)
218218
assert ('Vrf100') in db.cfgdb.get_table('VRF')
219219
assert result.exit_code == 0
220-
220+
221221
result = runner.invoke(config.config.commands["vrf"].commands["add"], ["Vrf1"], obj=vrf_obj)
222222
assert "VRF Vrf1 already exists!" in result.output
223223
assert ('Vrf1') in db.cfgdb.get_table('VRF')
224224
assert result.exit_code != 0
225-
225+
226226
expected_output_del = "VRF Vrf1 deleted and all associated IP addresses removed.\n"
227227
result = runner.invoke(config.config.commands["vrf"].commands["del"], ["Vrf1"], obj=vrf_obj)
228228
assert result.exit_code == 0
229229
assert result.output == expected_output_del
230230
assert ('Vrf1') not in db.cfgdb.get_table('VRF')
231231

232232
result = runner.invoke(config.config.commands["vrf"].commands["del"], ["Vrf200"], obj=vrf_obj)
233-
assert result.exit_code != 0
233+
assert result.exit_code != 0
234234
assert ('Vrf200') not in db.cfgdb.get_table('VRF')
235235
assert "VRF Vrf200 does not exist!" in result.output
236236

@@ -245,25 +245,33 @@ def test_invalid_vrf_name(self):
245245
assert result.exit_code != 0
246246
assert ('vrf-blue') not in db.cfgdb.get_table('VRF')
247247
assert expected_output in result.output
248-
248+
249249
result = runner.invoke(config.config.commands["vrf"].commands["add"], ["VRF2"], obj=obj)
250250
assert result.exit_code != 0
251251
assert ('VRF2') not in db.cfgdb.get_table('VRF')
252252
assert expected_output in result.output
253-
253+
254254
result = runner.invoke(config.config.commands["vrf"].commands["add"], ["VrF10"], obj=obj)
255255
assert result.exit_code != 0
256256
assert ('VrF10') not in db.cfgdb.get_table('VRF')
257257
assert expected_output in result.output
258-
258+
259259
result = runner.invoke(config.config.commands["vrf"].commands["del"], ["vrf-blue"], obj=obj)
260260
assert result.exit_code != 0
261261
assert expected_output in result.output
262-
262+
263263
result = runner.invoke(config.config.commands["vrf"].commands["del"], ["VRF2"], obj=obj)
264264
assert result.exit_code != 0
265265
assert expected_output in result.output
266-
266+
267267
result = runner.invoke(config.config.commands["vrf"].commands["del"], ["VrF10"], obj=obj)
268268
assert result.exit_code != 0
269269
assert expected_output in result.output
270+
271+
expected_output = """\
272+
Error: 'vrf_name' length should not exceed 15 characters
273+
"""
274+
result = runner.invoke(config.config.commands["vrf"].commands["add"], ["VrfNameTooLong!!!"], obj=obj)
275+
assert result.exit_code != 0
276+
assert ('VrfNameTooLong!!!') not in db.cfgdb.get_table('VRF')
277+
assert expected_output in result.output

tests/vxlan_test.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ def test_show_vxlan_remotevni_specific_cnt(self):
249249
print(result.output)
250250
assert result.exit_code == 0
251251
assert result.output == show_vxlan_remotevni_specific_cnt_output
252-
252+
253253
@patch("validated_config_db_connector.device_info.is_yang_config_validation_enabled", mock.Mock(return_value=True))
254254
@patch("config.validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry", mock.Mock(side_effect=ValueError))
255255
@patch("config.main.ConfigDBConnector.get_entry", mock.Mock(return_value="Vlan Data"))
@@ -371,6 +371,19 @@ def test_config_vxlan_add(self):
371371
assert result.exit_code == 0
372372
assert result.output == show_vxlan_vlanvnimap_output
373373

374+
def test_config_vxlan_add_invalid_name(self):
375+
runner = CliRunner()
376+
db = Db()
377+
378+
result = runner.invoke(config.config.commands["vxlan"].commands["add"], ["vtep111111111111", "1.1.1.1"], obj=db)
379+
print(result.exit_code)
380+
print(result.output)
381+
expected_output = """\
382+
Error: 'vxlan_name' length should not exceed 15 characters
383+
"""
384+
assert expected_output in result.output
385+
assert result.exit_code != 0
386+
374387
def test_config_vxlan_del(self):
375388
dbconnector.dedicated_dbs['CONFIG_DB'] = os.path.join(mock_db_path, 'config_db')
376389
db = Db()

0 commit comments

Comments
 (0)