ifstate 1.11.9__py3-none-any.whl → 1.13.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 CHANGED
@@ -24,16 +24,18 @@ class Actions():
24
24
  SHOWALL = "showall"
25
25
  VRRP = "vrrp"
26
26
  VRRP_FIFO = "vrrp-fifo"
27
+ VRRP_WORKER = "vrrp-worker"
27
28
  SHELL = "shell"
28
29
 
29
30
  ACTIONS_HELP = {
30
- "CHECK" : "dry run update the network config",
31
- "APPLY" : "update the network config",
32
- "SHOW" : "show running network config",
33
- "SHOWALL" : "show running network config (more settings)",
34
- "VRRP" : "run as keepalived notify script",
35
- "VRRP_FIFO": "run as keepalived notify_fifo_script",
36
- "SHELL" : "launch interactive python shell (pyroute2)",
31
+ "CHECK" : "dry run update the network config",
32
+ "APPLY" : "update the network config",
33
+ "SHOW" : "show running network config",
34
+ "SHOWALL" : "show running network config (more settings)",
35
+ "VRRP" : "run as keepalived notify script",
36
+ "VRRP_FIFO" : "run as keepalived notify_fifo_script",
37
+ "VRRP_WORKER": "worker process for vrrp-fifo",
38
+ "SHELL" : "launch interactive python shell (pyroute2)",
37
39
  }
38
40
 
39
41
  class IfsConfigHandler():
@@ -41,23 +43,22 @@ class IfsConfigHandler():
41
43
  self.fn = fn
42
44
  self.soft_schema = soft_schema
43
45
 
46
+ # require to be called from the root netns
47
+ try:
48
+ # assume init runs in the root netns
49
+ if os.readlink('/proc/1/ns/net') != os.readlink(f'/proc/{os.getpid()}/ns/net'):
50
+ logger.error("Must not be run from inside a netns!")
51
+ raise NetNSNotRoot()
52
+ except OSError as ex:
53
+ logger.debug(f'root netns test: {ex}')
54
+ pass
55
+
44
56
  self.ifs = self.load_config()
45
57
  self.cb = None
46
58
 
47
- def set_callback(self, cb):
48
- self.cb = cb
49
-
50
- def sighup_handler(self, signum, frame):
51
- logger.info("SIGHUP: reloading configuration")
52
- try:
53
- self.ifs = self.load_config()
54
- self.cb(self.ifs)
55
- except:
56
- logger.exception("failed to reload configuration")
57
-
58
59
  def load_config(self):
59
60
  try:
60
- parser = YamlParser(self.fn)
61
+ self.parser = YamlParser(self.fn)
61
62
  except ParserOpenError as ex:
62
63
  logger.error(
63
64
  "Config loading from {} failed: {}".format(ex.fn, ex.msg))
@@ -71,10 +72,8 @@ class IfsConfigHandler():
71
72
  raise ex
72
73
 
73
74
  try:
74
- ifstates = parser.config()
75
-
76
75
  ifs = IfState()
77
- ifs.update(ifstates, self.soft_schema)
76
+ ifs.update(self.parser.config(), self.soft_schema)
78
77
  return ifs
79
78
  except ParserValidationError as ex:
80
79
  logger.error("Config validation failed for {}".format(ex.detail))
@@ -83,10 +82,14 @@ class IfsConfigHandler():
83
82
  logger.error(
84
83
  "Config uses unavailable feature: {}".format(ex.feature))
85
84
  raise ex
86
- except NetNSNotRoot as ex:
87
- logger.error("Must not be run from inside a netns!")
88
- raise ex
89
85
 
86
+ def dump_config(self, fn):
87
+ umask = os.umask(0o077)
88
+ try:
89
+ with open(fn, "w") as fh:
90
+ self.parser.dump(fh)
91
+ finally:
92
+ os.umask(umask)
90
93
 
91
94
  def shell():
92
95
  from ifstate.shell import IfStateConsole
@@ -151,6 +154,12 @@ def main():
151
154
  action_parsers[Actions.VRRP_FIFO].add_argument(
152
155
  "fifo", type=str, help="named FIFO to read state changes from")
153
156
 
157
+ # Parameters for the vrrp-worker action
158
+ action_parsers[Actions.VRRP_WORKER].add_argument(
159
+ "type", type=str.lower, choices=["group", "instance"], help="type of vrrp notification")
160
+ action_parsers[Actions.VRRP_WORKER].add_argument(
161
+ "name", type=str, help="name of the vrrp group or instance")
162
+
154
163
  args = parser.parse_args()
155
164
  if args.verbose:
156
165
  lvl = logging.DEBUG
@@ -181,7 +190,7 @@ def main():
181
190
  ifslog.quit()
182
191
  exit(0)
183
192
 
184
- if args.action in [Actions.CHECK, Actions.APPLY, Actions.VRRP, Actions.VRRP_FIFO]:
193
+ if args.action in [Actions.CHECK, Actions.APPLY, Actions.VRRP, Actions.VRRP_FIFO, Actions.VRRP_WORKER]:
185
194
  try:
186
195
  ifs_config = IfsConfigHandler(args.config, args.soft_schema)
