ifstate 1.12.0__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.12.0
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
@@ -1,11 +1,11 @@
1
1
  ifstate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- ifstate/ifstate.py,sha256=1xXJmVt_w6X7K5KTqfeTDZ1y-wJxgtrtLJJ1bUFN5I0,8031
2
+ ifstate/ifstate.py,sha256=jNWVc-XPZOKwD--xSJvAXZP-DEomljmGXtMf8SAdmmU,8770
3
3
  ifstate/shell.py,sha256=7_JFpi4icr9MijynDzbb0v5mxhFsng6PCC4m3uQ255A,2177
4
- ifstate/vrrp.py,sha256=GOSwQFf3IrcWF5DbQCJQqZ7p33TaFMEVp8LwyRDolWM,4016
5
- libifstate/__init__.py,sha256=6GFuekZ-Wh3fD_1vMqXTkXSZYuQWFKBt-uFG7bLzOfk,31175
4
+ ifstate/vrrp.py,sha256=m-vkm_Yx82qx5RmTnouYceWpa1agziBUHr87yK9tBro,5542
5
+ libifstate/__init__.py,sha256=zAn9f1jay5LI9JWSwgzqLK8RmTIks4cYELHolpX60i0,30792
6
6
  libifstate/exception.py,sha256=5i59BZdl56J_sNJbyU9n6uHuUNJEyDOO4FJ-neDn9Ds,2608
7
7
  libifstate/log.py,sha256=XVoZdwdQoWsjuupFIuG6OP0OrBpXpx7oqyAaUhQ-nJk,4553
8
- libifstate/util.py,sha256=NYY6Yoy4t42z_Q_Auy_vRHZ_MWT_XXCBZGZj61YMuWQ,10111
8
+ libifstate/util.py,sha256=vqNaa67QZ2G07bQNAisTyUKbVmp0JGkvEGgbKVTtmYA,10579
9
9
  libifstate/address/__init__.py,sha256=zIGM7UWXSvoijHMD06DfS2CDtiHpiJlqDgJG7Dl1IPE,3222
10
10
  libifstate/bpf/__init__.py,sha256=NVzaunTmJU2PbIQg9eWBMKpFgLh3EnD3ujNa7Yt6rNc,7699
11
11
  libifstate/bpf/ctypes.py,sha256=kLZJHZlba09Vc-tbsJAcKpDwdoNO2IjlYVLCopawHmA,4274
@@ -21,17 +21,17 @@ libifstate/link/veth.py,sha256=dEnkhg1AU-CFOFJN-U68G6FJ74CNW9G4RXsa5vLs-qY,2114
21
21
  libifstate/neighbour/__init__.py,sha256=FJJpbJvqnxvOEii6QDMYzW5jQDEbiEy71GQOEbqaS48,2463
22
22
  libifstate/netns/__init__.py,sha256=lKv43oEcpVUmL494MNOOG_Uh4ght50uu8n8nb9TIwsA,9077
23
23
  libifstate/parser/__init__.py,sha256=byz1W0G7UewVc5FFie-ti3UZjGK3-75wHIaOeq0oySQ,88
24
- libifstate/parser/base.py,sha256=4Y_kaUysbgQRM9u_Kg3Lr_5GWFGdTXDmP4EC3McsNzU,4628
25
- libifstate/parser/yaml.py,sha256=u9D9nPfVhYADTd01EYxFYIywnTBccNF1solOcGeLUjM,1345
26
- libifstate/routing/__init__.py,sha256=-97i7wGCs9m0pF7b3IiBhqCYrdXsLv9DRUh0gDiMFl4,19670
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
27
  libifstate/sysctl/__init__.py,sha256=L2gkoLnac_HM6RbCaKmsEuDTNj2m7U4Rjw42TEti0kg,3074
28
28
  libifstate/tc/__init__.py,sha256=inPdampCOIr_4oKNB3awqMkW0Eh4fpPh9jvSba6sPVg,12092
29
29
  libifstate/wireguard/__init__.py,sha256=vM7vxioV9vQ3gq9SE_URf97ZfGhLsxNiam16y_gXZ5E,6225
30
30
  libifstate/xdp/__init__.py,sha256=X1xhEIGng7R5d5F4KsChykT2g6H-XBRWbWABijoYDQA,7208
31
- schema/ifstate.conf.schema.json,sha256=z19rf8Uj0_Lvohn9W-EioHYB1bCWU3btgltqvMLDzRk,197749
32
- ifstate-1.12.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
33
- ifstate-1.12.0.dist-info/METADATA,sha256=1zu-VuiinDDWPw1SvQgx7C1QfbXtTwJEtANRtUAI5xM,1385
34
- ifstate-1.12.0.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
35
- ifstate-1.12.0.dist-info/entry_points.txt,sha256=HF6jX7Uu_nF1Ly-J9uEPeiRapOxnM6LuHsb2y6Mt-k4,52
36
- ifstate-1.12.0.dist-info/top_level.txt,sha256=A7peI7aKBaM69fsiSPvMbL3rzTKZZr5qDxKC-pHMGdE,19
37
- ifstate-1.12.0.dist-info/RECORD,,
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: setuptools (74.1.2)
2
+ Generator: setuptools (75.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
libifstate/__init__.py CHANGED
@@ -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.12.0"
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():
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)
@@ -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:
@@ -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"
@@ -3015,6 +3015,32 @@
3015
3015
  "description": "specifies the maximum number of FDB entries (0: none)",
3016
3016
  "default": 0
3017
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
+ },
3018
3044
  "vxlan_proxy": {
3019
3045
  "type": "integer",
3020
3046
  "minimum": 0,