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.
- {ifstate-1.11.9 → ifstate-1.13.0}/PKG-INFO +1 -1
- {ifstate-1.11.9 → ifstate-1.13.0}/ifstate/ifstate.py +39 -27
- ifstate-1.13.0/ifstate/vrrp.py +166 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/ifstate.egg-info/PKG-INFO +1 -1
- {ifstate-1.11.9 → ifstate-1.13.0}/ifstate.egg-info/SOURCES.txt +1 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/__init__.py +8 -12
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/address/__init__.py +8 -4
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/exception.py +5 -1
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/link/__init__.py +1 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/link/base.py +26 -18
- ifstate-1.13.0/libifstate/link/dsa.py +10 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/link/veth.py +6 -2
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/log.py +20 -8
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/parser/base.py +7 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/parser/yaml.py +3 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/routing/__init__.py +4 -4
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/util.py +19 -1
- {ifstate-1.11.9 → ifstate-1.13.0}/schema/ifstate.conf.schema.json +75 -1
- ifstate-1.11.9/ifstate/vrrp.py +0 -113
- {ifstate-1.11.9 → ifstate-1.13.0}/LICENSE +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/README.md +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/ifstate/__init__.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/ifstate/shell.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/ifstate.egg-info/dependency_links.txt +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/ifstate.egg-info/entry_points.txt +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/ifstate.egg-info/requires.txt +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/ifstate.egg-info/top_level.txt +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/bpf/__init__.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/bpf/ctypes.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/bpf/map.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/brport/__init__.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/fdb/__init__.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/link/physical.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/link/tun.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/neighbour/__init__.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/netns/__init__.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/parser/__init__.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/sysctl/__init__.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/tc/__init__.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/wireguard/__init__.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/libifstate/xdp/__init__.py +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/setup.cfg +0 -0
- {ifstate-1.11.9 → ifstate-1.13.0}/setup.py +0 -0
@@ -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"
|
31
|
-
"APPLY"
|
32
|
-
"SHOW"
|
33
|
-
"SHOWALL"
|
34
|
-
"VRRP"
|
35
|
-
"VRRP_FIFO": "run as keepalived notify_fifo_script",
|
36
|
-
"
|
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(
|
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
|
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,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
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
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,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
|
-
|
250
|
-
|
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 =
|
255
|
-
|
256
|
-
|
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
|
-
|
472
|
-
self.
|
473
|
-
|
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
|
-
|
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
|
-
|
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'],
|
@@ -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
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from libifstate.util import logger, IfStateLogging
|
2
|
-
from libifstate.exception import
|
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.
|
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
|
},
|
ifstate-1.11.9/ifstate/vrrp.py
DELETED
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|