187
196
  except (ParserOpenError,
@@ -203,7 +212,10 @@ def main():
203
212
  exit(ex.exit_code())
204
213
  elif args.action == Actions.VRRP_FIFO:
205
214
  from ifstate.vrrp import vrrp_fifo
206
- vrrp_fifo(args.fifo, ifs_config, lvl)
215
+ vrrp_fifo(args.fifo, ifs_config)
216
+ elif args.action == Actions.VRRP_WORKER:
217
+ from ifstate.vrrp import vrrp_worker
218
+ vrrp_worker(args.type, args.name, ifs_config)
207
219
  else:
208
220
  # ignore some well-known signals to prevent interruptions (i.e. due to ssh connection loss)
209
221
  signal.signal(signal.SIGHUP, signal.SIG_IGN)
ifstate/vrrp.py CHANGED
@@ -1,51 +1,74 @@
1
1
  from libifstate.util import logger, IfStateLogging
2
2
 
3
+ import atexit
3
4
  from copy import deepcopy
4
5
  import logging
5
6
  import multiprocessing as mp
7
+ import threading
6
8
  import os
7
9
  import re
8
10
  from setproctitle import setproctitle
9
11
  import signal
12
+ import subprocess
13
+ import sys
10
14
 
11
- class VrrpFifoProcess(mp.Process):
15
+ class VrrpFifoProcess():
12
16
  '''
13
17
  Process for vrrp group/instance configuration.
14
18
  '''
15
- def __init__(self, vrrp_type, vrrp_name, ifstate, log_level):
16
- self.vrrp_type = vrrp_type
17
- self.vrrp_name = vrrp_name
18
- self.ifstate = ifstate
19
- self.log_level = log_level
20
- self.queue = mp.Queue()
21
- super().__init__(target=self.vrrp_worker, name=f'ifstate-vrrp-fifo|{vrrp_type}.{vrrp_name}', daemon=True)
19
+ def __init__(self, worker_args):
20
+ self.worker_args = worker_args
21
+ self.logger_extra = {'iface': f'{worker_args[-2]} "{worker_args[-1]}"'}
22
+ self.state_queue = mp.Queue()
23
+ self.worker_proc = None
24
+
25
+ worker_io = threading.Thread(target=self.dequeue)
26
+ worker_io.start()
22
27
 
23
28
  def vrrp_update(self, vrrp_state):
24
- self.queue.put(vrrp_state)
29
+ self.state_queue.put(vrrp_state)
25
30
 
26
- def vrrp_worker(self):
27
- instance_title = "vrrp-{}-{}".format(self.vrrp_type, self.vrrp_name)
28
- setproctitle("ifstate-{}".format(instance_title))
29
- ifslog = IfStateLogging(self.log_level, action=instance_title, log_stderr=False)
30
- logger.info('worker spawned')
31
+ def dequeue(self):
31
32
  while True:
32
- vrrp_state = self.queue.get()
33
- if vrrp_state is None:
34
- logger.info('terminating')
33
+ state = self.state_queue.get()
34
+
35
+ # Should we terminate?
36
+ if state is None:
37
+ self.worker_proc.stdin.close()
38
+ self.worker_proc.wait()
35
39
  return
36
- else:
37
- ifstate = deepcopy(self.ifstate)
38
- ifstate.apply(self.vrrp_type, self.vrrp_name, vrrp_state)
40
+
41
+ # Restart ifstate vrrp-worker if not alive alive?
42
+ if self.worker_proc.poll() is not None:
43
+ logger.warning("worker died", extra=self.logger_extra)
44
+ self.start()
45
+
46
+ logger.info(f'state => {state}', extra=self.logger_extra)
47
+ self.worker_proc.stdin.write(f'{state}\n')
48
+ self.worker_proc.stdin.flush()
49
+ logger.warning("dequeue terminated")
50
+
51
+ def start(self):
52
+ logger.info("spawning worker", extra=self.logger_extra)
53
+ self.worker_proc = subprocess.Popen(self.worker_args, stdin=subprocess.PIPE, stderr=sys.stderr, text=True)
54
+
39
55
 
40
56
  class VrrpStates():
41
57
  '''
42
58
  Tracks processes and states for vrrp groups/instances.
43
59
  '''
44
- def __init__(self, ifstate, log_level):
45
- self.ifstate = ifstate
46
- self.log_level = log_level
60
+ def __init__(self, ifs_config):
61
+ self.ifs_config = ifs_config
47
62
  self.processes = {}
48
63
  self.states = {}
64
+ self.pid_file = f"/run/libifstate/vrrp/{os.getpid()}.pid"
65
+ self.cfg_file = f"/run/libifstate/vrrp/{os.getpid()}.cfg"
66
+ self.worker_args = (
67
+ sys.argv[0],
68
+ '-c', self.cfg_file,
69
+ 'vrrp-worker')
70
+ if logger.level == logging.DEBUG:
71
+ self.worker_args.append('-v')
49
72
 
50
73
  def update(self, vrrp_type, vrrp_name, vrrp_state):
51
74
  '''
@@ -54,44 +77,65 @@ class VrrpStates():
54
77
  '''
55
78
  key = (vrrp_type, vrrp_name)
56
79
  if not key in self.processes:
57
- self.processes[key] = VrrpFifoProcess(vrrp_type, vrrp_name, self.ifstate, self.log_level)
80
+ worker_args = self.worker_args + key
81
+ self.processes[key] = VrrpFifoProcess(worker_args)
58
82
  self.processes[key].start()
59
83
 
60
84
  self.states[key] = vrrp_state
61
85
  self.processes[key].vrrp_update(vrrp_state)
62
86
 
63
- def reconfigure(self, ifstate):
87
+ def reconfigure(self, *argv):
64
88
  '''
65
89
  Reconfigure all known groups/instances using their last known state by spawning
66
90
  new worker Processes.
67
91
  '''
68
- self.ifstate = ifstate
92
+
93
+ # save new config to temp file
94
+ try:
95
+ self.ifs_config.load_config()
96
+ self.ifs_config.dump_config(self.cfg_file)
97
+ except Exception as ex:
98
+ logger.error(f'failed to reload config: {ex}')
99
+ return
100
+
69
101
  for key, state in self.states.items():
102
+ worker_args = self.worker_args + key
70
103
  self.processes[key].vrrp_update(None)
71
- self.processes[key] = VrrpFifoProcess(*key, ifstate, self.log_level)
104
+ self.processes[key] = VrrpFifoProcess(worker_args)
72
105
  self.processes[key].start()
73
106
  self.processes[key].vrrp_update(self.states[key])
74
107
 
75
- def vrrp_fifo(args_fifo, ifs_config, log_level):
76
- vrrp_states = VrrpStates(ifs_config.ifs, log_level)
77
- ifs_config.set_callback(vrrp_states.reconfigure)
108
+ def cleanup_run(self, *argv):
109
+ """
110
+ """
111
+ for file in [self.pid_file, self.cfg_file]:
112
+ try:
113
+ os.unlink(file)
114
+ except OSError as ex:
115
+ if ex.errno != 2:
116
+ logger.warning(f"cannot cleanup {file}: {ex}")
117
+
118
+ def vrrp_fifo(args_fifo, ifs_config):
119
+ vrrp_states = VrrpStates(ifs_config)
78
120
 
79
- signal.signal(signal.SIGHUP, ifs_config.sighup_handler)
121
+ signal.signal(signal.SIGHUP, vrrp_states.reconfigure)
80
122
  signal.signal(signal.SIGPIPE, signal.SIG_IGN)
81
- signal.signal(signal.SIGTERM, signal.SIG_IGN)
123
+ signal.signal(signal.SIGTERM, vrrp_states.cleanup_run)
82
124
 
83
- pid_file = f"/run/libifstate/vrrp/{os.getpid()}.pid"
84
125
  try:
85
126
  os.makedirs("/run/libifstate/vrrp", exist_ok=True)
86
127
 
87
- with open(pid_file, "w", encoding="utf-8") as fh:
128
+ with open(vrrp_states.pid_file, "w", encoding="utf-8") as fh:
88
129
  fh.write(args_fifo)
130
+ atexit.register(vrrp_states.cleanup_run)
131
+
132
+ ifs_config.dump_config(vrrp_states.cfg_file)
89
133
  except IOError as err:
90
- logger.exception(f'failed to write pid file {pid_file}: {err}')
134
+ logger.exception(f'failed to write pid file {vrrp_states.pid_file}: {err}')
91
135
 
92
136
  try:
93
137
  status_pattern = re.compile(
94
- r'(group|instance) "([^"]+)" (unknown|fault|backup|master)( \d+)?$', re.IGNORECASE)
138
+ r'(group|instance) "([^"]+)" (unknown|fault|backup|master|stop)( \d+)?$', re.IGNORECASE)
95
139
 
