ifstate 1.13.7__py3-none-any.whl → 2.0.0rc2__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.
- hooks/wrapper.sh +50 -0
- ifstate/ifstate.py +9 -2
- {ifstate-1.13.7.dist-info → ifstate-2.0.0rc2.dist-info}/METADATA +2 -2
- ifstate-2.0.0rc2.dist-info/RECORD +38 -0
- libifstate/__init__.py +97 -46
- libifstate/hook/__init__.py +195 -0
- libifstate/link/__init__.py +0 -1
- libifstate/link/base.py +23 -31
- libifstate/link/physical.py +2 -2
- libifstate/link/tun.py +2 -2
- libifstate/link/veth.py +2 -2
- libifstate/netns/__init__.py +40 -5
- libifstate/parser/base.py +107 -84
- libifstate/routing/__init__.py +62 -16
- libifstate/util.py +146 -147
- libifstate/wireguard/__init__.py +81 -21
- schema/2/ifstate.conf.schema.json +4398 -0
- ifstate-1.13.7.dist-info/RECORD +0 -37
- libifstate/link/dsa.py +0 -10
- schema/ifstate.conf.schema.json +0 -4259
- {ifstate-1.13.7.dist-info → ifstate-2.0.0rc2.dist-info}/WHEEL +0 -0
- {ifstate-1.13.7.dist-info → ifstate-2.0.0rc2.dist-info}/entry_points.txt +0 -0
- {ifstate-1.13.7.dist-info → ifstate-2.0.0rc2.dist-info}/licenses/LICENSE +0 -0
- {ifstate-1.13.7.dist-info → ifstate-2.0.0rc2.dist-info}/top_level.txt +0 -0
hooks/wrapper.sh
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# ifstate: wrapper to run hooks
|
4
|
+
|
5
|
+
# debugging
|
6
|
+
export IFS_VERBOSE=${verbose}
|
7
|
+
if [ "$$IFS_VERBOSE" = 1 ]; then
|
8
|
+
set -x
|
9
|
+
fi
|
10
|
+
|
11
|
+
# return codes
|
12
|
+
export IFS_RC_OK="${rc_ok}"
|
13
|
+
export IFS_RC_ERROR="${rc_error}"
|
14
|
+
export IFS_RC_STARTED="${rc_started}"
|
15
|
+
export IFS_RC_STOPPED="${rc_stopped}"
|
16
|
+
export IFS_RC_CHANGED="${rc_changed}"
|
17
|
+
|
18
|
+
# generic environment variables
|
19
|
+
export IFS_SCRIPT="${script}"
|
20
|
+
export IFS_RUNDIR="${rundir}"
|
21
|
+
|
22
|
+
export IFS_IFNAME="${ifname}"
|
23
|
+
export IFS_INDEX="${index}"
|
24
|
+
export IFS_NETNS="${netns}"
|
25
|
+
export IFS_VRF="${vrf}"
|
26
|
+
|
27
|
+
# hook arguments
|
28
|
+
${args}
|
29
|
+
|
30
|
+
# run hook NetNS and VRF aware
|
31
|
+
if [ -z "$$IFS_NETNS" ]; then
|
32
|
+
if [ -z "$$IFS_VRF" ]; then
|
33
|
+
# just exec the script
|
34
|
+
exec "$$IFS_SCRIPT" "$$@"
|
35
|
+
else
|
36
|
+
# exec in VRF
|
37
|
+
exec ip vrf exec "$$IFS_VRF" "$$IFS_SCRIPT" "$$@"
|
38
|
+
fi
|
39
|
+
else
|
40
|
+
if [ -z "$$IFS_VRF" ]; then
|
41
|
+
# exec in NetNS
|
42
|
+
exec ip netns exec "$$IFS_NETNS" "$$IFS_SCRIPT" "$$@"
|
43
|
+
else
|
44
|
+
# exec in NetNS->VRF
|
45
|
+
exec ip -n "$$IFS_NETNS" vrf exec "$$IFS_VRF" "$$IFS_SCRIPT" "$$@"
|
46
|
+
fi
|
47
|
+
fi
|
48
|
+
|
49
|
+
# somthing gone wrong
|
50
|
+
return $$IFS_RC_ERROR
|
ifstate/ifstate.py
CHANGED
@@ -21,6 +21,7 @@ import yaml
|
|
21
21
|
class Actions():
|
22
22
|
CHECK = "check"
|
23
23
|
APPLY = "apply"
|
24
|
+
IDENTIFY = "identify"
|
24
25
|
SHOW = "show"
|
25
26
|
SHOWALL = "showall"
|
26
27
|
VRRP = "vrrp"
|
@@ -31,6 +32,7 @@ class Actions():
|
|
31
32
|
ACTIONS_HELP = {
|
32
33
|
"CHECK" : "dry run update the network config",
|
33
34
|
"APPLY" : "update the network config",
|
35
|
+
"IDENTIFY" : "show interface attributes available for identification",
|
34
36
|
"SHOW" : "show running network config",
|
35
37
|
"SHOWALL" : "show running network config (more settings)",
|
36
38
|
"VRRP" : "run as keepalived notify script",
|
@@ -134,6 +136,8 @@ def main():
|
|
134
136
|
help="be more quiet, print only warnings and errors")
|
135
137
|
parser.add_argument("-s", "--soft-schema", action="store_true",
|
136
138
|
help="ignore schema validation errors, expect ifstatecli to trigger internal exceptions")
|
139
|
+
parser.add_argument("-S", "--show-secrets", action="store_true",
|
140
|
+
help="show secrets when dumping config")
|
137
141
|
parser.add_argument("-c", "--config", type=str,
|
138
142
|
default="/etc/ifstate/config.yml", help="configuration YaML filename")
|
139
143
|
subparsers = parser.add_subparsers(
|
@@ -180,13 +184,16 @@ def main():
|
|
180
184
|
|
181
185
|
ifslog = IfStateLogging(lvl, action=args.action)
|
182
186
|
|
183
|
-
if args.action in [Actions.SHOW, Actions.SHOWALL]:
|
187
|
+
if args.action in [Actions.IDENTIFY, Actions.SHOW, Actions.SHOWALL]:
|
184
188
|
# preserve dict order on python 3.7+
|
185
189
|
if sys.version_info >= (3, 7):
|
186
190
|
yaml.add_representer(
|
187
191
|
dict, lambda self, data: yaml.representer.SafeRepresenter.represent_dict(self, data.items()))
|
188
192
|
ifs = IfState()
|
189
|
-
|
193
|
+
if args.action == Actions.IDENTIFY:
|
194
|
+
print(yaml.dump(ifs.identify()))
|
195
|
+
else:
|
196
|
+
print(yaml.dump(ifs.show(args.action == Actions.SHOWALL, args.show_secrets)))
|
190
197
|
|
191
198
|
ifslog.quit()
|
192
199
|
exit(0)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ifstate
|
3
|
-
Version:
|
3
|
+
Version: 2.0.0rc2
|
4
4
|
Summary: Manage host interface settings in a declarative manner
|
5
5
|
Home-page: https://ifstate.net/
|
6
6
|
Author: Thomas Liske
|
@@ -9,7 +9,7 @@ License: GPL3+
|
|
9
9
|
Description-Content-Type: text/markdown
|
10
10
|
License-File: LICENSE
|
11
11
|
Requires-Dist: jsonschema
|
12
|
-
Requires-Dist: pyroute2
|
12
|
+
Requires-Dist: pyroute2!=0.9.3
|
13
13
|
Requires-Dist: pyyaml
|
14
14
|
Requires-Dist: setproctitle
|
15
15
|
Provides-Extra: shell
|
@@ -0,0 +1,38 @@
|
|
1
|
+
hooks/wrapper.sh,sha256=ipCmvcadJbXTT6oUR7BIhT5uglITnHfiAdm44vydZuw,1101
|
2
|
+
ifstate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
ifstate/ifstate.py,sha256=ioiM9xgVWdrpTpK_UxJdaJFlmpFM9R-2_d5pq9_Hepo,9272
|
4
|
+
ifstate/shell.py,sha256=7_JFpi4icr9MijynDzbb0v5mxhFsng6PCC4m3uQ255A,2177
|
5
|
+
ifstate/vrrp.py,sha256=FJ9b1eJseTtZFfknHU-xV68Qz7cPrRrc5PTcjUVj-fY,5953
|
6
|
+
ifstate-2.0.0rc2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
7
|
+
libifstate/__init__.py,sha256=REX94ISXtdyY24_cc26Od5D2xGnz2U8dYNtRRMjRu1M,33194
|
8
|
+
libifstate/exception.py,sha256=5i59BZdl56J_sNJbyU9n6uHuUNJEyDOO4FJ-neDn9Ds,2608
|
9
|
+
libifstate/log.py,sha256=XVoZdwdQoWsjuupFIuG6OP0OrBpXpx7oqyAaUhQ-nJk,4553
|
10
|
+
libifstate/util.py,sha256=1CGVgaEj7L4At9FPWcDQLwSe027XSKoxFHIdkS5spSA,11354
|
11
|
+
libifstate/address/__init__.py,sha256=zIGM7UWXSvoijHMD06DfS2CDtiHpiJlqDgJG7Dl1IPE,3222
|
12
|
+
libifstate/bpf/__init__.py,sha256=NVzaunTmJU2PbIQg9eWBMKpFgLh3EnD3ujNa7Yt6rNc,7699
|
13
|
+
libifstate/bpf/ctypes.py,sha256=kLZJHZlba09Vc-tbsJAcKpDwdoNO2IjlYVLCopawHmA,4274
|
14
|
+
libifstate/bpf/map.py,sha256=cLHNMvRBDNW2yVCEf3z242_oRdU0HqVbFEYVkKXng0w,10818
|
15
|
+
libifstate/brport/__init__.py,sha256=NzdA8F4hr2se1bXKNnyKZbvOFlCWBq_cdjwsL1H0Y-o,2964
|
16
|
+
libifstate/fdb/__init__.py,sha256=9dpL5n8ct3CaA-z8I6ZEkD3yL6yWJQeq3fpIe9pc2zw,6486
|
17
|
+
libifstate/hook/__init__.py,sha256=OPUyhrr-eT1UNp8ryYoJyvi4WWqL2Ql33szir673Td4,7379
|
18
|
+
libifstate/link/__init__.py,sha256=QZggoC-bIscqwVedqVycaSqS1CmXB3Bx3m2FZei8Q_4,115
|
19
|
+
libifstate/link/base.py,sha256=dazZVPRMyXms-ICiL7VOCUXwZnqNr1X6R2NjTlm2nEU,34384
|
20
|
+
libifstate/link/physical.py,sha256=ectyXVchCHqWZR8qsjAD9667wjQbGW1OUKLQheTUFys,594
|
21
|
+
libifstate/link/tun.py,sha256=SWkscSAgIk3DWFPWHo9Cmokhj88rO1_sR7Z0Qlg2xGA,993
|
22
|
+
libifstate/link/veth.py,sha256=Sv5WZMMsefYFz9I0BQSZyKytCQYxv0vjwAnB7FYHR1A,2283
|
23
|
+
libifstate/neighbour/__init__.py,sha256=FJJpbJvqnxvOEii6QDMYzW5jQDEbiEy71GQOEbqaS48,2463
|
24
|
+
libifstate/netns/__init__.py,sha256=4DWFsgkT84foKoE0ZIbCIAqezDZzOBf8Ka1OAZS_T9E,10164
|
25
|
+
libifstate/parser/__init__.py,sha256=byz1W0G7UewVc5FFie-ti3UZjGK3-75wHIaOeq0oySQ,88
|
26
|
+
libifstate/parser/base.py,sha256=EV7uJYdVke3a2ghkUZ6ZeaIeVQAPVS4rFnYhebXeQm0,5932
|
27
|
+
libifstate/parser/yaml.py,sha256=MC0kmwqt3P45z61fb_wfUqoj0iZyhFYkdPyr0UqMSZA,1415
|
28
|
+
libifstate/routing/__init__.py,sha256=EvuS0GC5s7up92DYwSGhkohqhCQKPJpvs-alY4WAQws,25326
|
29
|
+
libifstate/sysctl/__init__.py,sha256=EF52CdOOkVSUFR2t21A99KlG1-PjsD4qOiceQC4eI24,3074
|
30
|
+
libifstate/tc/__init__.py,sha256=inPdampCOIr_4oKNB3awqMkW0Eh4fpPh9jvSba6sPVg,12092
|
31
|
+
libifstate/wireguard/__init__.py,sha256=cpXqcZK_xNexwhz-1OqN1RQe21NaNVgF-adhXU6mv14,8797
|
32
|
+
libifstate/xdp/__init__.py,sha256=X1xhEIGng7R5d5F4KsChykT2g6H-XBRWbWABijoYDQA,7208
|
33
|
+
schema/2/ifstate.conf.schema.json,sha256=_ltHBK8iTy8xAhJapQIMmUavlXbDCcOI3R2EhjrMWa0,218618
|
34
|
+
ifstate-2.0.0rc2.dist-info/METADATA,sha256=Hgol7xinIWq2lp6D7PNFpgrZ9_lnqBgspEko0tzIaK4,1607
|
35
|
+
ifstate-2.0.0rc2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
36
|
+
ifstate-2.0.0rc2.dist-info/entry_points.txt,sha256=HF6jX7Uu_nF1Ly-J9uEPeiRapOxnM6LuHsb2y6Mt-k4,52
|
37
|
+
ifstate-2.0.0rc2.dist-info/top_level.txt,sha256=A7peI7aKBaM69fsiSPvMbL3rzTKZZr5qDxKC-pHMGdE,19
|
38
|
+
ifstate-2.0.0rc2.dist-info/RECORD,,
|
libifstate/__init__.py
CHANGED
@@ -2,6 +2,7 @@ from libifstate.exception import LinkDuplicate, NetnsUnknown
|
|
2
2
|
from libifstate.link.base import ethtool_path, Link
|
3
3
|
from libifstate.address import Addresses
|
4
4
|
from libifstate.fdb import FDB
|
5
|
+
from libifstate.hook import Hooks
|
5
6
|
from libifstate.neighbour import Neighbours
|
6
7
|
from libifstate.routing import Tables, Rules, RTLookups
|
7
8
|
from libifstate.parser import Parser
|
@@ -35,7 +36,7 @@ except ModuleNotFoundError:
|
|
35
36
|
pass
|
36
37
|
|
37
38
|
from libifstate.netns import NetNameSpace, prepare_netns, LinkRegistry, get_netns_instances
|
38
|
-
from libifstate.util import logger, IfStateLogging, LinkDependency
|
39
|
+
from libifstate.util import logger, root_iw, IfStateLogging, LinkDependency, IDENTIFY_LOOKUPS
|
39
40
|
from libifstate.exception import FeatureMissingError, LinkCircularLinked, LinkNoConfigFound, ParserValidationError
|
40
41
|
from ipaddress import ip_network, ip_interface
|
41
42
|
from jsonschema import validate, ValidationError, FormatChecker
|
@@ -48,8 +49,7 @@ import json
|
|
48
49
|
import errno
|
49
50
|
import logging
|
50
51
|
|
51
|
-
__version__ = "
|
52
|
-
|
52
|
+
__version__ = "2.0.0rc2"
|
53
53
|
|
54
54
|
class IfState():
|
55
55
|
def __init__(self):
|
@@ -61,6 +61,8 @@ class IfState():
|
|
61
61
|
self.ignore = {}
|
62
62
|
self.features = {
|
63
63
|
'brport': True,
|
64
|
+
'devicetree': os.access('/sys/firmware/devicetree', os.R_OK),
|
65
|
+
'iw': root_iw is not None,
|
64
66
|
'link': True,
|
65
67
|
'sysctl': os.access('/proc/sys/net', os.R_OK),
|
66
68
|
'ethtool': not ethtool_path is None,
|
@@ -81,7 +83,7 @@ class IfState():
|
|
81
83
|
def update(self, ifstates, soft_schema):
|
82
84
|
# check config schema
|
83
85
|
schema = json.loads(pkgutil.get_data(
|
84
|
-
"libifstate", "../schema/ifstate.conf.schema.json"))
|
86
|
+
"libifstate", "../schema/{}/ifstate.conf.schema.json".format(__version__.split('.')[0])))
|
85
87
|
try:
|
86
88
|
validate(ifstates, schema, format_checker=FormatChecker())
|
87
89
|
except ValidationError as ex:
|
@@ -104,10 +106,10 @@ class IfState():
|
|
104
106
|
|
105
107
|
# add interface defaults
|
106
108
|
if 'defaults' in ifstates:
|
107
|
-
self.defaults = ifstates['defaults']
|
109
|
+
self.defaults = ifstates['parameters']['defaults']
|
108
110
|
|
109
111
|
# add ignore list items
|
110
|
-
self.ignore.update(ifstates['ignore'])
|
112
|
+
self.ignore.update(ifstates['parameters']['ignore'])
|
111
113
|
self.ipaddr_ignore = set()
|
112
114
|
for ip in self.ignore.get('ipaddr', []):
|
113
115
|
self.ipaddr_ignore.add(ip_network(ip))
|
@@ -116,7 +118,10 @@ class IfState():
|
|
116
118
|
self.fdb_ignore = re.compile('|'.join(self.ignore.get('fdb')))
|
117
119
|
|
118
120
|
# save cshaper profiles
|
119
|
-
self.cshaper_profiles = ifstates['cshaper']
|
121
|
+
self.cshaper_profiles = ifstates['parameters']['cshaper']
|
122
|
+
|
123
|
+
# prepare hooks
|
124
|
+
self.hooks = Hooks(ifstates['parameters'].get('hooks', {}))
|
120
125
|
|
121
126
|
# build link registry over all named netns
|
122
127
|
self.link_registry = LinkRegistry(self.ignore.get('ifname', []), self.root_netns)
|
@@ -134,17 +139,15 @@ class IfState():
|
|
134
139
|
self._update(self.namespaces[netns_name], netns_ifstates)
|
135
140
|
|
136
141
|
def _update(self, netns, ifstates):
|
137
|
-
# parse
|
138
|
-
if '
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
netns.sysctl.add_global(
|
147
|
-
proto, ifstates['options']['sysctl'][proto])
|
142
|
+
# parse network sysctl settings
|
143
|
+
if 'sysctl' in ifstates:
|
144
|
+
for proto in ifstates['sysctl'].keys():
|
145
|
+
if proto in ['all', 'default']:
|
146
|
+
netns.sysctl.add(
|
147
|
+
proto, ifstates['sysctl'][proto])
|
148
|
+
else:
|
149
|
+
netns.sysctl.add_global(
|
150
|
+
proto, ifstates['sysctl'][proto])
|
148
151
|
|
149
152
|
# load BPF programs
|
150
153
|
if 'bpf' in ifstates:
|
@@ -157,8 +160,7 @@ class IfState():
|
|
157
160
|
netns.bpf_progs.add(name, config)
|
158
161
|
|
159
162
|
# add interfaces from config
|
160
|
-
for ifstate in ifstates['interfaces']:
|
161
|
-
name = ifstate['name']
|
163
|
+
for name, ifstate in ifstates['interfaces'].items():
|
162
164
|
kind = ifstate['link']['kind']
|
163
165
|
defaults = self.get_defaults(
|
164
166
|
ifname=name,
|
@@ -189,7 +191,7 @@ class IfState():
|
|
189
191
|
link.update(ifstate['link'])
|
190
192
|
if link:
|
191
193
|
netns.links[name] = Link(self,
|
192
|
-
netns, name, link, ethtool, ifstate.get('vrrp'), ifstate.get('brport'))
|
194
|
+
netns, name, link, ifstate.get('identify', {}), ethtool, ifstate.get('hooks', []), ifstate.get('vrrp'), ifstate.get('brport'))
|
193
195
|
else:
|
194
196
|
netns.links[name] = None
|
195
197
|
|
@@ -224,7 +226,7 @@ class IfState():
|
|
224
226
|
netns.sysctl.add(name, ifstate['sysctl'])
|
225
227
|
|
226
228
|
if 'cshaper' in ifstate:
|
227
|
-
profile_name = ifstate['cshaper'].get(
|
229
|
+
profile_name = ifstate['parameters']['cshaper'].get(
|
228
230
|
'profile', 'default')
|
229
231
|
logger.debug('cshaper profile {} enabled'.format(profile_name),
|
230
232
|
extra={'iface': name, 'netns': netns})
|
@@ -246,7 +248,7 @@ class IfState():
|
|
246
248
|
'qdisc': cshaper_profile['ingress_qdisc'],
|
247
249
|
}
|
248
250
|
}
|
249
|
-
ifb_state['tc']['qdisc']['bandwidth'] = ifstate['cshaper'].get(
|
251
|
+
ifb_state['tc']['qdisc']['bandwidth'] = ifstate['parameters']['cshaper'].get(
|
250
252
|
'ingress', 'unlimited')
|
251
253
|
|
252
254
|
if 'vrrp' in ifstate:
|
@@ -279,10 +281,10 @@ class IfState():
|
|
279
281
|
]
|
280
282
|
}
|
281
283
|
|
282
|
-
ifstate['tc']['qdisc']['bandwidth'] = ifstate['cshaper'].get(
|
284
|
+
ifstate['tc']['qdisc']['bandwidth'] = ifstate['parameters']['cshaper'].get(
|
283
285
|
'egress', 'unlimited')
|
284
286
|
|
285
|
-
del ifstate['cshaper']
|
287
|
+
del ifstate['parameters']['cshaper']
|
286
288
|
|
287
289
|
if 'tc' in ifstate:
|
288
290
|
netns.tc[name] = TC(
|
@@ -611,6 +613,8 @@ class IfState():
|
|
611
613
|
if ifname in netns.wireguard:
|
612
614
|
netns.wireguard[ifname].apply(do_apply)
|
613
615
|
|
616
|
+
self.hooks.apply(link, do_apply)
|
617
|
+
|
614
618
|
def _apply_routing(self, do_apply, netns, by_vrrp, vrrp_type, vrrp_name, vrrp_state):
|
615
619
|
if not netns.tables is None:
|
616
620
|
netns.tables.apply(self.ignore.get('routes', []), do_apply, by_vrrp, vrrp_type, vrrp_name, vrrp_state)
|
@@ -618,36 +622,82 @@ class IfState():
|
|
618
622
|
if not netns.rules is None:
|
619
623
|
netns.rules.apply(self.ignore.get('rules', []), do_apply, by_vrrp, vrrp_type, vrrp_name, vrrp_state)
|
620
624
|
|
621
|
-
def
|
625
|
+
def identify(self):
|
626
|
+
root_config = self._identify_netns(self.root_netns)
|
627
|
+
netns_instances = get_netns_instances()
|
628
|
+
if len(netns_instances) > 0:
|
629
|
+
netns_identifies = {}
|
630
|
+
for netns in netns_instances:
|
631
|
+
netns_identify = self._identify_netns(netns)
|
632
|
+
if netns_identify is not None:
|
633
|
+
netns_identifies[netns.netns] = netns_identify
|
634
|
+
|
635
|
+
if netns_identifies:
|
636
|
+
return {**root_config, 'namespaces': netns_identifies}
|
637
|
+
|
638
|
+
return {**root_config}
|
639
|
+
|
640
|
+
def _identify_netns(self, netns):
|
641
|
+
ifs_links = {}
|
642
|
+
for ipr_link in netns.ipr.get_links():
|
643
|
+
name = ipr_link.get_attr('IFLA_IFNAME')
|
644
|
+
# skip links on ignore list
|
645
|
+
if name != 'lo' and not any(re.match(regex, name) for regex in Parser._default_ifstates['parameters']['ignore']['ifname_builtin']):
|
646
|
+
info = ipr_link.get_attr('IFLA_LINKINFO')
|
647
|
+
if info is None:
|
648
|
+
kind = None
|
649
|
+
else:
|
650
|
+
kind = info.get_attr('IFLA_INFO_KIND')
|
651
|
+
|
652
|
+
if kind is not None:
|
653
|
+
continue
|
654
|
+
|
655
|
+
ifs_link = {
|
656
|
+
'identify': {
|
657
|
+
},
|
658
|
+
}
|
659
|
+
|
660
|
+
for attr, lookup in IDENTIFY_LOOKUPS.items():
|
661
|
+
value = lookup(netns, ipr_link)
|
662
|
+
if value is not None:
|
663
|
+
ifs_link['identify'][attr] = value
|
664
|
+
|
665
|
+
ifs_links[name] = ifs_link
|
666
|
+
|
667
|
+
if ifs_links:
|
668
|
+
return {**{'interfaces': ifs_links}}
|
669
|
+
else:
|
670
|
+
return None
|
671
|
+
|
672
|
+
def show(self, showall=False, show_secrets=False):
|
622
673
|
if showall:
|
623
674
|
defaults = deepcopy(Parser._default_ifstates)
|
624
675
|
else:
|
625
676
|
defaults = {}
|
626
677
|
|
627
678
|
ipaddr_ignore = []
|
628
|
-
for ip in Parser._default_ifstates['ignore']['ipaddr_builtin']:
|
679
|
+
for ip in Parser._default_ifstates['parameters']['ignore']['ipaddr_builtin']:
|
629
680
|
ipaddr_ignore.append(ip_network(ip))
|
630
681
|
|
631
|
-
root_config = self._show_netns(self.root_netns, showall, ipaddr_ignore)
|
682
|
+
root_config = self._show_netns(self.root_netns, showall, show_secrets, ipaddr_ignore)
|
632
683
|
netns_instances = get_netns_instances()
|
633
684
|
if len(netns_instances) > 0:
|
634
685
|
netns_configs = {}
|
635
686
|
for netns in netns_instances:
|
636
|
-
netns_configs[netns.netns] = self._show_netns(netns, showall, ipaddr_ignore)
|
687
|
+
netns_configs[netns.netns] = self._show_netns(netns, showall, show_secrets, ipaddr_ignore)
|
637
688
|
|
638
689
|
return {**defaults, **root_config, 'namespaces': netns_configs}
|
639
690
|
|
640
691
|
|
641
692
|
return {**defaults, **root_config}
|
642
693
|
|
643
|
-
def _show_netns(self, netns, showall, ipaddr_ignore):
|
644
|
-
ifs_links =
|
694
|
+
def _show_netns(self, netns, showall, show_secrets, ipaddr_ignore):
|
695
|
+
ifs_links = {}
|
645
696
|
for ipr_link in netns.ipr.get_links():
|
646
697
|
name = ipr_link.get_attr('IFLA_IFNAME')
|
647
698
|
# skip links on ignore list
|
648
|
-
if name != 'lo' and not any(re.match(regex, name) for regex in Parser._default_ifstates['ignore']['ifname_builtin']):
|
699
|
+
if name != 'lo' and not any(re.match(regex, name) for regex in Parser._default_ifstates['parameters']['ignore']['ifname_builtin']):
|
649
700
|
ifs_link = {
|
650
|
-
'name': name,
|
651
701
|
'addresses': [],
|
652
702
|
'link': {
|
653
703
|
'state': ipr_link['state'],
|
@@ -687,15 +737,13 @@ class IfState():
|
|
687
737
|
addr = ipr_link.get_attr('IFLA_ADDRESS')
|
688
738
|
if not addr is None:
|
689
739
|
ifs_link['link']['address'] = addr
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
if not businfo is None:
|
698
|
-
ifs_link['link']['businfo'] = businfo
|
740
|
+
|
741
|
+
# add identify section for physical links
|
742
|
+
ifs_link['identify'] = {}
|
743
|
+
for attr, func in IDENTIFY_LOOKUPS.items():
|
744
|
+
value = func(netns, ipr_link)
|
745
|
+
if value:
|
746
|
+
ifs_link['identify'][attr] = value
|
699
747
|
|
700
748
|
# add device group if not 0
|
701
749
|
group = ipr_link.get_attr('IFLA_GROUP')
|
@@ -722,6 +770,9 @@ class IfState():
|
|
722
770
|
|
723
771
|
brport.BRPort.show(netns.ipr, showall, ipr_link['index'], ifs_link)
|
724
772
|
|
773
|
+
if ifs_link['link']['kind'] == 'wireguard':
|
774
|
+
wireguard.WireGuard.show(netns, showall, show_secrets, name, ifs_link)
|
775
|
+
|
725
776
|
if name == 'lo':
|
726
777
|
if ifs_link['addresses'] == Parser._default_lo_link['addresses']:
|
727
778
|
del(ifs_link['addresses'])
|
@@ -730,13 +781,13 @@ class IfState():
|
|
730
781
|
del(ifs_link['link'])
|
731
782
|
|
732
783
|
if len(ifs_link) > 1:
|
733
|
-
ifs_links
|
784
|
+
ifs_links['lo'] = ifs_link
|
734
785
|
else:
|
735
|
-
ifs_links
|
786
|
+
ifs_links[name] = ifs_link
|
736
787
|
|
737
788
|
routing = {
|
738
|
-
'routes': Tables(netns).show_routes(Parser._default_ifstates['ignore']['routes_builtin']),
|
739
|
-
'rules': Rules(netns).show_rules(Parser._default_ifstates['ignore']['rules_builtin']),
|
789
|
+
'routes': Tables(netns).show_routes(Parser._default_ifstates['parameters']['ignore']['routes_builtin']),
|
790
|
+
'rules': Rules(netns).show_rules(Parser._default_ifstates['parameters']['ignore']['rules_builtin']),
|
740
791
|
}
|
741
792
|
|
742
793
|
return {**{'interfaces': ifs_links, 'routing': routing}}
|
@@ -0,0 +1,195 @@
|
|
1
|
+
from libifstate.util import get_netns_run_dir, dump_yaml_file, slurp_yaml_file, RUN_BASE_DIR
|
2
|
+
from libifstate.util import logger
|
3
|
+
|
4
|
+
import logging
|
5
|
+
from pathlib import Path
|
6
|
+
import os
|
7
|
+
import pkgutil
|
8
|
+
from shlex import quote
|
9
|
+
import shutil
|
10
|
+
from string import Template
|
11
|
+
import subprocess
|
12
|
+
import tempfile
|
13
|
+
|
14
|
+
|
15
|
+
HOOK_DIR = '/etc/ifstate/hooks'
|
16
|
+
HOOK_WRAPPER = Template(pkgutil.get_data("libifstate", "../hooks/wrapper.sh").decode("utf-8"))
|
17
|
+
|
18
|
+
RC_OK = 0
|
19
|
+
RC_ERROR = 1
|
20
|
+
RC_STARTED = 2
|
21
|
+
# it is the same value, but there are two constants to make it more convinient
|
22
|
+
RC_STOPPED = RC_STARTED
|
23
|
+
RC_CHANGED = 3
|
24
|
+
|
25
|
+
def _open_perm(path, flags):
|
26
|
+
return os.open(path, flags, 0o600)
|
27
|
+
|
28
|
+
class Hook():
|
29
|
+
def __init__(self, name, script, provides=[], after=[]):
|
30
|
+
self.name = name
|
31
|
+
|
32
|
+
if script[0] == '/':
|
33
|
+
self.script = Path(script).as_posix()
|
34
|
+
else:
|
35
|
+
self.script = Path(HOOK_DIR, script).as_posix()
|
36
|
+
|
37
|
+
self.provides = provides
|
38
|
+
self.after = after
|
39
|
+
|
40
|
+
def start(self, link, args, run_dir, do_apply):
|
41
|
+
wrapper_fn = f"{run_dir}/wrapper.sh"
|
42
|
+
|
43
|
+
with open(wrapper_fn, "w", opener=_open_perm) as fh:
|
44
|
+
template_vars = {
|
45
|
+
'verbose': 1 if logger.getEffectiveLevel() == logging.DEBUG else '',
|
46
|
+
'script': self.script,
|
47
|
+
'ifname': link.settings.get('ifname'),
|
48
|
+
'index': link.idx,
|
49
|
+
'netns': link.netns.netns or '',
|
50
|
+
'vrf': '',
|
51
|
+
'rundir': run_dir,
|
52
|
+
'rc_ok': RC_OK,
|
53
|
+
'rc_error': RC_ERROR,
|
54
|
+
'rc_started': RC_STARTED,
|
55
|
+
'rc_stopped': RC_STOPPED,
|
56
|
+
'rc_changed': RC_CHANGED,
|
57
|
+
}
|
58
|
+
|
59
|
+
if link.get_if_attr('IFLA_INFO_SLAVE_KIND') == 'vrf':
|
60
|
+
template_vars['vrf'] = link.settings.get('master')
|
61
|
+
|
62
|
+
args_list = []
|
63
|
+
for k, v in args.items():
|
64
|
+
args_list.append(f'export IFS_ARG_{k.upper()}={quote(v)}')
|
65
|
+
template_vars['args'] = "\n".join(args_list)
|
66
|
+
|
67
|
+
try:
|
68
|
+
fh.write(HOOK_WRAPPER.substitute(template_vars))
|
69
|
+
except KeyError as ex:
|
70
|
+
logger.error("Failed to prepare wrapper for hook {}: variable {} unknown".format(self.name, str(ex)))
|
71
|
+
return
|
72
|
+
except ValueError as ex:
|
73
|
+
logger.error("Failed to prepare wrapper for hook {}: {}".format(self.name, str(ex)))
|
74
|
+
return
|
75
|
+
|
76
|
+
try:
|
77
|
+
if do_apply:
|
78
|
+
subprocess.run(['/bin/sh', wrapper_fn, "start"], timeout=3, check=True)
|
79
|
+
else:
|
80
|
+
subprocess.run(['/bin/sh', wrapper_fn, "check-start"], timeout=3, check=True)
|
81
|
+
|
82
|
+
return RC_OK
|
83
|
+
except (FileNotFoundError, PermissionError) as ex:
|
84
|
+
logger.error("Failed executing hook {}: {}".format(self.name, str(ex)))
|
85
|
+
return RC_ERROR
|
86
|
+
except subprocess.TimeoutExpired as ex:
|
87
|
+
logger.error("Running hook {} has timed out.".format(self.name))
|
88
|
+
return RC_ERROR
|
89
|
+
except subprocess.CalledProcessError as ex:
|
90
|
+
if ex.returncode < 0:
|
91
|
+
logger.warning("Hook {} got signal {}".format(hook_name, -1 * ex.returncode))
|
92
|
+
return RC_ERROR
|
93
|
+
|
94
|
+
return ex.returncode
|
95
|
+
|
96
|
+
@staticmethod
|
97
|
+
def stop(link, hook_name, run_dir, do_apply):
|
98
|
+
wrapper_fn = f"{run_dir}/wrapper.sh"
|
99
|
+
|
100
|
+
try:
|
101
|
+
if do_apply:
|
102
|
+
subprocess.run(['/bin/sh', wrapper_fn, "stop"], timeout=3, check=True)
|
103
|
+
else:
|
104
|
+
subprocess.run(['/bin/sh', wrapper_fn, "check-stop"], timeout=3, check=True)
|
105
|
+
|
106
|
+
return RC_OK
|
107
|
+
except (FileNotFoundError, PermissionError) as ex:
|
108
|
+
logger.error("Failed executing hook {}: {}".format(hook_name, str(ex)))
|
109
|
+
return RC_ERROR
|
110
|
+
except subprocess.TimeoutExpired as ex:
|
111
|
+
logger.error("Running hook {} has timed out.".format(hook_name))
|
112
|
+
return RC_ERROR
|
113
|
+
except subprocess.CalledProcessError as ex:
|
114
|
+
if ex.returncode < 0:
|
115
|
+
logger.warning("Hook {} got signal {}".format(hook_name, -1 * ex.returncode))
|
116
|
+
return RC_ERROR
|
117
|
+
|
118
|
+
assert(run_dir.startswith(RUN_BASE_DIR))
|
119
|
+
|
120
|
+
try:
|
121
|
+
shutil.rmtree(run_dir)
|
122
|
+
except FileNotFoundError:
|
123
|
+
pass
|
124
|
+
except OSError as err:
|
125
|
+
logger.error("Failed cleanup hook rundir {}: {}".format(run_dir, str(err)))
|
126
|
+
|
127
|
+
return ex.returncode
|
128
|
+
|
129
|
+
class Hooks():
|
130
|
+
def __init__(self, ifstate):
|
131
|
+
self.hooks = {}
|
132
|
+
for hook, opts in ifstate.items():
|
133
|
+
if 'script' in opts:
|
134
|
+
self.hooks[hook] = Hook(hook, **opts)
|
135
|
+
else:
|
136
|
+
self.hooks[hook] = Hook(hook, script=hook, **opts)
|
137
|
+
|
138
|
+
def apply(self, link, do_apply):
|
139
|
+
run_dir = get_netns_run_dir('hooks', link.netns, str(link.idx))
|
140
|
+
|
141
|
+
state_fn = f"{run_dir}/state"
|
142
|
+
old_state = slurp_yaml_file(state_fn, default=[])
|
143
|
+
run_state = []
|
144
|
+
|
145
|
+
# Stop any running hooks which should not run any more or have other
|
146
|
+
# parameters - hooks are identified by all of their settings. Keep
|
147
|
+
# the rundir for any hook already running.
|
148
|
+
for entry in old_state:
|
149
|
+
if not entry.get('hook') in link.hooks:
|
150
|
+
rc = Hook.stop(link, entry["hook"]["name"], entry["rundir"], do_apply)
|
151
|
+
if rc == RC_OK:
|
152
|
+
pass
|
153
|
+
elif rc == RC_STOPPED:
|
154
|
+
logger.log_del('hooks', '- {}'.format(entry["hook"]["name"]))
|
155
|
+
else:
|
156
|
+
run_state.append(entry)
|
157
|
+
logger.log_err('hooks', '! {}'.format(entry["hook"]["name"]))
|
158
|
+
else:
|
159
|
+
hook = next((hook for hook in link.hooks if hook == entry["hook"]), None)
|
160
|
+
# tepmorary keep mapping between running and configured hook dict
|
161
|
+
hook["__rundir"] = entry["rundir"]
|
162
|
+
|
163
|
+
try:
|
164
|
+
if not link.hooks:
|
165
|
+
return
|
166
|
+
|
167
|
+
for hook in link.hooks:
|
168
|
+
if not hook["name"] in self.hooks:
|
169
|
+
logger.warning("Hook {} for {} is unknown!".format(link.settings.get('ifname'), hook))
|
170
|
+
continue
|
171
|
+
|
172
|
+
hook_run_dir = hook.get("__rundir")
|
173
|
+
# hook is not running, yet
|
174
|
+
if hook_run_dir is None:
|
175
|
+
hook_run_dir = tempfile.mkdtemp(prefix="hook_", dir=run_dir)
|
176
|
+
# hook was already running
|
177
|
+
else:
|
178
|
+
del(hook["__rundir"])
|
179
|
+
rc = self.hooks[hook["name"]].start(link, hook.get('args', {}), hook_run_dir, do_apply)
|
180
|
+
|
181
|
+
if rc == RC_OK:
|
182
|
+
logger.log_ok('hooks', '= {}'.format(hook["name"]))
|
183
|
+
elif rc == RC_STARTED:
|
184
|
+
logger.log_add('hooks', '+ {}'.format(hook["name"]))
|
185
|
+
elif rc == RC_CHANGED:
|
186
|
+
logger.log_change('hooks', '~ {}'.format(hook["name"]))
|
187
|
+
else:
|
188
|
+
logger.log_err('hooks', '! {}'.format(hook["name"]))
|
189
|
+
|
190
|
+
run_state.append({
|
191
|
+
"hook": hook,
|
192
|
+
"rundir": hook_run_dir,
|
193
|
+
})
|
194
|
+
finally:
|
195
|
+
dump_yaml_file(state_fn, run_state)
|