ifstate 1.13.6__py3-none-any.whl → 2.0.0rc1__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 +7 -2
- ifstate/vrrp.py +1 -1
- {ifstate-1.13.6.dist-info → ifstate-2.0.0rc1.dist-info}/METADATA +1 -1
- ifstate-2.0.0rc1.dist-info/RECORD +38 -0
- {ifstate-1.13.6.dist-info → ifstate-2.0.0rc1.dist-info}/WHEEL +1 -1
- libifstate/__init__.py +93 -41
- libifstate/hook/__init__.py +195 -0
- libifstate/link/__init__.py +0 -1
- libifstate/link/base.py +34 -36
- libifstate/link/physical.py +2 -2
- libifstate/link/tun.py +2 -2
- libifstate/link/veth.py +2 -2
- libifstate/netns/__init__.py +49 -13
- libifstate/parser/base.py +107 -84
- libifstate/routing/__init__.py +233 -113
- libifstate/util.py +146 -147
- libifstate/wireguard/__init__.py +16 -16
- schema/2/ifstate.conf.schema.json +4398 -0
- ifstate-1.13.6.dist-info/RECORD +0 -37
- libifstate/link/dsa.py +0 -10
- schema/ifstate.conf.schema.json +0 -4259
- {ifstate-1.13.6.dist-info → ifstate-2.0.0rc1.dist-info}/entry_points.txt +0 -0
- {ifstate-1.13.6.dist-info → ifstate-2.0.0rc1.dist-info}/licenses/LICENSE +0 -0
- {ifstate-1.13.6.dist-info → ifstate-2.0.0rc1.dist-info}/top_level.txt +0 -0
libifstate/util.py
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
import libifstate.exception
|
2
2
|
from libifstate.log import logger, IfStateLogging
|
3
|
-
|
3
|
+
import pyroute2
|
4
|
+
from pyroute2 import IPRoute, IW, NetNS
|
4
5
|
|
6
|
+
from pyroute2.netlink.exceptions import NetlinkError
|
5
7
|
from pyroute2.netlink.rtnl.tcmsg import tcmsg
|
6
8
|
from pyroute2.netlink.rtnl import RTM_DELTFILTER, RTM_NEWNSID
|
7
9
|
from pyroute2.netlink.rtnl.nsidmsg import nsidmsg
|
@@ -17,33 +19,45 @@ except ModuleNotFoundError:
|
|
17
19
|
# pyroute2 >= 0.6
|
18
20
|
from pr2modules.ethtool.ioctl import SIOCETHTOOL
|
19
21
|
|
22
|
+
import atexit
|
20
23
|
import socket
|
21
24
|
import fcntl
|
25
|
+
import re
|
22
26
|
import struct
|
27
|
+
import subprocess
|
23
28
|
import array
|
24
29
|
import struct
|
30
|
+
import tempfile
|
25
31
|
import typing
|
26
32
|
import os
|
33
|
+
import yaml
|
27
34
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
"
|
42
|
-
"
|
43
|
-
)
|
44
|
-
|
45
|
-
|
46
|
-
|
35
|
+
def _get_of_node(netns, link):
|
36
|
+
if netns.netns is not None:
|
37
|
+
pyroute2.netns.pushns(netns.netns)
|
38
|
+
|
39
|
+
try:
|
40
|
+
return os.path.relpath(os.path.realpath(f"{netns.ipr.sysfs_path}/class/net/{link.get_attr('IFLA_IFNAME')}/of_node", strict=True), start=f"{netns.ipr.sysfs_path}/firmware/devicetree")
|
41
|
+
except FileNotFoundError:
|
42
|
+
return None
|
43
|
+
finally:
|
44
|
+
if netns.netns is not None:
|
45
|
+
pyroute2.netns.popns()
|
46
|
+
|
47
|
+
IDENTIFY_LOOKUPS = {
|
48
|
+
"perm_address": lambda netns, link: link.get_attr("IFLA_PERM_ADDRESS"),
|
49
|
+
"parent_dev_name": lambda netns, link: link.get_attr("IFLA_PARENT_DEV_NAME"),
|
50
|
+
"parent_dev_bus_name": lambda netns, link: link.get_attr("IFLA_PARENT_DEV_BUS_NAME"),
|
51
|
+
"phys_port_id": lambda netns, link: link.get_attr("IFLA_PHYS_PORT_ID"),
|
52
|
+
"phys_port_name": lambda netns, link: link.get_attr("IFLA_PHYS_PORT_NAME"),
|
53
|
+
"phys_switch_id": lambda netns, link: link.get_attr("IFLA_PHYS_SWITCH_ID"),
|
54
|
+
"of_node": _get_of_node,
|
55
|
+
}
|
56
|
+
|
57
|
+
|
58
|
+
REGEX_ETHER_BYTE = re.compile('[a-f0-9]{2}')
|
59
|
+
|
60
|
+
RUN_BASE_DIR = '/run/libifstate'
|
47
61
|
|
48
62
|
root_ipr = typing.NewType("IPRouteExt", IPRoute)
|
49
63
|
|
@@ -59,28 +73,96 @@ def filter_ifla_dump(showall, ifla, defaults, prefix="IFLA"):
|
|
59
73
|
|
60
74
|
return dump
|
61
75
|
|
76
|
+
def format_ether_address(address):
|
77
|
+
"""
|
78
|
+
Formats a ether address string canonical. Accepted formats:
|
79
|
+
|
80
|
+
xx:xx:xx:xx:xx:xx
|
81
|
+
xx-xx-xx-xx-xx-xx
|
82
|
+
xxxx.xxxx.xxxx
|
83
|
+
|
84
|
+
The hex digits may be lower and upper case.
|
85
|
+
"""
|
86
|
+
|
87
|
+
return ':'.join(REGEX_ETHER_BYTE.findall(address.lower()))
|
88
|
+
|
89
|
+
|
90
|
+
def get_run_dir(function, *args):
|
91
|
+
"""
|
92
|
+
Returns a deterministic directory under /run/libifstate for saving state
|
93
|
+
informations between ifstate calls. A hierarchy is build from the ifstate
|
94
|
+
function and additional distinguishers (i.e. ifname).
|
95
|
+
|
96
|
+
The directory will be created if it does not already exists.
|
97
|
+
"""
|
98
|
+
|
99
|
+
run_dir = os.path.join(RUN_BASE_DIR, function, *args)
|
100
|
+
|
101
|
+
try:
|
102
|
+
os.makedirs(run_dir, mode=0o700)
|
103
|
+
except FileExistsError:
|
104
|
+
pass
|
105
|
+
|
106
|
+
return run_dir
|
107
|
+
|
108
|
+
def get_netns_run_dir(function, netns, *args):
|
109
|
+
"""
|
110
|
+
Returns a deterministic directory under /run/libifstate for saving state
|
111
|
+
informations between ifstate calls. A hierarchy is build from the ifstate
|
112
|
+
function, the netns and optional additional distinguishers (i.e. ifname).
|
113
|
+
|
114
|
+
The directory will be created if it does not already exists.
|
115
|
+
"""
|
116
|
+
|
117
|
+
if netns.netns is None:
|
118
|
+
run_dir = os.path.join(RUN_BASE_DIR, function, 'root', *args)
|
119
|
+
else:
|
120
|
+
run_dir = os.path.join(RUN_BASE_DIR, function, 'netns', netns.netns, *args)
|
121
|
+
|
122
|
+
try:
|
123
|
+
os.makedirs(run_dir, mode=0o700)
|
124
|
+
except FileExistsError:
|
125
|
+
pass
|
126
|
+
|
127
|
+
return run_dir
|
128
|
+
|
129
|
+
def dump_yaml_file(fn, obj, opener=None):
|
130
|
+
"""
|
131
|
+
Dump obj to a YaML file, create directories if needed and catch file
|
132
|
+
I/O errors.
|
133
|
+
"""
|
134
|
+
try:
|
135
|
+
os.makedirs(os.path.dirname(fn), mode=0o700)
|
136
|
+
except FileExistsError:
|
137
|
+
pass
|
138
|
+
|
139
|
+
try:
|
140
|
+
with open(fn, "w", opener=opener) as fh:
|
141
|
+
yaml.dump(obj, fh)
|
142
|
+
except OSError as err:
|
143
|
+
logger.error('Writing {} failed: {}'.format(fn, err))
|
144
|
+
|
145
|
+
def slurp_yaml_file(fn, default=None):
|
146
|
+
"""
|
147
|
+
Read the content of a YaML file, returns *default* if the file could not be
|
148
|
+
found, read or parsed.
|
149
|
+
"""
|
150
|
+
try:
|
151
|
+
with open(fn) as fh:
|
152
|
+
return yaml.load(fh, Loader=yaml.SafeLoader)
|
153
|
+
except OSError as err:
|
154
|
+
logger.debug('Reading {} failed: {}'.format(fn, err))
|
155
|
+
except yaml.YAMLError as err:
|
156
|
+
logger.warning('Parsing {} failed: {}'.format(fn, err))
|
157
|
+
|
158
|
+
return default
|
159
|
+
|
62
160
|
class IPRouteExt(IPRoute):
|
63
161
|
def __init__(self, *args, **kwargs):
|
64
162
|
super().__init__(*args, **kwargs)
|
65
163
|
|
66
164
|
self.__sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
# def fork_before():
|
71
|
-
# import sys
|
72
|
-
# print(f"FORK[{os.getpid()}] before", file=sys.stderr)
|
73
|
-
|
74
|
-
# def fork_parent():
|
75
|
-
# import sys
|
76
|
-
# print(f"FORK[{os.getpid()}] parent", file=sys.stderr)
|
77
|
-
|
78
|
-
# def fork_child():
|
79
|
-
# import sys
|
80
|
-
# print(f"FORK[{os.getpid()}] after", file=sys.stderr)
|
81
|
-
|
82
|
-
# os.register_at_fork(before=fork_before, after_in_parent=fork_parent, after_in_child=fork_child)
|
83
|
-
|
165
|
+
self.sysfs_path = "/sys"
|
84
166
|
|
85
167
|
def del_filter_by_info(self, index=0, handle=0, info=0, parent=0):
|
86
168
|
msg = tcmsg()
|
@@ -97,60 +179,6 @@ class IPRouteExt(IPRoute):
|
|
97
179
|
NLM_F_ACK
|
98
180
|
))
|
99
181
|
|
100
|
-
def get_businfo(self, ifname):
|
101
|
-
data = array.array("B", struct.pack(
|
102
|
-
"I", ETHTOOL_GDRVINFO))
|
103
|
-
data.extend(b'\x00' * (STRUCT_DRVINFO.size - len(data)))
|
104
|
-
|
105
|
-
ifr = struct.pack('16sP', ifname.encode(
|
106
|
-
"utf-8"), data.buffer_info()[0])
|
107
|
-
|
108
|
-
try:
|
109
|
-
r = fcntl.ioctl(self.__sock.fileno(), SIOCETHTOOL, ifr)
|
110
|
-
except OSError:
|
111
|
-
return None
|
112
|
-
|
113
|
-
drvinfo = STRUCT_DRVINFO.unpack(data)
|
114
|
-
|
115
|
-
return drvinfo[4].decode('ascii').split('\x00')[0]
|
116
|
-
|
117
|
-
def get_permaddr(self, ifname):
|
118
|
-
data = array.array("B", struct.pack(
|
119
|
-
"II", ETHTOOL_GPERMADDR, L2_ADDRLENGTH))
|
120
|
-
data.extend(b'\x00' * L2_ADDRLENGTH)
|
121
|
-
|
122
|
-
ifr = struct.pack('16sP', ifname.encode(
|
123
|
-
"utf-8"), data.buffer_info()[0])
|
124
|
-
|
125
|
-
try:
|
126
|
-
r = fcntl.ioctl(self.__sock.fileno(), SIOCETHTOOL, ifr)
|
127
|
-
except OSError:
|
128
|
-
return None
|
129
|
-
|
130
|
-
l2addr = ":".join(format(x, "02x") for x in data[8:])
|
131
|
-
if l2addr == "00:00:00:00:00:00":
|
132
|
-
return None
|
133
|
-
|
134
|
-
return l2addr
|
135
|
-
|
136
|
-
def get_iface_by_businfo(self, businfo):
|
137
|
-
for iface in iter(self.get_links()):
|
138
|
-
ifname = iface.get_attr('IFLA_IFNAME')
|
139
|
-
bi = self.get_businfo(ifname)
|
140
|
-
|
141
|
-
if bi and bi == businfo:
|
142
|
-
return iface['index']
|
143
|
-
|
144
|
-
def get_iface_by_permaddr(self, permaddr):
|
145
|
-
for iface in iter(self.get_links()):
|
146
|
-
ifname = iface.get_attr('IFLA_IFNAME')
|
147
|
-
addr = self.get_permaddr(ifname)
|
148
|
-
|
149
|
-
if addr and addr == permaddr:
|
150
|
-
return iface['index']
|
151
|
-
|
152
|
-
return None
|
153
|
-
|
154
182
|
def get_ifname_by_index(self, index):
|
155
183
|
link = next(iter(self.get_links(index)), None)
|
156
184
|
|
@@ -207,10 +235,31 @@ class NetNSExt(NetNS):
|
|
207
235
|
self.netns = self.status["netns"]
|
208
236
|
|
209
237
|
try:
|
210
|
-
netns.pushns(self.netns)
|
238
|
+
pyroute2.netns.pushns(self.netns)
|
211
239
|
self.__sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
240
|
+
|
241
|
+
os.makedirs("/run/libifstate/sysfs", exist_ok=True)
|
242
|
+
self.sysfs_path = tempfile.mkdtemp(prefix=self.netns, dir="/run/libifstate/sysfs")
|
243
|
+
rc = subprocess.run(['mount', '', self.sysfs_path, '-t', 'sysfs', '-o', 'ro,nosuid,nodev,noexec,noatime'])
|
244
|
+
if rc.returncode != 0:
|
245
|
+
logger.warning("Could not mount netns sysfs: {rc.stderr}")
|
246
|
+
else:
|
247
|
+
logger.debug('mounted sysfs for {} at {}'.format(self.netns, self.sysfs_path), extra={'netns': self.netns})
|
248
|
+
atexit.register(self.umount_sysfs)
|
212
249
|
finally:
|
213
|
-
netns.popns()
|
250
|
+
pyroute2.netns.popns()
|
251
|
+
|
252
|
+
def umount_sysfs(self):
|
253
|
+
rc = subprocess.run(['umount', self.sysfs_path, '-t', 'sysfs'])
|
254
|
+
if rc.returncode != 0:
|
255
|
+
logger.warning("Could not umount netns sysfs: {rc.stderr}")
|
256
|
+
else:
|
257
|
+
logger.debug('umounted sysfs for {} at {}'.format(self.netns, self.sysfs_path), extra={'netns': self.netns})
|
258
|
+
|
259
|
+
try:
|
260
|
+
os.rmdir(self.sysfs_path)
|
261
|
+
except OSError:
|
262
|
+
pass
|
214
263
|
|
215
264
|
def del_filter_by_info(self, index=0, handle=0, info=0, parent=0):
|
216
265
|
msg = tcmsg()
|
@@ -227,60 +276,6 @@ class NetNSExt(NetNS):
|
|
227
276
|
NLM_F_ACK
|
228
277
|
))
|
229
278
|
|
230
|
-
def get_businfo(self, ifname):
|
231
|
-
data = array.array("B", struct.pack(
|
232
|
-
"I", ETHTOOL_GDRVINFO))
|
233
|
-
data.extend(b'\x00' * (STRUCT_DRVINFO.size - len(data)))
|
234
|
-
|
235
|
-
ifr = struct.pack('16sP', ifname.encode(
|
236
|
-
"utf-8"), data.buffer_info()[0])
|
237
|
-
|
238
|
-
try:
|
239
|
-
r = fcntl.ioctl(self.__sock.fileno(), SIOCETHTOOL, ifr)
|
240
|
-
except OSError:
|
241
|
-
return None
|
242
|
-
|
243
|
-
drvinfo = STRUCT_DRVINFO.unpack(data)
|
244
|
-
|
245
|
-
return drvinfo[4].decode('ascii').split('\x00')[0]
|
246
|
-
|
247
|
-
def get_permaddr(self, ifname):
|
248
|
-
data = array.array("B", struct.pack(
|
249
|
-
"II", ETHTOOL_GPERMADDR, L2_ADDRLENGTH))
|
250
|
-
data.extend(b'\x00' * L2_ADDRLENGTH)
|
251
|
-
|
252
|
-
ifr = struct.pack('16sP', ifname.encode(
|
253
|
-
"utf-8"), data.buffer_info()[0])
|
254
|
-
|
255
|
-
try:
|
256
|
-
r = fcntl.ioctl(self.__sock.fileno(), SIOCETHTOOL, ifr)
|
257
|
-
except OSError:
|
258
|
-
return None
|
259
|
-
|
260
|
-
l2addr = ":".join(format(x, "02x") for x in data[8:])
|
261
|
-
if l2addr == "00:00:00:00:00:00":
|
262
|
-
return None
|
263
|
-
|
264
|
-
return l2addr
|
265
|
-
|
266
|
-
def get_iface_by_businfo(self, businfo):
|
267
|
-
for iface in iter(self.get_links()):
|
268
|
-
ifname = iface.get_attr('IFLA_IFNAME')
|
269
|
-
bi = self.get_businfo(ifname)
|
270
|
-
|
271
|
-
if bi and bi == businfo:
|
272
|
-
return iface['index']
|
273
|
-
|
274
|
-
def get_iface_by_permaddr(self, permaddr):
|
275
|
-
for iface in iter(self.get_links()):
|
276
|
-
ifname = iface.get_attr('IFLA_IFNAME')
|
277
|
-
addr = self.get_permaddr(ifname)
|
278
|
-
|
279
|
-
if addr and addr == permaddr:
|
280
|
-
return iface['index']
|
281
|
-
|
282
|
-
return None
|
283
|
-
|
284
279
|
def get_ifname_by_index(self, index):
|
285
280
|
link = next(iter(self.get_links(index)), None)
|
286
281
|
|
@@ -369,3 +364,7 @@ class LinkDependency:
|
|
369
364
|
|
370
365
|
|
371
366
|
root_ipr = IPRouteExt()
|
367
|
+
try:
|
368
|
+
root_iw = IW()
|
369
|
+
except NetlinkError:
|
370
|
+
root_iw = None
|
libifstate/wireguard/__init__.py
CHANGED
@@ -94,15 +94,15 @@ class WireGuard():
|
|
94
94
|
has_pchanges = False
|
95
95
|
|
96
96
|
avail = []
|
97
|
-
for
|
98
|
-
avail.append(
|
97
|
+
for public_key, opts in self.wireguard['peers'].items():
|
98
|
+
avail.append(public_key)
|
99
99
|
pubkey = next(
|
100
|
-
iter([x for x in peers.keys() if x ==
|
100
|
+
iter([x for x in peers.keys() if x == public_key]), None)
|
101
101
|
if pubkey is None:
|
102
102
|
has_pchanges = True
|
103
103
|
if do_apply:
|
104
104
|
try:
|
105
|
-
self.safe_set_peer(
|
105
|
+
self.safe_set_peer(public_key, opts)
|
106
106
|
except Exception as err:
|
107
107
|
if not isinstance(err, netlinkerror_classes):
|
108
108
|
raise
|
@@ -110,23 +110,23 @@ class WireGuard():
|
|
110
110
|
self.iface, err.args[1]))
|
111
111
|
else:
|
112
112
|
pchange = False
|
113
|
-
for setting in
|
113
|
+
for setting in opts.keys():
|
114
114
|
attr = getattr(peers[pubkey], setting)
|
115
115
|
if setting == 'allowedips':
|
116
|
-
attr =
|
116
|
+
attr = [str(ip) for ip in attr]
|
117
117
|
logger.debug(' peer.%s: %s => %s', setting, attr,
|
118
|
-
|
119
|
-
if type(attr) ==
|
120
|
-
pchange |= not (attr ==
|
118
|
+
opts[setting], extra={'iface': self.iface})
|
119
|
+
if type(attr) == list:
|
120
|
+
pchange |= not (attr == opts[setting])
|
121
121
|
else:
|
122
|
-
pchange |= str(
|
122
|
+
pchange |= str(opts[setting]) != str(getattr(
|
123
123
|
peers[pubkey], setting))
|
124
124
|
|
125
125
|
if pchange:
|
126
126
|
has_pchanges = True
|
127
127
|
if do_apply:
|
128
128
|
try:
|
129
|
-
self.safe_set_peer(
|
129
|
+
self.safe_set_peer(public_key, opts)
|
130
130
|
except Exception as err:
|
131
131
|
if not isinstance(err, netlinkerror_classes):
|
132
132
|
raise
|
@@ -149,11 +149,11 @@ class WireGuard():
|
|
149
149
|
else:
|
150
150
|
logger.log_ok('wg.peers')
|
151
151
|
|
152
|
-
def safe_set_peer(self,
|
152
|
+
def safe_set_peer(self, public_key, opts):
|
153
153
|
try:
|
154
|
-
self.wg.set_peer(self.iface, **
|
154
|
+
self.wg.set_peer(self.iface, public_key=public_key, **opts)
|
155
155
|
except (socket.gaierror, ValueError) as err:
|
156
|
-
logger.warning('failed to set wireguard endpoint at {}: {}'.format(self.iface, err))
|
156
|
+
logger.warning('failed to set wireguard endpoint for peer {} at {}: {}'.format(public_key, self.iface, err))
|
157
157
|
|
158
|
-
del(
|
159
|
-
self.wg.set_peer(self.iface, **
|
158
|
+
del(opts['endpoint'])
|
159
|
+
self.wg.set_peer(self.iface, public_key=public_key, **opts)
|