ifstate 1.11.9__tar.gz → 1.13.0__tar.gz

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.
Files changed (43) hide show
  1. {ifstate-1.11.9 → ifstate-1.13.0}/PKG-INFO +1 -1
  2. {ifstate-1.11.9 → ifstate-1.13.0}/ifstate/ifstate.py +39 -27
  3. ifstate-1.13.0/ifstate/vrrp.py +166 -0
  4. {ifstate-1.11.9 → ifstate-1.13.0}/ifstate.egg-info/PKG-INFO +1 -1
  5. {ifstate-1.11.9 → ifstate-1.13.0}/ifstate.egg-info/SOURCES.txt +1 -0
  6. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/__init__.py +8 -12
  7. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/address/__init__.py +8 -4
  8. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/exception.py +5 -1
  9. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/link/__init__.py +1 -0
  10. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/link/base.py +26 -18
  11. ifstate-1.13.0/libifstate/link/dsa.py +10 -0
  12. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/link/veth.py +6 -2
  13. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/log.py +20 -8
  14. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/parser/base.py +7 -0
  15. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/parser/yaml.py +3 -0
  16. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/routing/__init__.py +4 -4
  17. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/util.py +19 -1
  18. {ifstate-1.11.9 → ifstate-1.13.0}/schema/ifstate.conf.schema.json +75 -1
  19. ifstate-1.11.9/ifstate/vrrp.py +0 -113
  20. {ifstate-1.11.9 → ifstate-1.13.0}/LICENSE +0 -0
  21. {ifstate-1.11.9 → ifstate-1.13.0}/README.md +0 -0
  22. {ifstate-1.11.9 → ifstate-1.13.0}/ifstate/__init__.py +0 -0
  23. {ifstate-1.11.9 → ifstate-1.13.0}/ifstate/shell.py +0 -0
  24. {ifstate-1.11.9 → ifstate-1.13.0}/ifstate.egg-info/dependency_links.txt +0 -0
  25. {ifstate-1.11.9 → ifstate-1.13.0}/ifstate.egg-info/entry_points.txt +0 -0
  26. {ifstate-1.11.9 → ifstate-1.13.0}/ifstate.egg-info/requires.txt +0 -0
  27. {ifstate-1.11.9 → ifstate-1.13.0}/ifstate.egg-info/top_level.txt +0 -0
  28. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/bpf/__init__.py +0 -0
  29. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/bpf/ctypes.py +0 -0
  30. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/bpf/map.py +0 -0
  31. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/brport/__init__.py +0 -0
  32. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/fdb/__init__.py +0 -0
  33. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/link/physical.py +0 -0
  34. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/link/tun.py +0 -0
  35. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/neighbour/__init__.py +0 -0
  36. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/netns/__init__.py +0 -0
  37. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/parser/__init__.py +0 -0
  38. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/sysctl/__init__.py +0 -0
  39. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/tc/__init__.py +0 -0
  40. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/wireguard/__init__.py +0 -0
  41. {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/xdp/__init__.py +0 -0
  42. {ifstate-1.11.9 → ifstate-1.13.0}/setup.cfg +0 -0
  43. {ifstate-1.11.9 → ifstate-1.13.0}/setup.py +0 -0
@@ -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
@@ -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)
@@ -0,0 +1,166 @@
1
+ from libifstate.util import logger, IfStateLogging
2
+
3
+ import atexit
4
+ from copy import deepcopy
5
+ import logging
6
+ import multiprocessing as mp
7
+ import threading
8
+ import os
9
+ import re
10
+ from setproctitle import setproctitle
11
+ import signal
12
+ import subprocess
13
+ import sys
14
+
15
+ class VrrpFifoProcess():
16
+ '''
17
+ Process for vrrp group/instance configuration.
18
+ '''
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()
27
+
28
+ def vrrp_update(self, vrrp_state):
29
+ self.state_queue.put(vrrp_state)
30
+
31
+ def dequeue(self):
32
+ while True:
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()
39
+ return
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
+
55
+
56
+ class VrrpStates():
57
+ '''
58
+ Tracks processes and states for vrrp groups/instances.
59
+ '''
60
+ def __init__(self, ifs_config):
61
+ self.ifs_config = ifs_config
62
+ self.processes = {}
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')
72
+
73
+ def update(self, vrrp_type, vrrp_name, vrrp_state):
74
+ '''
75
+ Updates the state for a group/instance. A new VrrpFifoProcess is spawned on-demand
76
+ if a group/instance is called the first time.
77
+ '''
78
+ key = (vrrp_type, vrrp_name)
79
+ if not key in self.processes:
80
+ worker_args = self.worker_args + key
81
+ self.processes[key] = VrrpFifoProcess(worker_args)
82
+ self.processes[key].start()
83
+
84
+ self.states[key] = vrrp_state
85
+ self.processes[key].vrrp_update(vrrp_state)
86
+
87
+ def reconfigure(self, *argv):
88
+ '''
89
+ Reconfigure all known groups/instances using their last known state by spawning
90
+ new worker Processes.
91
+ '''
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
+
101
+ for key, state in self.states.items():
102
+ worker_args = self.worker_args + key
103
+ self.processes[key].vrrp_update(None)
104
+ self.processes[key] = VrrpFifoProcess(worker_args)
105
+ self.processes[key].start()
106
+ self.processes[key].vrrp_update(self.states[key])
107
+
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)
120
+
121
+ signal.signal(signal.SIGHUP, vrrp_states.reconfigure)
122
+ signal.signal(signal.SIGPIPE, signal.SIG_IGN)
123
+ signal.signal(signal.SIGTERM, vrrp_states.cleanup_run)
124
+
125
+ try:
126
+ os.makedirs("/run/libifstate/vrrp", exist_ok=True)
127
+
128
+ with open(vrrp_states.pid_file, "w", encoding="utf-8") as fh:
129
+ fh.write(args_fifo)
130
+ atexit.register(vrrp_states.cleanup_run)
131
+
132
+ ifs_config.dump_config(vrrp_states.cfg_file)
133
+ except IOError as err:
134
+ logger.exception(f'failed to write pid file {vrrp_states.pid_file}: {err}')
135
+
136
+ try:
137
+ status_pattern = re.compile(
138
+ r'(group|instance) "([^"]+)" (unknown|fault|backup|master|stop)( \d+)?$', re.IGNORECASE)
139
+
140
+ with open(args_fifo) as fifo:
141
+ logger.debug("entering fifo loop...")
142
+ for line in fifo:
143
+ m = status_pattern.match(line.strip())
144
+ if m:
145
+ vrrp_type = m.group(1)
146
+ vrrp_name = m.group(2)
147
+ vrrp_state = m.group(3)
148
+
149
+ vrrp_states.update(vrrp_type, vrrp_name, vrrp_state)
150
+ else:
151
+ logger.warning(f'failed to parse fifo input: {line.strip()}')
152
+ mp.active_children()
153
+ finally:
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
@@ -24,6 +24,7 @@ libifstate/brport/__init__.py
24
24
  libifstate/fdb/__init__.py
