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.
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
@@ -1,12 +1,14 @@
1
1
  from libifstate.util import logger, IfStateLogging
2
2
  from libifstate.exception import netlinkerror_classes, FeatureMissingError
3
- from wgnlpy import WireGuard as WG
4
- from ipaddress import ip_network
3
+ import wgnlpy
4
+ import ipaddress
5
5
  import collections
6
6
  from copy import deepcopy
7
7
  import pyroute2.netns
8
8
  import socket
9
9
 
10
+ SECRET_SETTINGS = ['private_key', 'preshared_key']
11
+
10
12
  class WireGuard():
11
13
  def __init__(self, netns, iface, wireguard):
12
14
  self.netns = netns
@@ -17,7 +19,7 @@ class WireGuard():
17
19
  pyroute2.netns.pushns(self.netns.netns)
18
20
 
19
21
  try:
20
- self.wg = WG()
22
+ self.wg = wgnlpy.WireGuard()
21
23
  finally:
22
24
  if self.netns.netns is not None:
23
25
  pyroute2.netns.popns()
@@ -28,7 +30,7 @@ class WireGuard():
28
30
  for i, peer in enumerate(self.wireguard['peers']):
29
31
  if 'allowedips' in peer:
30
32
  self.wireguard['peers'][i]['allowedips'] = set(
31
- [ip_network(x) for x in self.wireguard['peers'][i]['allowedips']])
33
+ [ipaddress.ip_network(x) for x in self.wireguard['peers'][i]['allowedips']])
32
34
 
33
35
  def __deepcopy__(self, memo):
34
36
  '''
@@ -43,7 +45,7 @@ class WireGuard():
43
45
  pyroute2.netns.pushns(self.netns.netns)
44
46
 
45
47
  try:
46
- setattr(result, k, WG())
48
+ setattr(result, k, wgnlpy.WireGuard())
47
49
  finally:
48
50
  if self.netns.netns is not None:
49
51
  pyroute2.netns.popns()
@@ -94,15 +96,15 @@ class WireGuard():
94
96
  has_pchanges = False
95
97
 
96
98
  avail = []
97
- for peer in self.wireguard['peers']:
98
- avail.append(peer['public_key'])
99
+ for public_key, opts in self.wireguard['peers'].items():
100
+ avail.append(public_key)
99
101
  pubkey = next(
100
- iter([x for x in peers.keys() if x == peer['public_key']]), None)
102
+ iter([x for x in peers.keys() if x == public_key]), None)
101
103
  if pubkey is None:
102
104
  has_pchanges = True
103
105
  if do_apply:
104
106
  try:
105
- self.safe_set_peer(peer)
107
+ self.safe_set_peer(public_key, opts)
106
108
  except Exception as err:
107
109
  if not isinstance(err, netlinkerror_classes):
108
110
  raise
@@ -110,23 +112,23 @@ class WireGuard():
110
112
  self.iface, err.args[1]))
111
113
  else:
112
114
  pchange = False
113
- for setting in peer.keys():
115
+ for setting in opts.keys():
114
116
  attr = getattr(peers[pubkey], setting)
115
117
  if setting == 'allowedips':
116
- attr = set(attr)
118
+ attr = [str(ip) for ip in attr]
117
119
  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])
120
+ opts[setting], extra={'iface': self.iface})
121
+ if type(attr) == list:
122
+ pchange |= not (attr == opts[setting])
121
123
  else:
122
- pchange |= str(peer[setting]) != str(getattr(
124
+ pchange |= str(opts[setting]) != str(getattr(
123
125
  peers[pubkey], setting))
124
126
 
125
127
  if pchange:
126
128
  has_pchanges = True
127
129
  if do_apply:
128
130
  try:
129
- self.safe_set_peer(peer)
131
+ self.safe_set_peer(public_key, opts)
130
132
  except Exception as err:
131
133
  if not isinstance(err, netlinkerror_classes):
132
134
  raise
@@ -149,11 +151,69 @@ class WireGuard():
149
151
  else:
150
152
  logger.log_ok('wg.peers')
151
153
 
152
- def safe_set_peer(self, peer):
154
+ def safe_set_peer(self, public_key, opts):
153
155
  try:
154
- self.wg.set_peer(self.iface, **peer)
156
+ self.wg.set_peer(self.iface, public_key=public_key, **opts)
155
157
  except (socket.gaierror, ValueError) as err:
156
- logger.warning('failed to set wireguard endpoint at {}: {}'.format(self.iface, err))
158
+ logger.warning('failed to set wireguard endpoint for peer {} at {}: {}'.format(public_key, self.iface, err))
159
+
160
+ del(opts['endpoint'])
161
+ self.wg.set_peer(self.iface, public_key=public_key, **opts)
162
+
163
+ def show(netns, show_all, show_secrets, name, config):
164
+ if netns.netns is not None:
165
+ pyroute2.netns.pushns(self.netns.netns)
166
+
167
+ try:
168
+ wg = wgnlpy.WireGuard()
169
+ finally:
170
+ if netns.netns is not None:
171
+ pyroute2.netns.popns()
172
+
173
+ state = wg.get_interface(
174
+ name, spill_private_key=show_secrets, spill_preshared_keys=show_secrets)
175
+
176
+ config['wireguard'] = {
177
+ 'peers': {},
178
+ }
179
+
180
+ def _dump_value(value):
181
+ if isinstance(value, (ipaddress.IPv4Network, ipaddress.IPv6Network, wgnlpy.sockaddr_in.sockaddr_in)):
182
+ return str(value)
183
+ elif type(value) is list:
184
+ result = []
185
+ for v in value:
186
+ result.append(_dump_value(v))
187
+ return result
188
+ else:
189
+ return value
190
+
191
+ def _dump_values(cfg, key, value):
192
+ if show_all:
193
+ if value is None:
194
+ return
195
+ else:
196
+ if not value:
197
+ return
198
+
199
+ if key in SECRET_SETTINGS:
200
+ if show_secrets:
201
+ cfg[key] = str(value)
202
+ else:
203
+ cfg[key] = f"# VALUE IS HIDDEN - USE --show-secrets TO REVEAL"
204
+ else:
205
+ cfg[key] = _dump_value(value)
206
+
207
+ for key in ['private_key', 'listen_port', 'fwmark']:
208
+ value = getattr(state, key)
209
+ print("{} => {}".format(key, value))
210
+ _dump_values(config['wireguard'], key, value)
157
211
 
158
- del(peer['endpoint'])
159
- self.wg.set_peer(self.iface, **peer)
212
+ for peer in state.peers:
213
+ config['wireguard']['peers'][str(peer)] = {}
214
+ print(state.peers[peer].endpoint)
215
+ print(type(state.peers[peer].endpoint))
216
+ print("---")
217
+ for key in ['preshared_key', 'endpoint', 'persistent_keepalive_interval', 'allowedips']:
218
+ value = getattr(state.peers[peer], key)
219
+ _dump_values(config['wireguard']['peers'][str(peer)], key, value)