96
140
  with open(args_fifo) as fifo:
97
141
  logger.debug("entering fifo loop...")
@@ -107,7 +151,16 @@ def vrrp_fifo(args_fifo, ifs_config, log_level):
107
151
  logger.warning(f'failed to parse fifo input: {line.strip()}')
108
152
  mp.active_children()
109
153
  finally:
110
- try:
111
- os.remove(pid_file)
112
- except IOError:
113
- pass
154
+ vrrp_states.cleanup_run()
155
+
156
+ def vrrp_worker(vrrp_type, vrrp_name, ifs_config):
157
+ instance_title = "vrrp-{}-{}".format(vrrp_type, vrrp_name)
158
+ setproctitle("ifstate-{}".format(instance_title))
159
+
160
+ logger.info('worker is alive')
161
+ for state in sys.stdin:
162
+ vrrp_state = state.strip()
163
+
164
+ ifstate = deepcopy(ifs_config.ifs)
165
+ ifstate.apply(vrrp_type, vrrp_name, vrrp_state)
166
+ logger.info('terminating')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ifstate
3
- Version: 1.11.9
3
+ Version: 1.13.0
4
4
  Summary: Manage host interface settings in a declarative manner
5
5
  Home-page: https://ifstate.net/
6
6
  Author: Thomas Liske