25
25
  libifstate/link/__init__.py
26
26
  libifstate/link/base.py
27
+ libifstate/link/dsa.py
27
28
  libifstate/link/physical.py
28
29
  libifstate/link/tun.py
29
30
  libifstate/link/veth.py
@@ -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:
@@ -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
@@ -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'],
@@ -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
@@ -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
 
@@ -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)
@@ -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
@@ -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)
@@ -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,113 +0,0 @@
1
- from libifstate.util import logger, IfStateLogging
2
-
3
- from copy import deepcopy
4
- import logging
5
- import multiprocessing as mp
6
- import os
7
- import re
8
- from setproctitle import setproctitle
9
- import signal
10
-
11
- class VrrpFifoProcess(mp.Process):
12
- '''
13
- Process for vrrp group/instance configuration.
14
- '''
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)
22
-
23
- def vrrp_update(self, vrrp_state):
24
- self.queue.put(vrrp_state)
25
-
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
- while True:
32
- vrrp_state = self.queue.get()
33
- if vrrp_state is None:
34
- logger.info('terminating')
35
- return
36
- else:
37
- ifstate = deepcopy(self.ifstate)
38
- ifstate.apply(self.vrrp_type, self.vrrp_name, vrrp_state)
39
-
40
- class VrrpStates():
41
- '''
42
- Tracks processes and states for vrrp groups/instances.
43
- '''
44
- def __init__(self, ifstate, log_level):
45
- self.ifstate = ifstate
46
- self.log_level = log_level
47
- self.processes = {}
48
- self.states = {}
49
-
50
- def update(self, vrrp_type, vrrp_name, vrrp_state):
51
- '''
52
- Updates the state for a group/instance. A new VrrpFifoProcess is spawned on-demand
53
- if a group/instance is called the first time.
54
- '''
55
- key = (vrrp_type, vrrp_name)
56
- if not key in self.processes:
57
- self.processes[key] = VrrpFifoProcess(vrrp_type, vrrp_name, self.ifstate, self.log_level)
58
- self.processes[key].start()
59
-
60
- self.states[key] = vrrp_state
61
- self.processes[key].vrrp_update(vrrp_state)
62
-
63
- def reconfigure(self, ifstate):
64
- '''
65
- Reconfigure all known groups/instances using their last known state by spawning
66
- new worker Processes.
67
- '''
68
- self.ifstate = ifstate
69
- for key, state in self.states.items():
70
- self.processes[key].vrrp_update(None)
71
- self.processes[key] = VrrpFifoProcess(*key, ifstate, self.log_level)
72
- self.processes[key].start()
73
- self.processes[key].vrrp_update(self.states[key])
74
-
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)
78
-
79
- signal.signal(signal.SIGHUP, ifs_config.sighup_handler)
80
- signal.signal(signal.SIGPIPE, signal.SIG_IGN)
81
- signal.signal(signal.SIGTERM, signal.SIG_IGN)
82
-
83
- pid_file = f"/run/libifstate/vrrp/{os.getpid()}.pid"
84
- try:
85
- os.makedirs("/run/libifstate/vrrp", exist_ok=True)
86
-
87
- with open(pid_file, "w", encoding="utf-8") as fh:
88
- fh.write(args_fifo)
89
- except IOError as err:
90
- logger.exception(f'failed to write pid file {pid_file}: {err}')
91
-
92
- try:
93
- status_pattern = re.compile(
94
- r'(group|instance) "([^"]+)" (unknown|fault|backup|master)( \d+)?$', re.IGNORECASE)
95
-
96
- with open(args_fifo) as fifo:
97
- logger.debug("entering fifo loop...")
98
- for line in fifo:
99
- m = status_pattern.match(line.strip())
100
- if m:
101
- vrrp_type = m.group(1)
102
- vrrp_name = m.group(2)
103
- vrrp_state = m.group(3)
104
-
105
- vrrp_states.update(vrrp_type, vrrp_name, vrrp_state)
106
- else:
107
- logger.warning(f'failed to parse fifo input: {line.strip()}')
108
- mp.active_children()
109
- finally:
110
- try:
111
- os.remove(pid_file)
112
- except IOError:
113
- pass
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes