ifstate 1.9.0__tar.gz → 1.10.1__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.9.0 → ifstate-1.10.1}/PKG-INFO +9 -3
- {ifstate-1.9.0 → ifstate-1.10.1}/README.md +1 -1
- {ifstate-1.9.0 → ifstate-1.10.1}/ifstate.egg-info/PKG-INFO +9 -3
- {ifstate-1.9.0 → ifstate-1.10.1}/ifstate.egg-info/SOURCES.txt +1 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/__init__.py +58 -11
- ifstate-1.10.1/libifstate/fdb/__init__.py +174 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/link/base.py +122 -7
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/netns/__init__.py +34 -13
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/parser/base.py +29 -1
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/sysctl/__init__.py +15 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/util.py +63 -6
- {ifstate-1.9.0 → ifstate-1.10.1}/schema/ifstate.conf.schema.json +487 -278
- {ifstate-1.9.0 → ifstate-1.10.1}/LICENSE +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/ifstate/__init__.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/ifstate/ifstate.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/ifstate/shell.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/ifstate.egg-info/dependency_links.txt +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/ifstate.egg-info/entry_points.txt +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/ifstate.egg-info/requires.txt +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/ifstate.egg-info/top_level.txt +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/address/__init__.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/bpf/__init__.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/bpf/ctypes.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/bpf/map.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/brport/__init__.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/exception.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/link/__init__.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/link/physical.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/link/tun.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/link/veth.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/log.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/neighbour/__init__.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/parser/__init__.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/parser/yaml.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/routing/__init__.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/tc/__init__.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/wireguard/__init__.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/libifstate/xdp/__init__.py +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/setup.cfg +0 -0
- {ifstate-1.9.0 → ifstate-1.10.1}/setup.py +0 -0
@@ -1,21 +1,27 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ifstate
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.10.1
|
4
4
|
Summary: Manage host interface settings in a declarative manner
|
5
5
|
Home-page: https://ifstate.net/
|
6
6
|
Author: Thomas Liske
|
7
7
|
Author-email: thomas@fiasko-nw.net
|
8
8
|
License: GPL3+
|
9
9
|
Description-Content-Type: text/markdown
|
10
|
+
License-File: LICENSE
|
11
|
+
Requires-Dist: jsonschema
|
12
|
+
Requires-Dist: pyroute2
|
13
|
+
Requires-Dist: pyyaml
|
14
|
+
Requires-Dist: setproctitle
|
10
15
|
Provides-Extra: shell
|
16
|
+
Requires-Dist: pygments; extra == "shell"
|
11
17
|
Provides-Extra: wireguard
|
12
|
-
|
18
|
+
Requires-Dist: wgnlpy; extra == "wireguard"
|
13
19
|
|
14
20
|
# IfState
|
15
21
|
|
16
22
|
[](https://badge.fury.io/py/ifstate)
|
17
23
|
|
18
|
-
A python
|
24
|
+
A python tool to configure (linux) host interfaces in a declarative manner.
|
19
25
|
It is a frontend for the kernel netlink protocol using
|
20
26
|
[pyroute2](https://pyroute2.org/) and aims to be as powerful as the
|
21
27
|
iproute2/bridge/ethtool/tc/wireguard commands.
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[](https://badge.fury.io/py/ifstate)
|
4
4
|
|
5
|
-
A python
|
5
|
+
A python tool to configure (linux) host interfaces in a declarative manner.
|
6
6
|
It is a frontend for the kernel netlink protocol using
|
7
7
|
[pyroute2](https://pyroute2.org/) and aims to be as powerful as the
|
8
8
|
iproute2/bridge/ethtool/tc/wireguard commands.
|
@@ -1,21 +1,27 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ifstate
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.10.1
|
4
4
|
Summary: Manage host interface settings in a declarative manner
|
5
5
|
Home-page: https://ifstate.net/
|
6
6
|
Author: Thomas Liske
|
7
7
|
Author-email: thomas@fiasko-nw.net
|
8
8
|
License: GPL3+
|
9
9
|
Description-Content-Type: text/markdown
|
10
|
+
License-File: LICENSE
|
11
|
+
Requires-Dist: jsonschema
|
12
|
+
Requires-Dist: pyroute2
|
13
|
+
Requires-Dist: pyyaml
|
14
|
+
Requires-Dist: setproctitle
|
10
15
|
Provides-Extra: shell
|
16
|
+
Requires-Dist: pygments; extra == "shell"
|
11
17
|
Provides-Extra: wireguard
|
12
|
-
|
18
|
+
Requires-Dist: wgnlpy; extra == "wireguard"
|
13
19
|
|
14
20
|
# IfState
|
15
21
|
|
16
22
|
[](https://badge.fury.io/py/ifstate)
|
17
23
|
|
18
|
-
A python
|
24
|
+
A python tool to configure (linux) host interfaces in a declarative manner.
|
19
25
|
It is a frontend for the kernel netlink protocol using
|
20
26
|
[pyroute2](https://pyroute2.org/) and aims to be as powerful as the
|
21
27
|
iproute2/bridge/ethtool/tc/wireguard commands.
|
@@ -1,11 +1,14 @@
|
|
1
1
|
from libifstate.exception import LinkDuplicate
|
2
2
|
from libifstate.link.base import ethtool_path, Link
|
3
3
|
from libifstate.address import Addresses
|
4
|
+
from libifstate.fdb import FDB
|
4
5
|
from libifstate.neighbour import Neighbours
|
5
6
|
from libifstate.routing import Tables, Rules, RTLookups
|
6
7
|
from libifstate.parser import Parser
|
7
8
|
from libifstate.tc import TC
|
8
9
|
from libifstate.exception import netlinkerror_classes
|
10
|
+
import bisect
|
11
|
+
import pyroute2
|
9
12
|
|
10
13
|
from pyroute2.netlink.rtnl.ifaddrmsg import IFA_F_PERMANENT
|
11
14
|
try:
|
@@ -39,11 +42,12 @@ from copy import deepcopy
|
|
39
42
|
import os
|
40
43
|
import pkgutil
|
41
44
|
import re
|
45
|
+
import secrets
|
42
46
|
import json
|
43
47
|
import errno
|
44
48
|
import logging
|
45
49
|
|
46
|
-
__version__ = "1.
|
50
|
+
__version__ = "1.10.1"
|
47
51
|
|
48
52
|
|
49
53
|
class IfState():
|
@@ -119,8 +123,13 @@ class IfState():
|
|
119
123
|
self._update(self.root_netns, ifstates)
|
120
124
|
if 'namespaces' in ifstates:
|
121
125
|
self.namespaces = {}
|
126
|
+
self.new_namespaces = []
|
122
127
|
for netns_name, netns_ifstates in ifstates['namespaces'].items():
|
128
|
+
is_new = netns_name not in pyroute2.netns.listnetns()
|
123
129
|
self.namespaces[netns_name] = NetNameSpace(netns_name)
|
130
|
+
if is_new:
|
131
|
+
self.new_namespaces.append(netns_name)
|
132
|
+
self.link_registry.inventory_netns(self.namespaces[netns_name])
|
124
133
|
self._update(self.namespaces[netns_name], netns_ifstates)
|
125
134
|
|
126
135
|
def _update(self, netns, ifstates):
|
@@ -128,10 +137,13 @@ class IfState():
|
|
128
137
|
if 'options' in ifstates:
|
129
138
|
# parse global sysctl settings
|
130
139
|
if 'sysctl' in ifstates['options']:
|
131
|
-
for
|
132
|
-
if
|
140
|
+
for proto in ifstates['options']['sysctl'].keys():
|
141
|
+
if proto in ['all', 'default']:
|
133
142
|
netns.sysctl.add(
|
134
|
-
|
143
|
+
proto, ifstates['options']['sysctl'][proto])
|
144
|
+
else:
|
145
|
+
netns.sysctl.add_global(
|
146
|
+
proto, ifstates['options']['sysctl'][proto])
|
135
147
|
|
136
148
|
# load BPF programs
|
137
149
|
if 'bpf' in ifstates:
|
@@ -168,6 +180,11 @@ class IfState():
|
|
168
180
|
elif defaults.get('clear_addresses', False):
|
169
181
|
netns.addresses[name] = Addresses(netns, name, [])
|
170
182
|
|
183
|
+
if 'fdb' in ifstate:
|
184
|
+
netns.fdb[name] = FDB(netns, name, ifstate['fdb'])
|
185
|
+
elif defaults.get('clear_fdb', False):
|
186
|
+
netns.fdb[name] = FDB(netns, name, [])
|
187
|
+
|
171
188
|
if 'neighbours' in ifstate:
|
172
189
|
netns.neighbours[name] = Neighbours(netns, name, ifstate['neighbours'])
|
173
190
|
elif defaults.get('clear_neighbours', False):
|
@@ -363,7 +380,7 @@ class IfState():
|
|
363
380
|
raise LinkCircularLinked()
|
364
381
|
|
365
382
|
# can be done right away
|
366
|
-
r.append(t)
|
383
|
+
r.append( sorted(t) )
|
367
384
|
# and cleaned up
|
368
385
|
d=dict(((k, v-t) for k, v in d.items() if v))
|
369
386
|
return r
|
@@ -404,7 +421,7 @@ class IfState():
|
|
404
421
|
|
405
422
|
# create and destroy namespaces to match config
|
406
423
|
if not by_vrrp and self.namespaces is not None:
|
407
|
-
prepare_netns(do_apply, self.namespaces.keys())
|
424
|
+
prepare_netns(do_apply, self.namespaces.keys(), self.new_namespaces)
|
408
425
|
logger.info("")
|
409
426
|
|
410
427
|
# get link dependency tree
|
@@ -459,7 +476,7 @@ class IfState():
|
|
459
476
|
# create/modify links in order of dependencies
|
460
477
|
logger.info("configure interfaces...")
|
461
478
|
for stage in stages:
|
462
|
-
for link_dep in
|
479
|
+
for link_dep in stage:
|
463
480
|
logger.info(" {}".format(link_dep))
|
464
481
|
if link_dep.netns is None:
|
465
482
|
self._apply_iface(do_apply, self.root_netns, link_dep.ifname, by_vrrp, vrrp_type, vrrp_name, vrrp_state)
|
@@ -490,6 +507,13 @@ class IfState():
|
|
490
507
|
logger.info("configure sysctl settings...")
|
491
508
|
had_sysctl = True
|
492
509
|
netns.sysctl.apply(iface, do_apply)
|
510
|
+
|
511
|
+
if netns.sysctl.has_globals():
|
512
|
+
if not had_sysctl:
|
513
|
+
logger.info("configure sysctl settings...")
|
514
|
+
had_sysctl = True
|
515
|
+
netns.sysctl.apply_globals(do_apply)
|
516
|
+
|
493
517
|
return had_sysctl
|
494
518
|
|
495
519
|
def _apply_iface(self, do_apply, netns, ifname, by_vrrp, vrrp_type, vrrp_name, vrrp_state):
|
@@ -534,6 +558,9 @@ class IfState():
|
|
534
558
|
netns.addresses[ifname].apply(self.ipaddr_ignore, self.ignore.get(
|
535
559
|
'ipaddr_dynamic', True), do_apply)
|
536
560
|
|
561
|
+
if ifname in netns.fdb:
|
562
|
+
netns.fdb[ifname].apply(do_apply)
|
563
|
+
|
537
564
|
if ifname in netns.neighbours:
|
538
565
|
netns.neighbours[ifname].apply(do_apply)
|
539
566
|
|
@@ -645,12 +672,23 @@ class IfState():
|
|
645
672
|
ifs_link['link'][attr] = ref
|
646
673
|
|
647
674
|
mtu = ipr_link.get_attr('IFLA_MTU')
|
648
|
-
if not mtu is None
|
649
|
-
|
675
|
+
if not mtu is None:
|
676
|
+
if not mtu in [1500, 65536] or name == 'lo':
|
677
|
+
ifs_link['link']['mtu'] = mtu
|
650
678
|
|
651
679
|
brport.BRPort.show(netns.ipr, showall, ipr_link['index'], ifs_link)
|
652
680
|
|
653
|
-
|
681
|
+
if name == 'lo':
|
682
|
+
if ifs_link['addresses'] == Parser._default_lo_link['addresses']:
|
683
|
+
del(ifs_link['addresses'])
|
684
|
+
|
685
|
+
if ifs_link['link'] == Parser._default_lo_link['link']:
|
686
|
+
del(ifs_link['link'])
|
687
|
+
|
688
|
+
if len(ifs_link) > 1:
|
689
|
+
ifs_links.append(ifs_link)
|
690
|
+
else:
|
691
|
+
ifs_links.append(ifs_link)
|
654
692
|
|
655
693
|
routing = {
|
656
694
|
'routes': Tables(netns).show_routes(Parser._default_ifstates['ignore']['routes_builtin']),
|
@@ -659,7 +697,6 @@ class IfState():
|
|
659
697
|
|
660
698
|
return {**{'interfaces': ifs_links, 'routing': routing}}
|
661
699
|
|
662
|
-
|
663
700
|
def get_defaults(self, **kwargs):
|
664
701
|
for default in self.defaults:
|
665
702
|
for match in default['match']:
|
@@ -675,3 +712,13 @@ class IfState():
|
|
675
712
|
return default
|
676
713
|
|
677
714
|
return {}
|
715
|
+
|
716
|
+
def gen_unique_ifname(self):
|
717
|
+
'''
|
718
|
+
Get a random unique ifname over all namespaces and configured ifnames.
|
719
|
+
'''
|
720
|
+
while True:
|
721
|
+
ifname = "ifs.tmp.{}".format(secrets.token_hex(3))
|
722
|
+
item = self.link_registry.get_link(ifname=ifname)
|
723
|
+
if item is None:
|
724
|
+
return ifname
|
@@ -0,0 +1,174 @@
|
|
1
|
+
from libifstate.util import logger, IfStateLogging
|
2
|
+
from libifstate.exception import netlinkerror_classes
|
3
|
+
from ipaddress import ip_address
|
4
|
+
from pyroute2.netlink.rtnl.ndmsg import NUD_NOARP, NUD_PERMANENT, NTF_SELF
|
5
|
+
from pyroute2.config import AF_BRIDGE
|
6
|
+
import pyroute2.netlink.rtnl.ndmsg
|
7
|
+
|
8
|
+
class FDB():
|
9
|
+
def __init__(self, netns, iface, fdb):
|
10
|
+
self.netns = netns
|
11
|
+
self.iface = iface
|
12
|
+
self.fdb = {}
|
13
|
+
self.state_mask = NUD_NOARP|NUD_PERMANENT
|
14
|
+
|
15
|
+
for entry in fdb:
|
16
|
+
lladdr = entry['lladdr'].lower()
|
17
|
+
_entry = {
|
18
|
+
'lladdr': lladdr,
|
19
|
+
}
|
20
|
+
|
21
|
+
if 'port' not in entry:
|
22
|
+
_entry['port'] = 8472
|
23
|
+
else:
|
24
|
+
_entry['port'] = entry['port']
|
25
|
+
|
26
|
+
if 'dst' in entry:
|
27
|
+
_entry['dst'] = str(ip_address(entry['dst']))
|
28
|
+
|
29
|
+
if 'state' in entry:
|
30
|
+
_entry['state'] = 0
|
31
|
+
for name, value in pyroute2.netlink.rtnl.ndmsg.states.items():
|
32
|
+
if name in entry['state']:
|
33
|
+
_entry['state'] |= value
|
34
|
+
|
35
|
+
if 'flags' in entry:
|
36
|
+
_entry['flags'] = 0
|
37
|
+
for name, value in pyroute2.netlink.rtnl.ndmsg.flags.items():
|
38
|
+
if name in entry['flags']:
|
39
|
+
_entry['flags'] |= value
|
40
|
+
else:
|
41
|
+
_entry['flags'] = NTF_SELF
|
42
|
+
|
43
|
+
for opt in ['nhid', 'src_vni', 'vni']:
|
44
|
+
if opt in entry:
|
45
|
+
_entry[opt] = entry[opt]
|
46
|
+
|
47
|
+
if not lladdr in self.fdb:
|
48
|
+
self.fdb[lladdr] = [_entry]
|
49
|
+
else:
|
50
|
+
self.fdb[lladdr].append(_entry)
|
51
|
+
|
52
|
+
def get_kernel_fdb(self):
|
53
|
+
# get fdb entries (NUD_NOARP|NUD_PERMANENT)
|
54
|
+
fdb = {}
|
55
|
+
for entry in self.netns.ipr.get_neighbours(ifindex=self.idx, family=AF_BRIDGE):
|
56
|
+
state = entry.get('state')
|
57
|
+
|
58
|
+
# look for permanent (local) or noarp (static) entries, only
|
59
|
+
if not state & self.state_mask:
|
60
|
+
continue
|
61
|
+
|
62
|
+
lladdr = entry.get_attr('NDA_LLADDR')
|
63
|
+
_entry = {
|
64
|
+
'lladdr': lladdr,
|
65
|
+
'state': state,
|
66
|
+
'flags': entry.get('flags')
|
67
|
+
}
|
68
|
+
|
69
|
+
attr = entry.get_attr('NDA_DST')
|
70
|
+
if attr is not None:
|
71
|
+
_entry['dst'] = str(ip_address(attr))
|
72
|
+
|
73
|
+
attr = entry.get_attr('NDA_PORT')
|
74
|
+
if attr is None or attr == 0:
|
75
|
+
_entry['port'] = 8472
|
76
|
+
else:
|
77
|
+
_entry['port'] = attr
|
78
|
+
|
79
|
+
for opt in ['nhid', 'src_vni', 'vni']:
|
80
|
+
attr = entry.get_attr(f"NDA_{opt.upper()}")
|
81
|
+
if attr is not None:
|
82
|
+
_entry[opt] = attr
|
83
|
+
|
84
|
+
if not lladdr in fdb:
|
85
|
+
fdb[lladdr] = [_entry]
|
86
|
+
else:
|
87
|
+
fdb[lladdr].append(_entry)
|
88
|
+
|
89
|
+
return fdb
|
90
|
+
|
91
|
+
def apply(self, do_apply):
|
92
|
+
logger.debug('getting fdb', extra={'iface': self.iface})
|
93
|
+
|
94
|
+
# get ifindex and lladdr
|
95
|
+
link = next(iter(self.netns.ipr.get_links(ifname=self.iface)), None)
|
96
|
+
|
97
|
+
if link == None:
|
98
|
+
logger.warning('link missing', extra={'iface': self.iface})
|
99
|
+
return
|
100
|
+
|
101
|
+
self.idx = link['index']
|
102
|
+
self.lladdr = link.get_attr('IFLA_ADDRESS')
|
103
|
+
|
104
|
+
# prepare default state for entries w/o state specified (depends on link type)
|
105
|
+
default_state = NUD_PERMANENT
|
106
|
+
|
107
|
+
linkinfo = link.get_attr('IFLA_LINKINFO')
|
108
|
+
if linkinfo and linkinfo.get_attr('IFLA_INFO_KIND') in ['vxlan']:
|
109
|
+
default_state |= NUD_NOARP
|
110
|
+
|
111
|
+
# configure fdb entries
|
112
|
+
ipr_entries = self.get_kernel_fdb()
|
113
|
+
for lladdr, entries in self.fdb.items():
|
114
|
+
for entry in entries:
|
115
|
+
# set default_state if missing
|
116
|
+
if not 'state' in entry:
|
117
|
+
entry['state'] = default_state
|
118
|
+
|
119
|
+
# check if fdb entry is already present
|
120
|
+
if lladdr in ipr_entries:
|
121
|
+
if entry in ipr_entries[lladdr]:
|
122
|
+
logger.log_ok('fdb', '= {}'.format(lladdr))
|
123
|
+
continue
|
124
|
+
|
125
|
+
# fdb entry needs to be added
|
126
|
+
logger.log_add('fdb', '+ {}'.format(lladdr))
|
127
|
+
|
128
|
+
# prepare arguments
|
129
|
+
args = {
|
130
|
+
'ifindex': self.idx,
|
131
|
+
'family': AF_BRIDGE
|
132
|
+
}
|
133
|
+
args.update(entry)
|
134
|
+
logger.debug("bridge fdb append: {}".format(
|
135
|
+
" ".join("{}={}".format(k, v) for k, v in args.items())))
|
136
|
+
|
137
|
+
if do_apply:
|
138
|
+
try:
|
139
|
+
self.netns.ipr.fdb("append", **args)
|
140
|
+
except Exception as err:
|
141
|
+
if not isinstance(err, netlinkerror_classes):
|
142
|
+
raise
|
143
|
+
logger.warning('add {} to fdb failed: {}'.format(
|
144
|
+
entry['lladdr'], err.args[1]))
|
145
|
+
|
146
|
+
# cleanup orphan fdb entries
|
147
|
+
ipr_entries = self.get_kernel_fdb()
|
148
|
+
for lladdr, entries in ipr_entries.items():
|
149
|
+
# ignore lladdr of the link
|
150
|
+
if lladdr == self.lladdr:
|
151
|
+
continue
|
152
|
+
|
153
|
+
for entry in entries:
|
154
|
+
# check if fdb entry is already present
|
155
|
+
if lladdr not in self.fdb or entry not in self.fdb[lladdr]:
|
156
|
+
logger.log_del('fdb', '- {}'.format(lladdr))
|
157
|
+
|
158
|
+
# prepare arguments
|
159
|
+
args = {
|
160
|
+
'ifindex': self.idx,
|
161
|
+
'family': AF_BRIDGE
|
162
|
+
}
|
163
|
+
args.update(entry)
|
164
|
+
logger.debug("bridge fdb del: {}".format(
|
165
|
+
" ".join("{}={}".format(k, v) for k, v in args.items())))
|
166
|
+
|
167
|
+
if do_apply:
|
168
|
+
try:
|
169
|
+
self.netns.ipr.fdb("del", **args)
|
170
|
+
except Exception as err:
|
171
|
+
if not isinstance(err, netlinkerror_classes):
|
172
|
+
raise
|
173
|
+
logger.warning('remove {} from fdb failed: {}'.format(
|
174
|
+
entry['lladdr'], err.args[1]))
|
@@ -80,6 +80,21 @@ class Link(ABC):
|
|
80
80
|
attr_value_lookup = {
|
81
81
|
'group': RTLookups.group,
|
82
82
|
}
|
83
|
+
attr_bind_kinds = [
|
84
|
+
'ip6tnl',
|
85
|
+
'tun',
|
86
|
+
'vti',
|
87
|
+
'vti6',
|
88
|
+
'vxlan',
|
89
|
+
'ipip',
|
90
|
+
'gre',
|
91
|
+
'gretap',
|
92
|
+
'ip6gre',
|
93
|
+
'ip6gretap',
|
94
|
+
'geneve',
|
95
|
+
'wireguard',
|
96
|
+
'xfrm',
|
97
|
+
]
|
83
98
|
|
84
99
|
def __new__(cls, *args, **kwargs):
|
85
100
|
cname = cls.__name__
|
@@ -170,6 +185,20 @@ class Link(ABC):
|
|
170
185
|
extra={'iface': self.settings['ifname'], 'netns': self.netns})
|
171
186
|
del(self.settings[attr])
|
172
187
|
|
188
|
+
if self.settings['kind'] in self.attr_bind_kinds:
|
189
|
+
if not 'bind_netns' in self.settings:
|
190
|
+
logger.debug('set bind_netns to current netns',
|
191
|
+
extra={'iface': self.settings['ifname'], 'netns': self.netns})
|
192
|
+
self.bind_netns = self.netns.netns
|
193
|
+
else:
|
194
|
+
self.bind_netns = self.settings['bind_netns']
|
195
|
+
elif 'bind_netns' in self.settings:
|
196
|
+
logger.warning('Ignoring not supported link attribute "bind_netns" for %s link.',
|
197
|
+
self.settings['kind'],
|
198
|
+
extra={'iface': self.settings['ifname'], 'netns': self.netns})
|
199
|
+
if 'bind_netns' in self.settings:
|
200
|
+
del(self.settings['bind_netns'])
|
201
|
+
|
173
202
|
def search_link_registry(self):
|
174
203
|
for args in self.link_registry_search_args:
|
175
204
|
item = self.ifstate.link_registry.get_link(**args)
|
@@ -317,9 +346,61 @@ class Link(ABC):
|
|
317
346
|
try:
|
318
347
|
with open(fn, 'w') as fh:
|
319
348
|
yaml.dump(self.ethtool[setting], fh)
|
320
|
-
except
|
349
|
+
except IOError as err:
|
321
350
|
logger.warning('failed write `{}`: {}'.format(fn, err.args[1]))
|
322
351
|
|
352
|
+
def get_bind_fn(self, netns_name, idx):
|
353
|
+
if netns_name is None:
|
354
|
+
dirname = "/run/ifstate/bind"
|
355
|
+
else:
|
356
|
+
dirname = "/run/ifstate/netns/{}/bind".format(netns_name)
|
357
|
+
|
358
|
+
try:
|
359
|
+
os.makedirs(dirname, exist_ok=True)
|
360
|
+
except:
|
361
|
+
pass
|
362
|
+
|
363
|
+
return "{}/{}.mount".format(dirname, idx)
|
364
|
+
|
365
|
+
def set_bind_state(self, state):
|
366
|
+
fn = self.get_bind_fn(self.netns.netns, self.idx)
|
367
|
+
try:
|
368
|
+
with open(fn, 'wb') as fh:
|
369
|
+
fh.write(state)
|
370
|
+
except IOError as err:
|
371
|
+
logger.warning('failed write `{}`: {}'.format(fn, err.args[1]))
|
372
|
+
|
373
|
+
def get_bind_netns(self):
|
374
|
+
if not hasattr(self, 'bind_netns'):
|
375
|
+
return None
|
376
|
+
|
377
|
+
if self.bind_netns is None:
|
378
|
+
return self.ifstate.root_netns
|
379
|
+
|
380
|
+
return self.ifstate.namespaces.get(self.bind_netns)
|
381
|
+
|
382
|
+
def bind_needs_recreate(self, item):
|
383
|
+
if not item.attributes['kind'] in self.attr_bind_kinds:
|
384
|
+
return False
|
385
|
+
|
386
|
+
bind_netns = self.get_bind_netns()
|
387
|
+
if bind_netns is None:
|
388
|
+
logger.warning('bind_netns "%s" is unknown',
|
389
|
+
self.bind_netns,
|
390
|
+
extra={
|
391
|
+
'iface': self.settings['ifname'],
|
392
|
+
'netns': self.netns})
|
393
|
+
return False
|
394
|
+
|
395
|
+
fn = self.get_bind_fn(item.netns.netns, item.index)
|
396
|
+
try:
|
397
|
+
with open(fn, 'rb') as fh:
|
398
|
+
state = fh.read()
|
399
|
+
except IOError:
|
400
|
+
return True
|
401
|
+
|
402
|
+
return state != bind_netns.mount
|
403
|
+
|
323
404
|
def has_vrrp(self):
|
324
405
|
return not self.vrrp is None
|
325
406
|
|
@@ -366,6 +447,14 @@ class Link(ABC):
|
|
366
447
|
# get interface from registry
|
367
448
|
item = self.search_link_registry()
|
368
449
|
|
450
|
+
# check if bind_netns option requires a recreate
|
451
|
+
if item is not None and self.bind_needs_recreate(item):
|
452
|
+
self.idx = item.index
|
453
|
+
self.recreate(do_apply, sysctl, excpts)
|
454
|
+
|
455
|
+
self.settings = osettings
|
456
|
+
return excpts
|
457
|
+
|
369
458
|
# move interface into netns if required
|
370
459
|
if item is not None and item.netns.netns != self.netns.netns:
|
371
460
|
logger.log_change('netns')
|
@@ -382,7 +471,7 @@ class Link(ABC):
|
|
382
471
|
return excpts
|
383
472
|
|
384
473
|
if item is not None:
|
385
|
-
self.idx = item.
|
474
|
+
self.idx = item.index
|
386
475
|
|
387
476
|
if self.idx is not None:
|
388
477
|
self.iface = next(iter(item.netns.ipr.get_links(self.idx)), None)
|
@@ -421,24 +510,50 @@ class Link(ABC):
|
|
421
510
|
return excpts
|
422
511
|
|
423
512
|
def create(self, do_apply, sysctl, excpts, oper="add"):
|
424
|
-
logger.log_add('link')
|
513
|
+
logger.log_add('link', oper)
|
514
|
+
|
515
|
+
settings = copy.deepcopy(self.settings)
|
516
|
+
bind_netns = self.get_bind_netns()
|
517
|
+
if bind_netns is not None and bind_netns.netns != self.netns.netns:
|
518
|
+
logger.debug("handle link binding", extra={
|
519
|
+
'iface': self.settings['ifname'],
|
520
|
+
'netns': self.netns})
|
521
|
+
settings['ifname'] = self.ifstate.gen_unique_ifname()
|
425
522
|
|
426
523
|
logger.debug("ip link add: {}".format(
|
427
|
-
" ".join("{}={}".format(k, v) for k, v in
|
524
|
+
" ".join("{}={}".format(k, v) for k, v in settings.items())))
|
428
525
|
if do_apply:
|
429
526
|
try:
|
430
527
|
# set state later
|
431
|
-
state =
|
528
|
+
state = settings.pop('state', None)
|
432
529
|
|
433
530
|
# prevent altname conflict
|
434
531
|
self.prevent_altname_conflict()
|
435
532
|
|
436
533
|
# add link
|
437
|
-
|
534
|
+
if bind_netns is None or bind_netns.netns == self.netns.netns:
|
535
|
+
self.netns.ipr.link('add', **(settings))
|
536
|
+
link = next(iter(self.netns.ipr.get_links(
|
537
|
+
ifname=settings['ifname'])), None)
|
538
|
+
if link is not None:
|
539
|
+
item = self.ifstate.link_registry.add_link(self.netns, link)
|
540
|
+
# add and move link
|
541
|
+
else:
|
542
|
+
bind_netns.ipr.link('add', **(settings))
|
543
|
+
link = next(iter(bind_netns.ipr.get_links(
|
544
|
+
ifname=settings['ifname'])), None)
|
545
|
+
if link is not None:
|
546
|
+
item = self.ifstate.link_registry.add_link(bind_netns, link)
|
547
|
+
item.update_netns(self.netns)
|
548
|
+
item.update_ifname(self.settings['ifname'])
|
549
|
+
|
438
550
|
self.idx = next(iter(self.netns.ipr.link_lookup(
|
439
551
|
ifname=self.settings['ifname'])), None)
|
440
552
|
|
441
553
|
if self.idx is not None:
|
554
|
+
if bind_netns is not None:
|
555
|
+
self.set_bind_state(bind_netns.mount)
|
556
|
+
|
442
557
|
# set sysctl settings if required
|
443
558
|
sysctl.apply(self.settings['ifname'], do_apply)
|
444
559
|
|
@@ -466,7 +581,7 @@ class Link(ABC):
|
|
466
581
|
self.settings['ifname'], self.ethtool.keys(), do_apply)
|
467
582
|
|
468
583
|
def recreate(self, do_apply, sysctl, excpts):
|
469
|
-
logger.debug('
|
584
|
+
logger.debug('needs to be recreated', extra={
|
470
585
|
'iface': self.settings['ifname'], 'netns': self.netns})
|
471
586
|
if do_apply:
|
472
587
|
try:
|