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.
libifstate/util.py CHANGED
@@ -1,7 +1,9 @@
1
1
  import libifstate.exception
2
2
  from libifstate.log import logger, IfStateLogging
3
- from pyroute2 import IPRoute, NetNS, netns
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
- # ethtool helper
29
- ETHTOOL_GDRVINFO = 0x00000003 # Get driver info
30
- STRUCT_DRVINFO = struct.Struct(
31
- "I" + # cmd
32
- "32s" + # driver
33
- "32s" + # version
34
- "32s" + # fw_version
35
- "32s" + # bus_info
36
- "32s" + # reserved1
37
- "12s" + # reserved2
38
- "I" + # n_priv_flags
39
- "I" + # n_stats
40
- "I" + # testinfo_len
41
- "I" + # eedump_len
42
- "I" # regdump_len
43
- )
44
-
45
- ETHTOOL_GPERMADDR = 0x00000020 # Get permanent hardware address
46
- L2_ADDRLENGTH = 6 # L2 address length
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
@@ -94,15 +94,15 @@ class WireGuard():
94
94
  has_pchanges = False
95
95
 
96
96
  avail = []
97
- for peer in self.wireguard['peers']:
98
- avail.append(peer['public_key'])
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 == peer['public_key']]), None)
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(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 peer.keys():
113
+ for setting in opts.keys():
114
114
  attr = getattr(peers[pubkey], setting)
115
115
  if setting == 'allowedips':
116
- attr = set(attr)
116
+ attr = [str(ip) for ip in attr]
117
117
  logger.debug(' peer.%s: %s => %s', setting, attr,
118
- peer[setting], extra={'iface': self.iface})
119
- if type(attr) == set:
120
- pchange |= not (attr == peer[setting])
118
+ opts[setting], extra={'iface': self.iface})
119
+ if type(attr) == list:
120
+ pchange |= not (attr == opts[setting])
121
121
  else:
122
- pchange |= str(peer[setting]) != str(getattr(
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(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, peer):
152
+ def safe_set_peer(self, public_key, opts):
153
153
  try:
154
- self.wg.set_peer(self.iface, **peer)
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(peer['endpoint'])
159
- self.wg.set_peer(self.iface, **peer)
158
+ del(opts['endpoint'])
159
+ self.wg.set_peer(self.iface, public_key=public_key, **opts)