@@ -0,0 +1,37 @@
1
+ ifstate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ ifstate/ifstate.py,sha256=jNWVc-XPZOKwD--xSJvAXZP-DEomljmGXtMf8SAdmmU,8770
3
+ ifstate/shell.py,sha256=7_JFpi4icr9MijynDzbb0v5mxhFsng6PCC4m3uQ255A,2177
4
+ ifstate/vrrp.py,sha256=m-vkm_Yx82qx5RmTnouYceWpa1agziBUHr87yK9tBro,5542
5
+ libifstate/__init__.py,sha256=zAn9f1jay5LI9JWSwgzqLK8RmTIks4cYELHolpX60i0,30792
6
+ libifstate/exception.py,sha256=5i59BZdl56J_sNJbyU9n6uHuUNJEyDOO4FJ-neDn9Ds,2608
7
+ libifstate/log.py,sha256=XVoZdwdQoWsjuupFIuG6OP0OrBpXpx7oqyAaUhQ-nJk,4553
8
+ libifstate/util.py,sha256=vqNaa67QZ2G07bQNAisTyUKbVmp0JGkvEGgbKVTtmYA,10579
9
+ libifstate/address/__init__.py,sha256=zIGM7UWXSvoijHMD06DfS2CDtiHpiJlqDgJG7Dl1IPE,3222
10
+ libifstate/bpf/__init__.py,sha256=NVzaunTmJU2PbIQg9eWBMKpFgLh3EnD3ujNa7Yt6rNc,7699
11
+ libifstate/bpf/ctypes.py,sha256=kLZJHZlba09Vc-tbsJAcKpDwdoNO2IjlYVLCopawHmA,4274
12
+ libifstate/bpf/map.py,sha256=cLHNMvRBDNW2yVCEf3z242_oRdU0HqVbFEYVkKXng0w,10818
13
+ libifstate/brport/__init__.py,sha256=NzdA8F4hr2se1bXKNnyKZbvOFlCWBq_cdjwsL1H0Y-o,2964
14
+ libifstate/fdb/__init__.py,sha256=jMplRZZQKkgwFQT2L7Ua4YCdLKwOzkd43_6OFtet2No,6262
15
+ libifstate/link/__init__.py,sha256=epVw6jY8exNeJZUmmUas91yJoeupfgIY7rthq7SGIIw,142
16
+ libifstate/link/base.py,sha256=QbHi3R3Ptyi5L4gdBbOPzJ2Ftpvhnk4Hdl4CCIz5pxs,33162
17
+ libifstate/link/dsa.py,sha256=Y3axTtcym6YL1voKblxctx4PoKDZHzpteKQNnEBUrS8,264
18
+ libifstate/link/physical.py,sha256=cJiq-MCfy-3XQoU-OxzgfPZZtu_pJ8u2ioJgn9VYdGk,560
19
+ libifstate/link/tun.py,sha256=m55o5cwO3h3DCLofUR-68fM4ggLoTKElp6ZJ2LrJSCc,959
20
+ libifstate/link/veth.py,sha256=dEnkhg1AU-CFOFJN-U68G6FJ74CNW9G4RXsa5vLs-qY,2114
21
+ libifstate/neighbour/__init__.py,sha256=FJJpbJvqnxvOEii6QDMYzW5jQDEbiEy71GQOEbqaS48,2463
22
+ libifstate/netns/__init__.py,sha256=lKv43oEcpVUmL494MNOOG_Uh4ght50uu8n8nb9TIwsA,9077
23
+ libifstate/parser/__init__.py,sha256=byz1W0G7UewVc5FFie-ti3UZjGK3-75wHIaOeq0oySQ,88
24
+ libifstate/parser/base.py,sha256=VFAo05O3tiKtI381LVUMYfsDTseMKoQGTfkgnEkm3H4,4770
25
+ libifstate/parser/yaml.py,sha256=MC0kmwqt3P45z61fb_wfUqoj0iZyhFYkdPyr0UqMSZA,1415
26
+ libifstate/routing/__init__.py,sha256=sYe05nuYPYhzahWWwCofDk3F7eRx0vh-iJ6rZr2Utkw,19745
27
+ libifstate/sysctl/__init__.py,sha256=L2gkoLnac_HM6RbCaKmsEuDTNj2m7U4Rjw42TEti0kg,3074
28
+ libifstate/tc/__init__.py,sha256=inPdampCOIr_4oKNB3awqMkW0Eh4fpPh9jvSba6sPVg,12092
29
+ libifstate/wireguard/__init__.py,sha256=vM7vxioV9vQ3gq9SE_URf97ZfGhLsxNiam16y_gXZ5E,6225
30
+ libifstate/xdp/__init__.py,sha256=X1xhEIGng7R5d5F4KsChykT2g6H-XBRWbWABijoYDQA,7208
31
+ schema/ifstate.conf.schema.json,sha256=NZSBedK5m037Iu8O1G5KQ3cc5anNjDs_sPK64U9a6z8,199377
32
+ ifstate-1.13.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
33
+ ifstate-1.13.0.dist-info/METADATA,sha256=gcKwFbvj7qRlszUfdQyFHNOqO1ifd1tu09XwlC4L5u4,1385
34
+ ifstate-1.13.0.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
35
+ ifstate-1.13.0.dist-info/entry_points.txt,sha256=HF6jX7Uu_nF1Ly-J9uEPeiRapOxnM6LuHsb2y6Mt-k4,52
36
+ ifstate-1.13.0.dist-info/top_level.txt,sha256=A7peI7aKBaM69fsiSPvMbL3rzTKZZr5qDxKC-pHMGdE,19
37
+ ifstate-1.13.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (75.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
libifstate/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- from libifstate.exception import LinkDuplicate
1
+ 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
@@ -6,7 +6,7 @@ from libifstate.neighbour import Neighbours
6
6
  from libifstate.routing import Tables, Rules, RTLookups
7
7
  from libifstate.parser import Parser
8
8
  from libifstate.tc import TC
9
- from libifstate.exception import netlinkerror_classes, NetNSNotRoot
9
+ from libifstate.exception import netlinkerror_classes
10
10
  import bisect
11
11
  import os
12
12
  import pyroute2
@@ -48,7 +48,7 @@ import json
48
48
  import errno
49
49
  import logging
50
50
 
51
- __version__ = "1.11.9"
51
+ __version__ = "1.13.0"
52
52
 
53
53
 
54
54
  class IfState():
@@ -120,14 +120,6 @@ class IfState():
120
120
 
121
121
  self._update(self.root_netns, ifstates)
122
122
  if 'namespaces' in ifstates:
123
- # require to called from the root netns
124
- try:
125
- # assume init runs in the root netns
126
- if os.readlink('/proc/1/ns/net') != os.readlink(f'/proc/{os.getpid()}/ns/net'):
127
- raise NetNSNotRoot()
128
- except OSError as ex:
129
- logger.debug(f'root netns test: {ex}')
130
- pass
131
123
  self.namespaces = {}
132
124
  self.new_namespaces = []
133
125
  for netns_name, netns_ifstates in ifstates['namespaces'].items():
@@ -519,7 +511,11 @@ class IfState():
519
511
  if link_dep.netns is None:
520
512
  self._apply_iface(do_apply, self.root_netns, link_dep, by_vrrp, vrrp_type, vrrp_name, vrrp_state)
521
513
  else:
522
- self._apply_iface(do_apply, self.namespaces[link_dep.netns], link_dep, by_vrrp, vrrp_type, vrrp_name, vrrp_state)
514
+ if link_dep.netns not in self.namespaces:
515
+ logger.warning("add link {} failed: netns '{}' is unknown".format(link_dep.ifname, link_dep.netns))
516
+ return
517
+ else:
518
+ self._apply_iface(do_apply, self.namespaces[link_dep.netns], link_dep, by_vrrp, vrrp_type, vrrp_name, vrrp_state)
523
519
 
524
520
  # configure routing
525
521
  logger.info("")
@@ -25,25 +25,29 @@ class Addresses():
25
25
  # get active ip addresses
26
26
  ipr_addr = {}
27
27
  addr_add = []
28
- addr_dad = []
28
+ addr_renew = []
29
29
  for addr in self.netns.ipr.get_addr(index=idx):
30
30
  flags = addr.get_attr('IFA_FLAGS', 0)
31
31
  ip = ip_interface(addr.get_attr('IFA_ADDRESS') +
32
32
  '/' + str(addr['prefixlen']))
33
33
  if flags & IFA_F_DADFAILED == IFA_F_DADFAILED:
34
34
  logger.debug('{} has failed dad'.format(ip), extra={'iface': self.iface, 'netns': self.netns})
35
- addr_dad.append(ip)
35
+ addr_renew.append(ip)
36
36
  ipr_addr[ip] = addr
37
37
 
38
38
  for addr in self.addresses:
39
- if addr in ipr_addr and addr not in addr_dad:
39
+ if addr in ipr_addr and addr not in addr_renew:
40
40
  logger.log_ok('addresses', '= {}'.format(addr.with_prefixlen))
41
41
  del ipr_addr[addr]
42
42
  else:
43
43
  addr_add.append(addr)
44
+ for ip in ipr_addr.keys():
45
+ if addr.ip == ip.ip:
46
+ addr_renew.append(ip)
47
+ break
44
48
 
45
49
  for ip, addr in ipr_addr.items():
46
- if addr in addr_dad or not any(ip in net for net in ignore):
50
+ if ip in addr_renew or not any(ip in net for net in ignore):
47
51
  if not ign_dynamic or ipr_addr[ip]['flags'] & IFA_F_PERMANENT == IFA_F_PERMANENT:
48
52
  logger.log_del('addresses', '- {}'.format(ip.with_prefixlen))
49
53
  try:
libifstate/exception.py CHANGED
@@ -104,5 +104,9 @@ class ParserParseError(Exception):
104
104
  def exit_code(self):
105
105
  return 2
106
106
 
107
- class RouteDupblicate(Exception):
107
+ class RouteDuplicate(Exception):
108
108
  pass
109
+
110
+ class NetnsUnknown(Exception):
111
+ def __init__(self, netns):
112
+ self.args = (None, "netns '{}' is unknown".format(netns))
@@ -1,4 +1,5 @@
1
1
  import libifstate.link.base
2
+ import libifstate.link.dsa
2
3
  import libifstate.link.physical
3
4
  import libifstate.link.tun
4
5
  import libifstate.link.veth
libifstate/link/base.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from libifstate.util import logger, IfStateLogging, LinkDependency
2
- from libifstate.exception import ExceptionCollector, LinkTypeUnknown, netlinkerror_classes
2
+ from libifstate.exception import ExceptionCollector, LinkTypeUnknown, NetnsUnknown, netlinkerror_classes
3
3
  from libifstate.brport import BRPort
4
4
  from libifstate.routing import RTLookups
5
5
  from abc import ABC, abstractmethod
@@ -99,6 +99,7 @@ class Link(ABC):
99
99
  'ip6gre',
100
100
  'ip6gretap',
101
101
  'geneve',
102
+ 'sit',
102
103
  'wireguard',
103
104
  'xfrm',
104
105
  ]
