ifstate 1.13.7__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ifstate/ifstate.py +15 -8
- {ifstate-1.13.7.dist-info → ifstate-2.0.0.dist-info}/METADATA +2 -2
- ifstate-2.0.0.dist-info/RECORD +39 -0
- libifstate/__init__.py +147 -78
- libifstate/address/__init__.py +1 -1
- libifstate/exception.py +6 -12
- libifstate/hook/__init__.py +195 -0
- libifstate/hook/wrapper.sh +50 -0
- libifstate/link/base.py +29 -34
- libifstate/link/dsa.py +1 -1
- libifstate/link/physical.py +3 -3
- libifstate/link/tun.py +3 -3
- libifstate/link/veth.py +2 -2
- libifstate/log.py +7 -4
- libifstate/netns/__init__.py +44 -6
- libifstate/parser/__init__.py +1 -1
- libifstate/parser/base.py +131 -85
- libifstate/routing/__init__.py +63 -17
- libifstate/schema/2/ifstate.conf.schema.json +4442 -0
- libifstate/sysctl/__init__.py +20 -2
- libifstate/tc/__init__.py +1 -1
- libifstate/util.py +153 -147
- libifstate/wireguard/__init__.py +82 -21
- ifstate-1.13.7.dist-info/RECORD +0 -37
- schema/ifstate.conf.schema.json +0 -4259
- {ifstate-1.13.7.dist-info → ifstate-2.0.0.dist-info}/WHEEL +0 -0
- {ifstate-1.13.7.dist-info → ifstate-2.0.0.dist-info}/entry_points.txt +0 -0
- {ifstate-1.13.7.dist-info → ifstate-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {ifstate-1.13.7.dist-info → ifstate-2.0.0.dist-info}/top_level.txt +0 -0
libifstate/parser/base.py
CHANGED
@@ -1,12 +1,26 @@
|
|
1
|
+
from libifstate.hook import HOOK_DIR
|
1
2
|
from libifstate.util import logger
|
2
3
|
from libifstate.exception import ParserValidationError
|
3
4
|
from abc import ABC, abstractmethod
|
4
5
|
from copy import deepcopy
|
6
|
+
import os
|
7
|
+
|
8
|
+
|
9
|
+
def get_available_hooks():
|
10
|
+
# check if there is a hooks dir before trying to scan it
|
11
|
+
if not os.path.isdir(HOOK_DIR):
|
12
|
+
return {}
|
13
|
+
|
14
|
+
hooks = {}
|
15
|
+
with os.scandir(HOOK_DIR) as it:
|
16
|
+
for dentry in it:
|
17
|
+
if dentry.is_file():
|
18
|
+
hooks[dentry.name] = {}
|
19
|
+
return hooks
|
5
20
|
|
6
21
|
|
7
22
|
class Parser(ABC):
|
8
23
|
_default_lo_link = {
|
9
|
-
'name': 'lo',
|
10
24
|
'addresses': [
|
11
25
|
'127.0.0.1/8',
|
12
26
|
'::1/128',
|
@@ -18,79 +32,96 @@ class Parser(ABC):
|
|
18
32
|
}
|
19
33
|
}
|
20
34
|
_default_ifstates = {
|
21
|
-
'
|
22
|
-
'
|
23
|
-
'
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
'
|
39
|
-
{
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
{'proto': 42},
|
52
|
-
{'proto': 186},
|
53
|
-
{'proto': 187},
|
54
|
-
{'proto': 188},
|
55
|
-
{'proto': 189},
|
56
|
-
{'proto': 192},
|
57
|
-
{'to': 'ff00::/8'},
|
58
|
-
],
|
59
|
-
'rules_builtin': [
|
60
|
-
{'proto': 1},
|
61
|
-
{'proto': 2},
|
62
|
-
{'proto': 8},
|
63
|
-
{'proto': 9},
|
64
|
-
{'proto': 10},
|
65
|
-
{'proto': 11},
|
66
|
-
{'proto': 12},
|
67
|
-
{'proto': 13},
|
68
|
-
{'proto': 14},
|
69
|
-
{'proto': 15},
|
70
|
-
{'proto': 16},
|
71
|
-
{'proto': 18},
|
72
|
-
{'proto': 42},
|
73
|
-
{'proto': 186},
|
74
|
-
{'proto': 187},
|
75
|
-
{'proto': 188},
|
76
|
-
{'proto': 189},
|
77
|
-
{'proto': 192},
|
78
|
-
],
|
79
|
-
},
|
80
|
-
'cshaper': {
|
81
|
-
'default': {
|
82
|
-
'egress_qdisc': {
|
83
|
-
'kind': 'cake',
|
84
|
-
'handle': '1:',
|
85
|
-
},
|
86
|
-
'ingress_qdisc': {
|
87
|
-
'kind': 'cake',
|
88
|
-
'handle': '1:',
|
89
|
-
},
|
90
|
-
'ingress_ifname': {
|
91
|
-
'search': r'^\D{1,3}',
|
92
|
-
'replace': 'ifb',
|
35
|
+
'parameters': {
|
36
|
+
'cshaper': {
|
37
|
+
'default': {
|
38
|
+
'egress_qdisc': {
|
39
|
+
'kind': 'cake',
|
40
|
+
'handle': '1:',
|
41
|
+
},
|
42
|
+
'ingress_qdisc': {
|
43
|
+
'kind': 'cake',
|
44
|
+
'handle': '1:',
|
45
|
+
},
|
46
|
+
'ingress_ifname': {
|
47
|
+
'search': r'^\D{1,3}',
|
48
|
+
'replace': 'ifb',
|
49
|
+
}
|
50
|
+
}
|
51
|
+
},
|
52
|
+
'defaults_builtin': [
|
53
|
+
{
|
54
|
+
'match': [
|
55
|
+
{'ifname': ''}
|
56
|
+
],
|
57
|
+
'clear_addresses': True,
|
58
|
+
'clear_fdb': True,
|
59
|
+
'clear_neighbours': True,
|
60
|
+
'clear_tc': True,
|
61
|
+
'link': {
|
62
|
+
'state': 'down',
|
63
|
+
'master': None
|
64
|
+
}
|
93
65
|
}
|
66
|
+
],
|
67
|
+
'ignore': {
|
68
|
+
'ipaddr_builtin': [
|
69
|
+
'fe80::/10'
|
70
|
+
],
|
71
|
+
'ipaddr_dynamic': True,
|
72
|
+
'ifname_builtin': [
|
73
|
+
r'^br-[\da-f]{12}',
|
74
|
+
r'^docker\d+',
|
75
|
+
r'^ppp\d+$',
|
76
|
+
r'^veth',
|
77
|
+
r'^virbr\d+',
|
78
|
+
r'^vrrp\d*\.\d+$'
|
79
|
+
],
|
80
|
+
'fdb_builtin': [
|
81
|
+
r'^33:33:',
|
82
|
+
r'^01:00:5e:'
|
83
|
+
],
|
84
|
+
'routes_builtin': [
|
85
|
+
{'proto': 1},
|
86
|
+
{'proto': 2},
|
87
|
+
{'proto': 8},
|
88
|
+
{'proto': 9},
|
89
|
+
{'proto': 10},
|
90
|
+
{'proto': 11},
|
91
|
+
{'proto': 12},
|
92
|
+
{'proto': 13},
|
93
|
+
{'proto': 14},
|
94
|
+
{'proto': 15},
|
95
|
+
{'proto': 16},
|
96
|
+
{'proto': 18},
|
97
|
+
{'proto': 42},
|
98
|
+
{'proto': 186},
|
99
|
+
{'proto': 187},
|
100
|
+
{'proto': 188},
|
101
|
+
{'proto': 189},
|
102
|
+
{'proto': 192},
|
103
|
+
{'to': 'ff00::/8'},
|
104
|
+
],
|
105
|
+
'rules_builtin': [
|
106
|
+
{'proto': 1},
|
107
|
+
{'proto': 2},
|
108
|
+
{'proto': 8},
|
109
|
+
{'proto': 9},
|
110
|
+
{'proto': 10},
|
111
|
+
{'proto': 11},
|
112
|
+
{'proto': 12},
|
113
|
+
{'proto': 13},
|
114
|
+
{'proto': 14},
|
115
|
+
{'proto': 15},
|
116
|
+
{'proto': 16},
|
117
|
+
{'proto': 18},
|
118
|
+
{'proto': 42},
|
119
|
+
{'proto': 186},
|
120
|
+
{'proto': 187},
|
121
|
+
{'proto': 188},
|
122
|
+
{'proto': 189},
|
123
|
+
{'proto': 192},
|
124
|
+
],
|
94
125
|
}
|
95
126
|
}
|
96
127
|
}
|
@@ -113,12 +144,12 @@ class Parser(ABC):
|
|
113
144
|
return a
|
114
145
|
|
115
146
|
def _update_lo(self, cfg):
|
116
|
-
if 'interfaces' in cfg:
|
117
|
-
lo
|
118
|
-
|
119
|
-
|
147
|
+
if 'interfaces' in cfg and isinstance(cfg['interfaces'], dict):
|
148
|
+
if 'lo' in cfg['interfaces']:
|
149
|
+
cfg['interfaces']['lo'] = self.merge(
|
150
|
+
deepcopy(Parser._default_lo_link), cfg['interfaces']['lo'])
|
120
151
|
else:
|
121
|
-
cfg['interfaces']
|
152
|
+
cfg['interfaces']['lo'] = Parser._default_lo_link
|
122
153
|
|
123
154
|
def config(self):
|
124
155
|
# merge builtin defaults with config
|
@@ -126,7 +157,7 @@ class Parser(ABC):
|
|
126
157
|
|
127
158
|
# 'ignore' should still be an object
|
128
159
|
try:
|
129
|
-
iter(cfg["ignore"])
|
160
|
+
iter(cfg["parameters"]["ignore"])
|
130
161
|
except TypeError:
|
131
162
|
raise ParserValidationError("$.ignore: is not of type 'object'")
|
132
163
|
|
@@ -135,19 +166,34 @@ class Parser(ABC):
|
|
135
166
|
for namespace in cfg.get('namespaces', {}):
|
136
167
|
self._update_lo(cfg['namespaces'][namespace])
|
137
168
|
|
169
|
+
# add available hooks
|
170
|
+
if "hooks" in cfg["parameters"]:
|
171
|
+
hooks = get_available_hooks()
|
172
|
+
hooks.update(cfg["parameters"]["hooks"])
|
173
|
+
cfg["parameters"]["hooks"] = hooks
|
174
|
+
else:
|
175
|
+
cfg["parameters"]["hooks"] = get_available_hooks()
|
176
|
+
|
138
177
|
# merge builtin defaults
|
139
|
-
|
178
|
+
if "defaults" in cfg["parameters"]:
|
179
|
+
cfg["parameters"]["defaults"].extend(cfg["parameters"]["defaults_builtin"])
|
180
|
+
else:
|
181
|
+
cfg["parameters"]["defaults"] = cfg["parameters"]["defaults_builtin"]
|
182
|
+
del cfg["parameters"]["defaults_builtin"]
|
183
|
+
|
184
|
+
# merge builtin ignore lists
|
185
|
+
for k in list(cfg["parameters"]["ignore"]):
|
140
186
|
if k.endswith("_builtin"):
|
141
187
|
n = k[:-8]
|
142
|
-
if n in cfg["ignore"]:
|
188
|
+
if n in cfg["parameters"]["ignore"]:
|
143
189
|
try:
|
144
|
-
cfg["ignore"][n] += cfg["ignore"][k]
|
190
|
+
cfg["parameters"]["ignore"][n] += cfg["parameters"]["ignore"][k]
|
145
191
|
except TypeError:
|
146
192
|
raise ParserValidationError("$.ignore.{}: is not of type '{}'".format(
|
147
|
-
n, type(cfg["ignore"][k]).__name__))
|
193
|
+
n, type(cfg["parameters"]["ignore"][k]).__name__))
|
148
194
|
else:
|
149
|
-
cfg["ignore"][n] = cfg["ignore"][k]
|
150
|
-
del(cfg["ignore"][k])
|
195
|
+
cfg["parameters"]["ignore"][n] = cfg["parameters"]["ignore"][k]
|
196
|
+
del (cfg["parameters"]["ignore"][k])
|
151
197
|
|
152
198
|
return cfg
|
153
199
|
|
libifstate/routing/__init__.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from libifstate.util import logger, IfStateLogging
|
2
2
|
from libifstate.exception import RouteDuplicate, netlinkerror_classes
|
3
|
-
from ipaddress import ip_address, ip_network, IPv6Network
|
3
|
+
from ipaddress import ip_address, ip_interface, ip_network, IPv6Network
|
4
4
|
from pyroute2.netlink.rtnl.fibmsg import FR_ACT_VALUES
|
5
5
|
from pyroute2.netlink.rtnl import rt_type
|
6
6
|
import collections.abc
|
@@ -202,7 +202,7 @@ class RTLookup():
|
|
202
202
|
self.str2id = {}
|
203
203
|
self.id2str = {}
|
204
204
|
|
205
|
-
for basedir in ['/usr/share/iproute2', '/
|
205
|
+
for basedir in ['/usr/share/iproute2', '/etc/iproute2']:
|
206
206
|
fn = os.path.join(basedir, name)
|
207
207
|
try:
|
208
208
|
with open(fn, 'r') as fp:
|
@@ -284,8 +284,8 @@ class Tables(collections.abc.Mapping):
|
|
284
284
|
idx = next(
|
285
285
|
iter(self.netns.ipr.link_lookup(ifname=ignore[attr])), None)
|
286
286
|
if idx is None:
|
287
|
-
logger.debug("{} '{}' is unknown".format(attr, ignore[attr]))
|
288
|
-
|
287
|
+
logger.debug("{} '{}' is unknown, skipping ignore".format(attr, ignore[attr]))
|
288
|
+
continue
|
289
289
|
else:
|
290
290
|
ignore[attr] = idx
|
291
291
|
parsed.append(ignore)
|
@@ -321,7 +321,7 @@ class Tables(collections.abc.Mapping):
|
|
321
321
|
continue
|
322
322
|
|
323
323
|
if route['dst_len'] > 0:
|
324
|
-
dst =
|
324
|
+
dst = ip_interface(
|
325
325
|
'{}/{}'.format(route.get_attr('RTA_DST'), route['dst_len'])).with_prefixlen
|
326
326
|
elif route['family'] == AF_INET:
|
327
327
|
dst = ip_network('0.0.0.0/0').with_prefixlen
|
@@ -546,13 +546,18 @@ class Rules():
|
|
546
546
|
'tos': rule['tos'],
|
547
547
|
}
|
548
548
|
|
549
|
-
if
|
550
|
-
|
551
|
-
|
549
|
+
# add source and destination ip spaces even if they are not given by pyroute2
|
550
|
+
# as the user might specify something like "from: ::/0" explicitly which would not
|
551
|
+
# be present on a krule (because default values are ommited by pyroute2) and therefore
|
552
|
+
# would not match, even though it is the same rule
|
553
|
+
# if the user doesn't specify 'from' or 'to', these fields are just ignored because
|
554
|
+
# rule_matches is instructed to only check fields that are present on the config rule,
|
555
|
+
# meaning that these just get ignored
|
556
|
+
ru['dst'] = rule.get_attr('FRA_DST', default=("::" if rule['family'] == AF_INET6 else "0.0.0.0"))
|
557
|
+
ru['dst_len'] = rule['dst_len']
|
552
558
|
|
553
|
-
if rule['
|
554
|
-
|
555
|
-
ru['src_len'] = rule['src_len']
|
559
|
+
ru['src'] = rule.get_attr('FRA_SRC', default=("::" if rule['family'] == AF_INET6 else "0.0.0.0"))
|
560
|
+
ru['src_len'] = rule['src_len']
|
556
561
|
|
557
562
|
for field in ['iifname', 'oifname', 'fwmark', 'ip_proto']:
|
558
563
|
value = rule.get_attr('FRA_{}'.format(field.upper()))
|
@@ -593,7 +598,7 @@ class Rules():
|
|
593
598
|
del rule['protocol']
|
594
599
|
|
595
600
|
if 'src_len' in rule and rule['src_len'] > 0:
|
596
|
-
rule['from'] =
|
601
|
+
rule['from'] = ip_interface(
|
597
602
|
'{}/{}'.format(rule['src'], rule['src_len'])).with_prefixlen
|
598
603
|
|
599
604
|
for key in ['src', 'src_len', 'tos', 'protocol']:
|
@@ -609,32 +614,51 @@ class Rules():
|
|
609
614
|
|
610
615
|
# get rules that are currently in the kernels list
|
611
616
|
krules = self.kernel_rules()
|
617
|
+
|
618
|
+
# loop over all rules that are wanted by the config
|
612
619
|
for rule in self.rules:
|
613
620
|
log_str = '#{}'.format(rule['priority'])
|
621
|
+
|
622
|
+
if rule.get('family', None) == AF_INET6:
|
623
|
+
log_str += " (inet6)"
|
624
|
+
elif rule.get('family', None) == AF_INET:
|
625
|
+
log_str += " (inet)"
|
626
|
+
|
614
627
|
if self.netns.netns != None:
|
615
628
|
log_str += "[netns={}]".format(self.netns.netns)
|
616
629
|
|
630
|
+
vrrp_matched = vrrp_match(rule, by_vrrp, vrrp_type, vrrp_name, vrrp_state)
|
631
|
+
# 'found' indicates whether the rule wanted by the config was found
|
632
|
+
# in the kernels rules. if not, it is going to be added below
|
617
633
|
found = False
|
618
|
-
matched = vrrp_match(rule, by_vrrp, vrrp_type, vrrp_name, vrrp_state)
|
619
634
|
for i, krule in enumerate(krules):
|
620
635
|
if rule_matches(
|
621
636
|
rule,
|
622
637
|
krule,
|
623
638
|
# skip helper attrs like _vrrp
|
639
|
+
# note that rule_matches it told to only check fields that are
|
640
|
+
# present on the config rule, so additional ones from the krule
|
641
|
+
# just get ignored
|
624
642
|
[key for key in rule.keys() if key[0] != '_']
|
625
643
|
):
|
626
644
|
found = True
|
627
645
|
|
628
|
-
#
|
629
|
-
|
646
|
+
# we've just confirmed that this krule matches a rule
|
647
|
+
# that is wanted by the config so we delete it from the
|
648
|
+
# krules because any that remain in krule are unwanted
|
649
|
+
# and get deleted below
|
650
|
+
# EXCEPT if vrrp tells us to leave it be
|
651
|
+
if vrrp_matched != VRRP_MATCH_DISABLE:
|
630
652
|
del krules[i]
|
631
653
|
|
632
654
|
break
|
633
655
|
|
634
|
-
if
|
656
|
+
if vrrp_matched not in [VRRP_MATCH_IGNORE, VRRP_MATCH_DISABLE]:
|
635
657
|
if found:
|
658
|
+
# rule already exists
|
636
659
|
logger.log_ok(log_str)
|
637
660
|
else:
|
661
|
+
# rule doesn't exist, add it
|
638
662
|
logger.log_add(log_str)
|
639
663
|
|
640
664
|
logger.debug("ip rule add: {}".format(
|
@@ -647,7 +671,12 @@ class Rules():
|
|
647
671
|
raise
|
648
672
|
logger.warning('rule setup failed: {}'.format(err.args[1]))
|
649
673
|
|
674
|
+
# at this point all wanted rules have been deleted from the krules list,
|
675
|
+
# leaving the rest to be deleted here (see 'del krules[i]' above)
|
650
676
|
for rule in krules:
|
677
|
+
|
678
|
+
# if this rule falls into the ignored ones (like the ones set by the kernel itself by default)
|
679
|
+
# we will ignore it and continue
|
651
680
|
ignore = False
|
652
681
|
for irule in ignores:
|
653
682
|
if 'proto' in irule:
|
@@ -659,9 +688,26 @@ class Rules():
|
|
659
688
|
if ignore:
|
660
689
|
continue
|
661
690
|
|
662
|
-
|
691
|
+
log_str = '#{}'.format(rule['priority'])
|
692
|
+
|
693
|
+
if rule.get('family', None) == AF_INET6:
|
694
|
+
log_str += " (inet6)"
|
695
|
+
elif rule.get('family', None) == AF_INET:
|
696
|
+
log_str += " (inet)"
|
697
|
+
|
698
|
+
logger.log_del(log_str)
|
699
|
+
|
663
700
|
try:
|
664
701
|
if do_apply:
|
702
|
+
# for some reason pyroute2 raises an exception when
|
703
|
+
# we pass an address like "0.0.0.0" or "::" with an
|
704
|
+
# 'src_len' or 'dst_len' of 0.
|
705
|
+
# using this as a workaround:
|
706
|
+
if rule['src_len'] == 0:
|
707
|
+
del rule['src']
|
708
|
+
if rule['dst_len'] == 0:
|
709
|
+
del rule['dst']
|
710
|
+
|
665
711
|
self.netns.ipr.rule('del', **rule)
|
666
712
|
except Exception as err:
|
667
713
|
if not isinstance(err, netlinkerror_classes):
|