Skip to content

Commit 2e0aa4f

Browse files
pavel-shirshovPavel Shirshov
and
Pavel Shirshov
authored
[pfx_filter]: Add a prefix mask by default in pfx_filter, when there is no one (#4860)
If some table with a list of tuples (interface name, ip prefix) has ip prefixes without a mask length, it will cause issues in SONiC. For example quagga and frr will treat ipv4 address without a mask, so "10.20.30.40" address will be treated as "10.0.0.0/8", which is dangerous. The fix here is that when pfx_filter get a tuple (interface name, ip prefix), where the ip prefix doesn't have prefix mask length, add a mask by default: "/32 for ipv4 addresses, /128 for ipv6 addresses". Co-authored-by: Pavel Shirshov <[email protected]>
1 parent e66cb47 commit 2e0aa4f

File tree

7 files changed

+196
-2
lines changed

7 files changed

+196
-2
lines changed

src/sonic-bgpcfgd/app/template.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import jinja2
55
import netaddr
66

7+
from .log import log_err
78

89
class TemplateFabric(object):
910
""" Fabric for rendering jinja2 templates """
@@ -94,5 +95,14 @@ def pfx_filter(value):
9495
for key, val in value.items():
9596
if not isinstance(key, tuple):
9697
continue
97-
table[key] = val
98+
intf, ip_address = key
99+
if '/' not in ip_address:
100+
if TemplateFabric.is_ipv4(ip_address):
101+
table[(intf, "%s/32" % ip_address)] = val
102+
elif TemplateFabric.is_ipv6(ip_address):
103+
table[(intf, "%s/128" % ip_address)] = val
104+
else:
105+
log_err("'%s' is invalid ip address" % ip_address)
106+
else:
107+
table[key] = val
98108
return table
+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from app.template import TemplateFabric
2+
from collections import OrderedDict
3+
import pytest
4+
5+
6+
def test_pfx_filter_none():
7+
res = TemplateFabric.pfx_filter(None)
8+
assert isinstance(res, OrderedDict) and len(res) == 0
9+
10+
def test_pfx_filter_empty_tuple():
11+
res = TemplateFabric.pfx_filter(())
12+
assert isinstance(res, OrderedDict) and len(res) == 0
13+
14+
def test_pfx_filter_empty_list():
15+
res = TemplateFabric.pfx_filter([])
16+
assert isinstance(res, OrderedDict) and len(res) == 0
17+
18+
def test_pfx_filter_empty_dict():
19+
res = TemplateFabric.pfx_filter({})
20+
assert isinstance(res, OrderedDict) and len(res) == 0
21+
22+
def test_pfx_filter_strings():
23+
src = {
24+
'Loopback0': {},
25+
'Loopback1': {},
26+
}
27+
expected = OrderedDict([])
28+
res = TemplateFabric.pfx_filter(src)
29+
assert res == expected
30+
31+
def test_pfx_filter_mixed_keys():
32+
src = {
33+
'Loopback0': {},
34+
('Loopback0', '11.11.11.11/32'): {},
35+
'Loopback1': {},
36+
('Loopback1', '55.55.55.55/32'): {},
37+
}
38+
expected = OrderedDict(
39+
[
40+
(('Loopback1', '55.55.55.55/32'), {}),
41+
(('Loopback0', '11.11.11.11/32'), {}),
42+
]
43+
)
44+
res = TemplateFabric.pfx_filter(src)
45+
assert res == expected
46+
47+
48+
def test_pfx_filter_pfx_v4_w_mask():
49+
src = {
50+
('Loopback0', '11.11.11.11/32'): {},
51+
('Loopback1', '55.55.55.55/32'): {},
52+
}
53+
expected = OrderedDict(
54+
[
55+
(('Loopback1', '55.55.55.55/32'), {}),
56+
(('Loopback0', '11.11.11.11/32'), {}),
57+
]
58+
)
59+
res = TemplateFabric.pfx_filter(src)
60+
assert res == expected
61+
62+
def test_pfx_filter_pfx_v6_w_mask():
63+
src = {
64+
('Loopback0', 'fc00::/128'): {},
65+
('Loopback1', 'fc00::1/128'): {},
66+
}
67+
expected = OrderedDict(
68+
[
69+
(('Loopback0', 'fc00::/128'), {}),
70+
(('Loopback1', 'fc00::1/128'), {}),
71+
]
72+
)
73+
res = TemplateFabric.pfx_filter(src)
74+
assert res == expected
75+
76+
def test_pfx_filter_pfx_v4_no_mask():
77+
src = {
78+
('Loopback0', '11.11.11.11'): {},
79+
('Loopback1', '55.55.55.55'): {},
80+
}
81+
expected = OrderedDict(
82+
[
83+
(('Loopback1', '55.55.55.55/32'), {}),
84+
(('Loopback0', '11.11.11.11/32'), {}),
85+
]
86+
)
87+
res = TemplateFabric.pfx_filter(src)
88+
assert res == expected
89+
90+
def test_pfx_filter_pfx_v6_no_mask():
91+
src = {
92+
('Loopback0', 'fc00::'): {},
93+
('Loopback1', 'fc00::1'): {},
94+
}
95+
expected = OrderedDict(
96+
[
97+
(('Loopback0', 'fc00::/128'), {}),
98+
(('Loopback1', 'fc00::1/128'), {}),
99+
]
100+
)
101+
res = TemplateFabric.pfx_filter(src)
102+
assert res == expected
103+
104+
105+
def test_pfx_filter_pfx_comprehensive():
106+
src = {
107+
'Loopback0': {},
108+
('Loopback0', 'fc00::'): {},
109+
'Loopback1': {},
110+
('Loopback1', 'fc00::1/128'): {},
111+
('Loopback2', '11.11.11.11/32'): {},
112+
('Loopback3', '55.55.55.55'): {},
113+
'Loopback2': {},
114+
'Loopback3': {},
115+
('Loopback5', '22.22.22.1/24'): {},
116+
('Loopback6', 'fc00::55/64'): {},
117+
}
118+
expected = OrderedDict(
119+
[
120+
(('Loopback1', 'fc00::1/128'), {}),
121+
(('Loopback3', '55.55.55.55/32'), {}),
122+
(('Loopback6', 'fc00::55/64'), {}),
123+
(('Loopback2', '11.11.11.11/32'), {}),
124+
(('Loopback0', 'fc00::/128'), {}),
125+
(('Loopback5', '22.22.22.1/24'), {}),
126+
]
127+
)
128+
res = TemplateFabric.pfx_filter(src)
129+
assert res == expected
130+
131+
@pytest.fixture
132+
def test_pfx_filter_wrong_ip(caplog):
133+
src = {
134+
('Loopback0', 'wrong_ip'): {},
135+
}
136+
res = TemplateFabric.pfx_filter(src)
137+
assert "'wrong_ip' is invalid ip address" in caplog.text
138+
assert isinstance(res, OrderedDict) and len(res) == 0
139+

src/sonic-config-engine/sonic-cfggen

+11-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,17 @@ def pfx_filter(value):
113113
for key,val in value.items():
114114
if not isinstance(key, tuple):
115115
continue
116-
table[key] = val
116+
intf, ip_address = key
117+
if '/' not in ip_address:
118+
if is_ipv4(ip_address):
119+
new_ip_address = "%s/32" % ip_address
120+
elif is_ipv6(ip_address):
121+
new_ip_address = "%s/128" % ip_address
122+
else:
123+
raise ValueError("'%s' is invalid ip address" % ip_address)
124+
table[(intf, new_ip_address)] = val
125+
else:
126+
table[key] = val
117127
return table
118128

119129
def ip_network(value):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"VLAN_INTERFACE": {
3+
"Vlan1": {},
4+
"Vlan1|1.1.1.1/32": {},
5+
"Vlan2": {},
6+
"Vlan2|2.2.2.2": {},
7+
"Vlan3": {},
8+
"Vlan3|fc00::1": {},
9+
"Vlan4": {},
10+
"Vlan4|fc00::2/64": {}
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"Vlan1"="1.1.1.1/32"
2+
"Vlan2"="2.2.2.2/32"
3+
"Vlan3"="fc00::1/128"
4+
"Vlan4"="fc00::2/64"
5+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{% for intf, addr in VLAN_INTERFACE|pfx_filter %}
2+
"{{ intf }}"="{{ addr }}"
3+
{% endfor %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from unittest import TestCase
2+
import subprocess
3+
4+
class TestPfxFilter(TestCase):
5+
def test_comprehensive(self):
6+
# Generate output
7+
data_dir = "tests/data/pfx_filter"
8+
cmd = "./sonic-cfggen -j %s/param_1.json -t %s/tmpl_1.txt.j2 > /tmp/result_1.txt" % (data_dir, data_dir)
9+
subprocess.check_output(cmd, shell=True)
10+
# Compare outputs
11+
cmd = "diff -u tests/data/pfx_filter/result_1.txt /tmp/result_1.txt"
12+
try:
13+
res = subprocess.check_output(cmd, shell=True)
14+
except subprocess.CalledProcessError as e:
15+
assert False, "Wrong output. return code: %d, Diff: %s" % (e.returncode, e.output)

0 commit comments

Comments
 (0)