@@ -246,14 +247,16 @@ class Link(ABC):
246
247
  return ret
247
248
 
248
249
  info = self.iface.get_attr('IFLA_LINKINFO')
249
- if not info is None:
250
- ret = info.get_attr(nla)
250
+ op = getattr(info, 'get_attr', None)
251
+ if callable(op):
252
+ ret = op(nla)
251
253
  if not ret is None:
252
254
  return ret
253
255
 
254
- info = info.get_attr('IFLA_INFO_DATA')
255
- if not info is None:
256
- ret = info.get_attr(nla)
256
+ info = op('IFLA_INFO_DATA')
257
+ op = getattr(info, 'get_attr', None)
258
+ if callable(op):
259
+ ret = op(nla)
257
260
  if not ret is None:
258
261
  return ret
259
262
 
@@ -388,6 +391,9 @@ class Link(ABC):
388
391
  if self.bind_netns is None:
389
392
  return self.ifstate.root_netns
390
393
 
394
+ if not self.bind_netns in self.ifstate.namespaces:
395
+ raise NetnsUnknown(self.bind_netns)
396
+
391
397
  return self.ifstate.namespaces.get(self.bind_netns)
392
398
 
393
399
  def bind_needs_recreate(self, item):
@@ -395,13 +401,6 @@ class Link(ABC):
395
401
  return False
396
402
 
397
403
  bind_netns = self.get_bind_netns()
398
- if bind_netns is None:
399
- logger.warning('bind_netns "%s" is unknown',
400
- self.bind_netns,
401
- extra={
402
- 'iface': self.settings['ifname'],
403
- 'netns': self.netns})
404
- return False
405
404
 
406
405
  fn = self.get_bind_fn(item.netns.netns, item.index)
407
406
  try:
@@ -468,11 +467,15 @@ class Link(ABC):
468
467
  item = self.search_link_registry()
469
468
 
470
469
  # check if bind_netns option requires a recreate
471
- if item is not None and self.bind_needs_recreate(item):
472
- self.idx = item.index
473
- self.recreate(do_apply, sysctl, excpts)
470
+ try:
471
+ if item is not None and self.bind_needs_recreate(item):
472
+ self.idx = item.index
473
+ self.recreate(do_apply, sysctl, excpts)
474
474
 
475
- self.settings = osettings
475
+ self.settings = osettings
476
+ return excpts
477
+ except NetnsUnknown as ex:
478
+ excpts.add('apply', ex)
476
479
  return excpts
477
480
 
478
481
  # move interface into netns if required
@@ -534,7 +537,12 @@ class Link(ABC):
534
537
  logger.log_add('link', oper)
535
538
 
536
539
  settings = copy.deepcopy(self.settings)
537
- bind_netns = self.get_bind_netns()
540
+ try:
541
+ bind_netns = self.get_bind_netns()
542
+ except NetnsUnknown as ex:
543
+ excpts.add(oper, ex)
544
+ return excpts
545
+
538
546
  if bind_netns is not None and bind_netns.netns != self.netns.netns:
539
547
  logger.debug("handle link binding", extra={
540
548
  'iface': self.settings['ifname'],
libifstate/link/dsa.py ADDED
@@ -0,0 +1,10 @@
1
+ from libifstate.util import logger
2
+ from libifstate.link.physical import PhysicalLink
3
+
4
+ class DsaLink(PhysicalLink):
5
+ """
6
+ Distributed Switch Architecture (DSA) user interface
7
+
8
+ https://docs.kernel.org/networking/dsa/configuration.html
9
+ """
10
+ pass
libifstate/link/veth.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from libifstate.util import logger
2
2
  from libifstate.link.base import Link
3
- from libifstate.exception import LinkCannotAdd
3
+ from libifstate.exception import LinkCannotAdd, NetnsUnknown
4
4
 
5
5
  class VethLink(Link):
6
6
  def __init__(self, ifstate, netns, name, link, ethtool, vrrp, brport):
@@ -18,7 +18,11 @@ class VethLink(Link):
18
18
  '''
19
19
  result = super().create(do_apply, sysctl, excpts, oper)
20
20
 
21
- bind_netns = self.get_bind_netns()
21
+ try:
22
+ bind_netns = self.get_bind_netns()
23
+ except NetnsUnknown as ex:
24
+ excpts.add(oper, ex)
25
+ return excpts
22
26
  peer_link = next(iter(bind_netns.ipr.get_links(ifname=self.settings['peer'])), None)
23
27
  self.ifstate.link_registry.add_link(bind_netns, peer_link)
24
28
 
libifstate/log.py CHANGED
@@ -2,6 +2,7 @@ import logging
2
2
  from logging.handlers import QueueHandler, QueueListener
3
3
  import os
4
4
  import queue
5
+ import stat
5
6
  import sys
6
7
 
7
8
  logger = logging.getLogger('ifstate')
@@ -98,14 +99,25 @@ class IfStateLogging:
98
99
  handlers.append(stream)
99
100
 
100
101
  # log to syslog
101
- syslog = logging.handlers.SysLogHandler('/dev/log', facility=logging.handlers.SysLogHandler.LOG_DAEMON)
102
- if action is None:
103
- syslog.ident = 'ifstate[{}] '.format(os.getpid())
104
- else:
105
- syslog.ident = 'ifstate-{}[{}] '.format(action, os.getpid())
106
- syslog.addFilter(IfStateLogFilter(False))
107
- syslog.setFormatter(formatter)
108
- handlers.append(syslog)
102
+ socket_filename = '/dev/log'
103
+ try:
104
+ if not stat.S_ISSOCK(os.stat(socket_filename).st_mode):
105
+ socket_filename = None
106
+ except OSError:
107
+ socket_filename = None
108
+
109
+ if socket_filename is not None:
110
+ syslog = logging.handlers.SysLogHandler(socket_filename, facility=logging.handlers.SysLogHandler.LOG_DAEMON)
111
+ if action is None:
112
+ syslog.ident = 'ifstate[{}] '.format(os.getpid())
113
+ else:
114
+ syslog.ident = 'ifstate-{}[{}] '.format(action, os.getpid())
115
+ syslog.addFilter(IfStateLogFilter(False))
116
+ syslog.setFormatter(formatter)
117
+ handlers.append(syslog)
118
+
119
+ if len(handlers) == 0:
120
+ handlers.append(logging.NullHandler())
109
121
 
110
122
  qu = queue.SimpleQueue()
111
123
  queue_handler = QueueHandler(qu)
libifstate/parser/base.py CHANGED
@@ -150,3 +150,10 @@ class Parser(ABC):
150
150
  del(cfg["ignore"][k])
151
151
 
152
152
  return cfg
153
+
154
+ @abstractmethod
155
+ def dump(self, stream):
156
+ """
157
+ Dump the parsed configuration into a io stream.
158
+ """
159
+ pass
libifstate/parser/yaml.py CHANGED
@@ -42,3 +42,6 @@ class YamlParser(Parser):
42
42
  raise ParserOpenError(ex)
43
43
  except yaml.parser.ParserError as ex:
44
44
  raise ParserParseError(ex)
45
+
46
+ def dump(self, stream):
47
+ yaml.dump(self.config(), stream)
@@ -1,5 +1,5 @@
1
1
  from libifstate.util import logger, IfStateLogging
2
- from libifstate.exception import RouteDupblicate, netlinkerror_classes
2
+ from libifstate.exception import RouteDuplicate, netlinkerror_classes
3
3
  from ipaddress import ip_address, ip_network, IPv6Network
4
4
  from pyroute2.netlink.rtnl.fibmsg import FR_ACT_VALUES
5
5
  from pyroute2.netlink.rtnl import rt_type
@@ -61,7 +61,7 @@ class RTLookup():
61
61
  self.str2id = {}
62
62
  self.id2str = {}
63
63
 
64
- for basedir in ['/usr/lib/iproute2', '/etc/iproute2']:
64
+ for basedir in ['/usr/share/iproute2', '/usr/lib/iproute2', '/etc/iproute2']:
65
65
  fn = os.path.join(basedir, name)
66
66
  try:
67
67
  with open(fn, 'r') as fp:
@@ -90,7 +90,7 @@ class RTLookup():
90
90
  return self.str2id[key]
91
91
 
92
92
  def lookup_str(self, key):
93
- return self.id2str.get(key, key)
93
+ return self.id2str.get(key, str(key))
94
94
 
95
95
 
96
96
  class RTLookups():
@@ -307,7 +307,7 @@ class Tables(collections.abc.Mapping):
307
307
 
308
308
  kroutes = self.kernel_routes(table)
309
309
 
310
- for route in croutes:
310
+ for route in sorted(croutes, key=lambda x: [x.get('via', ''), x['dst']]):
311
311
  if 'oif' in route and type(route['oif']) == str:
312
312
  oif = next(
313
313
  iter(self.netns.ipr.link_lookup(ifname=route['oif'])), None)
libifstate/util.py CHANGED
@@ -23,6 +23,7 @@ import struct
23
23
  import array
24
24
  import struct
25
25
  import typing
26
+ import os
26
27
 
27
28
  # ethtool helper
28
29
  ETHTOOL_GDRVINFO = 0x00000003 # Get driver info
@@ -64,6 +65,23 @@ class IPRouteExt(IPRoute):
64
65
 
65
66
  self.__sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
66
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
+
84
+
67
85
  def del_filter_by_info(self, index=0, handle=0, info=0, parent=0):
68
86
  msg = tcmsg()
69
87
  msg['index'] = index
@@ -173,7 +191,7 @@ class IPRouteExt(IPRoute):
173
191
  try:
174
192
  return next(iter(self.get_links(*argv, **kwarg)), None)
175
193
  except Exception as err:
176
- if not isinstance(err, netlinkerror_classes):
194
+ if not isinstance(err, libifstate.exception.netlinkerror_classes):
177
195
  raise
178
196
 
179
197
  return None
@@ -2,7 +2,7 @@
2
2
  "$id": "https://ifstate.net/schema/ifstate.conf.schema.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "ifstate.conf",
5
- "description": "IfState 1.11.9 Configuration Schema",
5
+ "description": "IfState 1.13 Configuration Schema",
6
6
  "type": "object",
7
7
  "required": [
8
8
  "interfaces"
@@ -2743,6 +2743,51 @@
2743
2743
  }
2744
2744
  }
2745
2745
  },
2746
+ {
2747
+ "description": "DSA interface",
2748
+ "required": [
2749
+ "kind",
2750
+ "link"
2751
+ ],
2752
+ "additionalProperties": false,
2753
+ "properties": {
2754
+ "kind": {
2755
+ "const": "dsa",
2756
+ "description": "link type"
2757
+ },
2758
+ "address": {
2759
+ "$ref": "#/$defs/iface-link_address"
2760
+ },
2761
+ "group": {
2762
+ "$ref": "#/$defs/iface-link_group"
2763
+ },
2764
+ "permaddr": {
2765
+ "description": "select interface by permanent address [ethtool -P]",
2766
+ "$ref": "#/$defs/iface-link_address"
2767
+ },
2768
+ "state": {
2769
+ "$ref": "#/$defs/iface-link_state"
2770
+ },
2771
+ "master": {
2772
+ "$ref": "#/$defs/iface-link_master"
2773
+ },
2774
+ "mtu": {
2775
+ "$ref": "#/$defs/iface-link_mtu"
2776
+ },
2777
+ "txqlen": {
2778
+ "$ref": "#/$defs/iface-link_txqlen"
2779
+ },
2780
+ "ifalias": {
2781
+ "$ref": "#/$defs/iface-link_ifalias"
2782
+ },
2783
+ "link": {
2784
+ "$ref": "#/$defs/iface-link_link"
2785
+ },
2786
+ "link_netns": {
2787
+ "$ref": "#/$defs/iface-link_link-netns"
2788
+ }
2789
+ }
2790
+ },
2746
2791
  {
2747
2792
  "description": "Dummy network interface",
2748
2793
  "required": [
@@ -2970,6 +3015,32 @@
2970
3015
  "description": "specifies the maximum number of FDB entries (0: none)",
2971
3016
  "default": 0
2972
3017
  },
3018
+ "vxlan_port": {
3019
+ "description": "specifies the UDP destination port to communicate to the remote VXLAN tunnel endpoint",
3020
+ "type": "integer",
3021
+ "minimum": 0,
3022
+ "maximum": 65535
3023
+ },
3024
+ "vxlan_port_range": {
3025
+ "description": "specifies the range of port numbers to use as UDP source ports to communicate to the remote VXLAN tunnel endpoint",
3026
+ "required": [
3027
+ "low",
3028
+ "high"
3029
+ ],
3030
+ "additionalProperties": false,
3031
+ "properties": {
3032
+ "low": {
3033
+ "type": "integer",
3034
+ "minimum": 0,
3035
+ "maximum": 65535
3036
+ },
3037
+ "high": {
3038
+ "type": "integer",
3039
+ "minimum": 0,
3040
+ "maximum": 65535
3041
+ }
3042
+ }
3043
+ },
2973
3044
  "vxlan_proxy": {
2974
3045
  "type": "integer",
2975
3046
  "minimum": 0,
@@ -3136,6 +3207,9 @@
3136
3207
  "address": {
3137
3208
  "$ref": "#/$defs/iface-link_address"
3138
3209
  },
3210
+ "bind_netns": {
3211
+ "$ref": "#/$defs/iface-link_bind-netns"
3212
+ },
3139
3213
  "group": {
3140
3214
  "$ref": "#/$defs/iface-link_group"
3141
3215
  },
@@ -1,36 +0,0 @@
1
- ifstate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- ifstate/ifstate.py,sha256=1xXJmVt_w6X7K5KTqfeTDZ1y-wJxgtrtLJJ1bUFN5I0,8031
3
- ifstate/shell.py,sha256=7_JFpi4icr9MijynDzbb0v5mxhFsng6PCC4m3uQ255A,2177
4
- ifstate/vrrp.py,sha256=GOSwQFf3IrcWF5DbQCJQqZ7p33TaFMEVp8LwyRDolWM,4016
5
- libifstate/__init__.py,sha256=UBj-_U7UY6ar2ds0ZS9yb15bR4813RJ3NUQwN8uSWHg,30914
6
- libifstate/exception.py,sha256=jCFub2kWSpntcxwL-yJkOpw9sqCmCypBqN_qaIzwEQU,2480
7
- libifstate/log.py,sha256=CKETu-GUyhBgs4WVtbIYhm_Lzgilap9ALLPeSkO1sWc,4136
8
- libifstate/util.py,sha256=NYY6Yoy4t42z_Q_Auy_vRHZ_MWT_XXCBZGZj61YMuWQ,10111
9
- libifstate/address/__init__.py,sha256=wDs0JgLBdZjKzLAnCsMgsyQwgCzXNckcRBL7NIGS_aI,3056
10
- libifstate/bpf/__init__.py,sha256=NVzaunTmJU2PbIQg9eWBMKpFgLh3EnD3ujNa7Yt6rNc,7699
11
- libifstate/bpf/ctypes.py,sha256=kLZJHZlba09Vc-tbsJAcKpDwdoNO2IjlYVLCopawHmA,4274
12
- libifstate/bpf/map.py,sha256=cLHNMvRBDNW2yVCEf3z242_oRdU0HqVbFEYVkKXng0w,10818
13
- libifstate/brport/__init__.py,sha256=NzdA8F4hr2se1bXKNnyKZbvOFlCWBq_cdjwsL1H0Y-o,2964
14
- libifstate/fdb/__init__.py,sha256=jMplRZZQKkgwFQT2L7Ua4YCdLKwOzkd43_6OFtet2No,6262
15
- libifstate/link/__init__.py,sha256=QZggoC-bIscqwVedqVycaSqS1CmXB3Bx3m2FZei8Q_4,115
16
- libifstate/link/base.py,sha256=BLdFUXTmzzE1dj3eBAnLf3JNSISI6fIODqI1EK2uNwI,33028
17
- libifstate/link/physical.py,sha256=cJiq-MCfy-3XQoU-OxzgfPZZtu_pJ8u2ioJgn9VYdGk,560
18
- libifstate/link/tun.py,sha256=m55o5cwO3h3DCLofUR-68fM4ggLoTKElp6ZJ2LrJSCc,959
19
- libifstate/link/veth.py,sha256=DtohI4PNlWoPijROf6AA6pikCvDhFXh15zQSWQBbfsA,1989
20
- libifstate/neighbour/__init__.py,sha256=FJJpbJvqnxvOEii6QDMYzW5jQDEbiEy71GQOEbqaS48,2463
21
- libifstate/netns/__init__.py,sha256=lKv43oEcpVUmL494MNOOG_Uh4ght50uu8n8nb9TIwsA,9077
22
- libifstate/parser/__init__.py,sha256=byz1W0G7UewVc5FFie-ti3UZjGK3-75wHIaOeq0oySQ,88
23
- libifstate/parser/base.py,sha256=4Y_kaUysbgQRM9u_Kg3Lr_5GWFGdTXDmP4EC3McsNzU,4628
24
- libifstate/parser/yaml.py,sha256=u9D9nPfVhYADTd01EYxFYIywnTBccNF1solOcGeLUjM,1345
25
- libifstate/routing/__init__.py,sha256=DBX8wv386rMii2gZf9n-8vbPikSqqaZO1NoHJX8Bjn4,19666
26
- libifstate/sysctl/__init__.py,sha256=L2gkoLnac_HM6RbCaKmsEuDTNj2m7U4Rjw42TEti0kg,3074
27
- libifstate/tc/__init__.py,sha256=inPdampCOIr_4oKNB3awqMkW0Eh4fpPh9jvSba6sPVg,12092
28
- libifstate/wireguard/__init__.py,sha256=vM7vxioV9vQ3gq9SE_URf97ZfGhLsxNiam16y_gXZ5E,6225
29
- libifstate/xdp/__init__.py,sha256=X1xhEIGng7R5d5F4KsChykT2g6H-XBRWbWABijoYDQA,7208
30
- schema/ifstate.conf.schema.json,sha256=-ACPDuqnemSbdoy2ZapMpUzXzabrLPoruE7BO7P080Q,195193
31
- ifstate-1.11.9.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
32
- ifstate-1.11.9.dist-info/METADATA,sha256=hxlJKKkeBkbm4m7y4Ddi3ujTDpMwzN_OMbF2P0ls5qE,1385
33
- ifstate-1.11.9.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
34
- ifstate-1.11.9.dist-info/entry_points.txt,sha256=HF6jX7Uu_nF1Ly-J9uEPeiRapOxnM6LuHsb2y6Mt-k4,52
35
- ifstate-1.11.9.dist-info/top_level.txt,sha256=A7peI7aKBaM69fsiSPvMbL3rzTKZZr5qDxKC-pHMGdE,19
36
- ifstate-1.11.9.dist-info/RECORD,,