ifstate 1.13.7__py3-none-any.whl → 2.0.0rc2__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.
hooks/wrapper.sh ADDED
@@ -0,0 +1,50 @@
1
+ #!/bin/sh
2
+
3
+ # ifstate: wrapper to run hooks
4
+
5
+ # debugging
6
+ export IFS_VERBOSE=${verbose}
7
+ if [ "$$IFS_VERBOSE" = 1 ]; then
8
+ set -x
9
+ fi
10
+
11
+ # return codes
12
+ export IFS_RC_OK="${rc_ok}"
13
+ export IFS_RC_ERROR="${rc_error}"
14
+ export IFS_RC_STARTED="${rc_started}"
15
+ export IFS_RC_STOPPED="${rc_stopped}"
16
+ export IFS_RC_CHANGED="${rc_changed}"
17
+
18
+ # generic environment variables
19
+ export IFS_SCRIPT="${script}"
20
+ export IFS_RUNDIR="${rundir}"
21
+
22
+ export IFS_IFNAME="${ifname}"
23
+ export IFS_INDEX="${index}"
24
+ export IFS_NETNS="${netns}"
25
+ export IFS_VRF="${vrf}"
26
+
27
+ # hook arguments
28
+ ${args}
29
+
30
+ # run hook NetNS and VRF aware
31
+ if [ -z "$$IFS_NETNS" ]; then
32
+ if [ -z "$$IFS_VRF" ]; then
33
+ # just exec the script
34
+ exec "$$IFS_SCRIPT" "$$@"
35
+ else
36
+ # exec in VRF
37
+ exec ip vrf exec "$$IFS_VRF" "$$IFS_SCRIPT" "$$@"
38
+ fi
39
+ else
40
+ if [ -z "$$IFS_VRF" ]; then
41
+ # exec in NetNS
42
+ exec ip netns exec "$$IFS_NETNS" "$$IFS_SCRIPT" "$$@"
43
+ else
44
+ # exec in NetNS->VRF
45
+ exec ip -n "$$IFS_NETNS" vrf exec "$$IFS_VRF" "$$IFS_SCRIPT" "$$@"
46
+ fi
47
+ fi
48
+
49
+ # somthing gone wrong
50
+ return $$IFS_RC_ERROR
ifstate/ifstate.py CHANGED
@@ -21,6 +21,7 @@ import yaml
21
21
  class Actions():
22
22
  CHECK = "check"
23
23
  APPLY = "apply"
24
+ IDENTIFY = "identify"
24
25
  SHOW = "show"
25
26
  SHOWALL = "showall"
26
27
  VRRP = "vrrp"
@@ -31,6 +32,7 @@ class Actions():
31
32
  ACTIONS_HELP = {
32
33
  "CHECK" : "dry run update the network config",
33
34
  "APPLY" : "update the network config",
35
+ "IDENTIFY" : "show interface attributes available for identification",
34
36
  "SHOW" : "show running network config",
35
37
  "SHOWALL" : "show running network config (more settings)",
36
38
  "VRRP" : "run as keepalived notify script",
@@ -134,6 +136,8 @@ def main():
134
136
  help="be more quiet, print only warnings and errors")
135
137
  parser.add_argument("-s", "--soft-schema", action="store_true",
136
138
  help="ignore schema validation errors, expect ifstatecli to trigger internal exceptions")
139
+ parser.add_argument("-S", "--show-secrets", action="store_true",
140
+ help="show secrets when dumping config")
137
141
  parser.add_argument("-c", "--config", type=str,
138
142
  default="/etc/ifstate/config.yml", help="configuration YaML filename")
139
143
  subparsers = parser.add_subparsers(
@@ -180,13 +184,16 @@ def main():
180
184
 
181
185
  ifslog = IfStateLogging(lvl, action=args.action)
182
186
 
183
- if args.action in [Actions.SHOW, Actions.SHOWALL]:
187
+ if args.action in [Actions.IDENTIFY, Actions.SHOW, Actions.SHOWALL]:
184
188
  # preserve dict order on python 3.7+
185
189
  if sys.version_info >= (3, 7):
186
190
  yaml.add_representer(
187
191
  dict, lambda self, data: yaml.representer.SafeRepresenter.represent_dict(self, data.items()))
188
192
  ifs = IfState()
189
- print(yaml.dump(ifs.show(args.action == Actions.SHOWALL)))
193
+ if args.action == Actions.IDENTIFY:
194
+ print(yaml.dump(ifs.identify()))
195
+ else:
196
+ print(yaml.dump(ifs.show(args.action == Actions.SHOWALL, args.show_secrets)))
190
197
 
191
198
  ifslog.quit()
192
199
  exit(0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ifstate
3
- Version: 1.13.7
3
+ Version: 2.0.0rc2
4
4
  Summary: Manage host interface settings in a declarative manner
5
5
  Home-page: https://ifstate.net/
6
6
  Author: Thomas Liske
@@ -9,7 +9,7 @@ License: GPL3+
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: jsonschema
12
- Requires-Dist: pyroute2
12
+ Requires-Dist: pyroute2!=0.9.3
13
13
  Requires-Dist: pyyaml
14
14
  Requires-Dist: setproctitle
15
15
  Provides-Extra: shell
@@ -0,0 +1,38 @@
1
+ hooks/wrapper.sh,sha256=ipCmvcadJbXTT6oUR7BIhT5uglITnHfiAdm44vydZuw,1101
2
+ ifstate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ ifstate/ifstate.py,sha256=ioiM9xgVWdrpTpK_UxJdaJFlmpFM9R-2_d5pq9_Hepo,9272
4
+ ifstate/shell.py,sha256=7_JFpi4icr9MijynDzbb0v5mxhFsng6PCC4m3uQ255A,2177
5
+ ifstate/vrrp.py,sha256=FJ9b1eJseTtZFfknHU-xV68Qz7cPrRrc5PTcjUVj-fY,5953
6
+ ifstate-2.0.0rc2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
7
+ libifstate/__init__.py,sha256=REX94ISXtdyY24_cc26Od5D2xGnz2U8dYNtRRMjRu1M,33194
8
+ libifstate/exception.py,sha256=5i59BZdl56J_sNJbyU9n6uHuUNJEyDOO4FJ-neDn9Ds,2608
9
+ libifstate/log.py,sha256=XVoZdwdQoWsjuupFIuG6OP0OrBpXpx7oqyAaUhQ-nJk,4553
10
+ libifstate/util.py,sha256=1CGVgaEj7L4At9FPWcDQLwSe027XSKoxFHIdkS5spSA,11354
11
+ libifstate/address/__init__.py,sha256=zIGM7UWXSvoijHMD06DfS2CDtiHpiJlqDgJG7Dl1IPE,3222
12
+ libifstate/bpf/__init__.py,sha256=NVzaunTmJU2PbIQg9eWBMKpFgLh3EnD3ujNa7Yt6rNc,7699
13
+ libifstate/bpf/ctypes.py,sha256=kLZJHZlba09Vc-tbsJAcKpDwdoNO2IjlYVLCopawHmA,4274
14
+ libifstate/bpf/map.py,sha256=cLHNMvRBDNW2yVCEf3z242_oRdU0HqVbFEYVkKXng0w,10818
15
+ libifstate/brport/__init__.py,sha256=NzdA8F4hr2se1bXKNnyKZbvOFlCWBq_cdjwsL1H0Y-o,2964
16
+ libifstate/fdb/__init__.py,sha256=9dpL5n8ct3CaA-z8I6ZEkD3yL6yWJQeq3fpIe9pc2zw,6486
17
+ libifstate/hook/__init__.py,sha256=OPUyhrr-eT1UNp8ryYoJyvi4WWqL2Ql33szir673Td4,7379
18
+ libifstate/link/__init__.py,sha256=QZggoC-bIscqwVedqVycaSqS1CmXB3Bx3m2FZei8Q_4,115
19
+ libifstate/link/base.py,sha256=dazZVPRMyXms-ICiL7VOCUXwZnqNr1X6R2NjTlm2nEU,34384
20
+ libifstate/link/physical.py,sha256=ectyXVchCHqWZR8qsjAD9667wjQbGW1OUKLQheTUFys,594
21
+ libifstate/link/tun.py,sha256=SWkscSAgIk3DWFPWHo9Cmokhj88rO1_sR7Z0Qlg2xGA,993
22
+ libifstate/link/veth.py,sha256=Sv5WZMMsefYFz9I0BQSZyKytCQYxv0vjwAnB7FYHR1A,2283
23
+ libifstate/neighbour/__init__.py,sha256=FJJpbJvqnxvOEii6QDMYzW5jQDEbiEy71GQOEbqaS48,2463
24
+ libifstate/netns/__init__.py,sha256=4DWFsgkT84foKoE0ZIbCIAqezDZzOBf8Ka1OAZS_T9E,10164
25
+ libifstate/parser/__init__.py,sha256=byz1W0G7UewVc5FFie-ti3UZjGK3-75wHIaOeq0oySQ,88
26
+ libifstate/parser/base.py,sha256=EV7uJYdVke3a2ghkUZ6ZeaIeVQAPVS4rFnYhebXeQm0,5932
27
+ libifstate/parser/yaml.py,sha256=MC0kmwqt3P45z61fb_wfUqoj0iZyhFYkdPyr0UqMSZA,1415
28
+ libifstate/routing/__init__.py,sha256=EvuS0GC5s7up92DYwSGhkohqhCQKPJpvs-alY4WAQws,25326
29
+ libifstate/sysctl/__init__.py,sha256=EF52CdOOkVSUFR2t21A99KlG1-PjsD4qOiceQC4eI24,3074
30
+ libifstate/tc/__init__.py,sha256=inPdampCOIr_4oKNB3awqMkW0Eh4fpPh9jvSba6sPVg,12092
31
+ libifstate/wireguard/__init__.py,sha256=cpXqcZK_xNexwhz-1OqN1RQe21NaNVgF-adhXU6mv14,8797
32
+ libifstate/xdp/__init__.py,sha256=X1xhEIGng7R5d5F4KsChykT2g6H-XBRWbWABijoYDQA,7208
33
+ schema/2/ifstate.conf.schema.json,sha256=_ltHBK8iTy8xAhJapQIMmUavlXbDCcOI3R2EhjrMWa0,218618
34
+ ifstate-2.0.0rc2.dist-info/METADATA,sha256=Hgol7xinIWq2lp6D7PNFpgrZ9_lnqBgspEko0tzIaK4,1607
35
+ ifstate-2.0.0rc2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
+ ifstate-2.0.0rc2.dist-info/entry_points.txt,sha256=HF6jX7Uu_nF1Ly-J9uEPeiRapOxnM6LuHsb2y6Mt-k4,52
37
+ ifstate-2.0.0rc2.dist-info/top_level.txt,sha256=A7peI7aKBaM69fsiSPvMbL3rzTKZZr5qDxKC-pHMGdE,19
38
+ ifstate-2.0.0rc2.dist-info/RECORD,,
libifstate/__init__.py CHANGED
@@ -2,6 +2,7 @@ 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
5
+ from libifstate.hook import Hooks
5
6
  from libifstate.neighbour import Neighbours
6
7
  from libifstate.routing import Tables, Rules, RTLookups
7
8
  from libifstate.parser import Parser
@@ -35,7 +36,7 @@ except ModuleNotFoundError:
35
36
  pass
36
37
 
37
38
  from libifstate.netns import NetNameSpace, prepare_netns, LinkRegistry, get_netns_instances
38
- from libifstate.util import logger, IfStateLogging, LinkDependency
39
+ from libifstate.util import logger, root_iw, IfStateLogging, LinkDependency, IDENTIFY_LOOKUPS
39
40
  from libifstate.exception import FeatureMissingError, LinkCircularLinked, LinkNoConfigFound, ParserValidationError
40
41
  from ipaddress import ip_network, ip_interface
41
42
  from jsonschema import validate, ValidationError, FormatChecker
@@ -48,8 +49,7 @@ import json
48
49
  import errno
49
50
  import logging
50
51
 
51
- __version__ = "1.13.7"
52
-
52
+ __version__ = "2.0.0rc2"
53
53
 
54
54
  class IfState():
55
55
  def __init__(self):
@@ -61,6 +61,8 @@ class IfState():
61
61
  self.ignore = {}
62
62
  self.features = {
63
63
  'brport': True,
64
+ 'devicetree': os.access('/sys/firmware/devicetree', os.R_OK),
65
+ 'iw': root_iw is not None,
64
66
  'link': True,
65
67
  'sysctl': os.access('/proc/sys/net', os.R_OK),
66
68
  'ethtool': not ethtool_path is None,
@@ -81,7 +83,7 @@ class IfState():
81
83
  def update(self, ifstates, soft_schema):
82
84
  # check config schema
83
85
  schema = json.loads(pkgutil.get_data(
84
- "libifstate", "../schema/ifstate.conf.schema.json"))
86
+ "libifstate", "../schema/{}/ifstate.conf.schema.json".format(__version__.split('.')[0])))
85
87
  try:
86
88
  validate(ifstates, schema, format_checker=FormatChecker())
87
89
  except ValidationError as ex:
@@ -104,10 +106,10 @@ class IfState():
104
106
 
105
107
  # add interface defaults
106
108
  if 'defaults' in ifstates:
107
- self.defaults = ifstates['defaults']
109
+ self.defaults = ifstates['parameters']['defaults']
108
110
 
109
111
  # add ignore list items
110
- self.ignore.update(ifstates['ignore'])
112
+ self.ignore.update(ifstates['parameters']['ignore'])
111
113
  self.ipaddr_ignore = set()
112
114
  for ip in self.ignore.get('ipaddr', []):
113
115
  self.ipaddr_ignore.add(ip_network(ip))
@@ -116,7 +118,10 @@ class IfState():
116
118
  self.fdb_ignore = re.compile('|'.join(self.ignore.get('fdb')))
117
119
 
118
120
  # save cshaper profiles
119
- self.cshaper_profiles = ifstates['cshaper']
121
+ self.cshaper_profiles = ifstates['parameters']['cshaper']
122
+
123
+ # prepare hooks
124
+ self.hooks = Hooks(ifstates['parameters'].get('hooks', {}))
120
125
 
121
126
  # build link registry over all named netns
122
127
  self.link_registry = LinkRegistry(self.ignore.get('ifname', []), self.root_netns)
@@ -134,17 +139,15 @@ class IfState():
134
139
  self._update(self.namespaces[netns_name], netns_ifstates)
135
140
 
136
141
  def _update(self, netns, ifstates):
137
- # parse options
138
- if 'options' in ifstates:
139
- # parse global sysctl settings
140
- if 'sysctl' in ifstates['options']:
141
- for proto in ifstates['options']['sysctl'].keys():
142
- if proto in ['all', 'default']:
143
- netns.sysctl.add(
144
- proto, ifstates['options']['sysctl'][proto])
145
- else:
146
- netns.sysctl.add_global(
147
- proto, ifstates['options']['sysctl'][proto])
142
+ # parse network sysctl settings
143
+ if 'sysctl' in ifstates:
144
+ for proto in ifstates['sysctl'].keys():
145
+ if proto in ['all', 'default']:
146
+ netns.sysctl.add(
147
+ proto, ifstates['sysctl'][proto])
148
+ else:
149
+ netns.sysctl.add_global(
150
+ proto, ifstates['sysctl'][proto])
148
151
 
149
152
  # load BPF programs
150
153
  if 'bpf' in ifstates:
@@ -157,8 +160,7 @@ class IfState():
157
160
  netns.bpf_progs.add(name, config)
158
161
 
159
162
  # add interfaces from config
160
- for ifstate in ifstates['interfaces']:
161
- name = ifstate['name']
163
+ for name, ifstate in ifstates['interfaces'].items():
162
164
  kind = ifstate['link']['kind']
163
165
  defaults = self.get_defaults(
164
166
  ifname=name,
@@ -189,7 +191,7 @@ class IfState():
189
191
  link.update(ifstate['link'])
190
192
  if link:
191
193
  netns.links[name] = Link(self,
192
- netns, name, link, ethtool, ifstate.get('vrrp'), ifstate.get('brport'))
194
+ netns, name, link, ifstate.get('identify', {}), ethtool, ifstate.get('hooks', []), ifstate.get('vrrp'), ifstate.get('brport'))
193
195
  else:
194
196
  netns.links[name] = None
195
197
 
@@ -224,7 +226,7 @@ class IfState():
224
226
  netns.sysctl.add(name, ifstate['sysctl'])
225
227
 
226
228
  if 'cshaper' in ifstate:
227
- profile_name = ifstate['cshaper'].get(
229
+ profile_name = ifstate['parameters']['cshaper'].get(
228
230
  'profile', 'default')
229
231
  logger.debug('cshaper profile {} enabled'.format(profile_name),
230
232
  extra={'iface': name, 'netns': netns})
@@ -246,7 +248,7 @@ class IfState():
246
248
  'qdisc': cshaper_profile['ingress_qdisc'],
247
249
  }
248
250
  }
249
- ifb_state['tc']['qdisc']['bandwidth'] = ifstate['cshaper'].get(
251
+ ifb_state['tc']['qdisc']['bandwidth'] = ifstate['parameters']['cshaper'].get(
250
252
  'ingress', 'unlimited')
251
253
 
252
254
  if 'vrrp' in ifstate:
@@ -279,10 +281,10 @@ class IfState():
279
281
  ]
280
282
  }
281
283
 
282
- ifstate['tc']['qdisc']['bandwidth'] = ifstate['cshaper'].get(
284
+ ifstate['tc']['qdisc']['bandwidth'] = ifstate['parameters']['cshaper'].get(
283
285
  'egress', 'unlimited')
284
286
 
285
- del ifstate['cshaper']
287
+ del ifstate['parameters']['cshaper']
286
288
 
287
289
  if 'tc' in ifstate:
288
290
  netns.tc[name] = TC(
@@ -611,6 +613,8 @@ class IfState():
611
613
  if ifname in netns.wireguard:
612
614
  netns.wireguard[ifname].apply(do_apply)
613
615
 
616
+ self.hooks.apply(link, do_apply)
617
+
614
618
  def _apply_routing(self, do_apply, netns, by_vrrp, vrrp_type, vrrp_name, vrrp_state):
615
619
  if not netns.tables is None:
616
620
  netns.tables.apply(self.ignore.get('routes', []), do_apply, by_vrrp, vrrp_type, vrrp_name, vrrp_state)
@@ -618,36 +622,82 @@ class IfState():
618
622
  if not netns.rules is None:
619
623
  netns.rules.apply(self.ignore.get('rules', []), do_apply, by_vrrp, vrrp_type, vrrp_name, vrrp_state)
620
624
 
621
- def show(self, showall=False):
625
+ def identify(self):
626
+ root_config = self._identify_netns(self.root_netns)
627
+ netns_instances = get_netns_instances()
628
+ if len(netns_instances) > 0:
629
+ netns_identifies = {}
630
+ for netns in netns_instances:
631
+ netns_identify = self._identify_netns(netns)
632
+ if netns_identify is not None:
633
+ netns_identifies[netns.netns] = netns_identify
634
+
635
+ if netns_identifies:
636
+ return {**root_config, 'namespaces': netns_identifies}
637
+
638
+ return {**root_config}
639
+
640
+ def _identify_netns(self, netns):
641
+ ifs_links = {}
642
+ for ipr_link in netns.ipr.get_links():
643
+ name = ipr_link.get_attr('IFLA_IFNAME')
644
+ # skip links on ignore list
645
+ if name != 'lo' and not any(re.match(regex, name) for regex in Parser._default_ifstates['parameters']['ignore']['ifname_builtin']):
646
+ info = ipr_link.get_attr('IFLA_LINKINFO')
647
+ if info is None:
648
+ kind = None
649
+ else:
650
+ kind = info.get_attr('IFLA_INFO_KIND')
651
+
652
+ if kind is not None:
653
+ continue
654
+
655
+ ifs_link = {
656
+ 'identify': {
657
+ },
658
+ }
659
+
660
+ for attr, lookup in IDENTIFY_LOOKUPS.items():
661
+ value = lookup(netns, ipr_link)
662
+ if value is not None:
663
+ ifs_link['identify'][attr] = value
664
+
665
+ ifs_links[name] = ifs_link
666
+
667
+ if ifs_links:
668
+ return {**{'interfaces': ifs_links}}
669
+ else:
670
+ return None
671
+
672
+ def show(self, showall=False, show_secrets=False):
622
673
  if showall:
623
674
  defaults = deepcopy(Parser._default_ifstates)
624
675
  else:
625
676
  defaults = {}
626
677
 
627
678
  ipaddr_ignore = []
628
- for ip in Parser._default_ifstates['ignore']['ipaddr_builtin']:
679
+ for ip in Parser._default_ifstates['parameters']['ignore']['ipaddr_builtin']:
629
680
  ipaddr_ignore.append(ip_network(ip))
630
681
 
631
- root_config = self._show_netns(self.root_netns, showall, ipaddr_ignore)
682
+ root_config = self._show_netns(self.root_netns, showall, show_secrets, ipaddr_ignore)
632
683
  netns_instances = get_netns_instances()
633
684
  if len(netns_instances) > 0:
634
685
  netns_configs = {}
635
686
  for netns in netns_instances:
636
- netns_configs[netns.netns] = self._show_netns(netns, showall, ipaddr_ignore)
687
+ netns_configs[netns.netns] = self._show_netns(netns, showall, show_secrets, ipaddr_ignore)
637
688
 
638
689
  return {**defaults, **root_config, 'namespaces': netns_configs}
639
690
 
640
691
 
641
692
  return {**defaults, **root_config}
642
693
 
643
- def _show_netns(self, netns, showall, ipaddr_ignore):
644
- ifs_links = []
694
+ def _show_netns(self, netns, showall, show_secrets, ipaddr_ignore):
695
+ ifs_links = {}
645
696
  for ipr_link in netns.ipr.get_links():
646
697
  name = ipr_link.get_attr('IFLA_IFNAME')
647
698
  # skip links on ignore list
648
- if name != 'lo' and not any(re.match(regex, name) for regex in Parser._default_ifstates['ignore']['ifname_builtin']):
699
+ if name != 'lo' and not any(re.match(regex, name) for regex in Parser._default_ifstates['parameters']['ignore']['ifname_builtin']):
649
700
  ifs_link = {
650
- 'name': name,
651
701
  'addresses': [],
652
702
  'link': {
653
703
  'state': ipr_link['state'],
@@ -687,15 +737,13 @@ class IfState():
687
737
  addr = ipr_link.get_attr('IFLA_ADDRESS')
688
738
  if not addr is None:
689
739
  ifs_link['link']['address'] = addr
690
- permaddr = netns.ipr.get_permaddr(name)
691
- if not permaddr is None:
692
- if addr is None:
693
- ifs_link['link']['addr'] = permaddr
694
- elif addr != permaddr:
695
- ifs_link['link']['permaddr'] = permaddr
696
- businfo = netns.ipr.get_businfo(name)
697
- if not businfo is None:
698
- ifs_link['link']['businfo'] = businfo
740
+
741
+ # add identify section for physical links
742
+ ifs_link['identify'] = {}
743
+ for attr, func in IDENTIFY_LOOKUPS.items():
744
+ value = func(netns, ipr_link)
745
+ if value:
746
+ ifs_link['identify'][attr] = value
699
747
 
700
748
  # add device group if not 0
701
749
  group = ipr_link.get_attr('IFLA_GROUP')
@@ -722,6 +770,9 @@ class IfState():
722
770
 
723
771
  brport.BRPort.show(netns.ipr, showall, ipr_link['index'], ifs_link)
724
772
 
773
+ if ifs_link['link']['kind'] == 'wireguard':
774
+ wireguard.WireGuard.show(netns, showall, show_secrets, name, ifs_link)
775
+
725
776
  if name == 'lo':
726
777
  if ifs_link['addresses'] == Parser._default_lo_link['addresses']:
727
778
  del(ifs_link['addresses'])
@@ -730,13 +781,13 @@ class IfState():
730
781
  del(ifs_link['link'])
731
782
 
732
783
  if len(ifs_link) > 1:
733
- ifs_links.append(ifs_link)
784
+ ifs_links['lo'] = ifs_link
734
785
  else:
735
- ifs_links.append(ifs_link)
786
+ ifs_links[name] = ifs_link
736
787
 
737
788
  routing = {
738
- 'routes': Tables(netns).show_routes(Parser._default_ifstates['ignore']['routes_builtin']),
739
- 'rules': Rules(netns).show_rules(Parser._default_ifstates['ignore']['rules_builtin']),
789
+ 'routes': Tables(netns).show_routes(Parser._default_ifstates['parameters']['ignore']['routes_builtin']),
790
+ 'rules': Rules(netns).show_rules(Parser._default_ifstates['parameters']['ignore']['rules_builtin']),
740
791
  }
741
792
 
742
793
  return {**{'interfaces': ifs_links, 'routing': routing}}
@@ -0,0 +1,195 @@
1
+ from libifstate.util import get_netns_run_dir, dump_yaml_file, slurp_yaml_file, RUN_BASE_DIR
2
+ from libifstate.util import logger
3
+
4
+ import logging
5
+ from pathlib import Path
6
+ import os
7
+ import pkgutil
8
+ from shlex import quote
9
+ import shutil
10
+ from string import Template
11
+ import subprocess
12
+ import tempfile
13
+
14
+
15
+ HOOK_DIR = '/etc/ifstate/hooks'
16
+ HOOK_WRAPPER = Template(pkgutil.get_data("libifstate", "../hooks/wrapper.sh").decode("utf-8"))
17
+
18
+ RC_OK = 0
19
+ RC_ERROR = 1
20
+ RC_STARTED = 2
21
+ # it is the same value, but there are two constants to make it more convinient
22
+ RC_STOPPED = RC_STARTED
23
+ RC_CHANGED = 3
24
+
25
+ def _open_perm(path, flags):
26
+ return os.open(path, flags, 0o600)
27
+
28
+ class Hook():
29
+ def __init__(self, name, script, provides=[], after=[]):
30
+ self.name = name
31
+
32
+ if script[0] == '/':
33
+ self.script = Path(script).as_posix()
34
+ else:
35
+ self.script = Path(HOOK_DIR, script).as_posix()
36
+
37
+ self.provides = provides
38
+ self.after = after
39
+
40
+ def start(self, link, args, run_dir, do_apply):
41
+ wrapper_fn = f"{run_dir}/wrapper.sh"
42
+
43
+ with open(wrapper_fn, "w", opener=_open_perm) as fh:
44
+ template_vars = {
45
+ 'verbose': 1 if logger.getEffectiveLevel() == logging.DEBUG else '',
46
+ 'script': self.script,
47
+ 'ifname': link.settings.get('ifname'),
48
+ 'index': link.idx,
49
+ 'netns': link.netns.netns or '',
50
+ 'vrf': '',
51
+ 'rundir': run_dir,
52
+ 'rc_ok': RC_OK,
53
+ 'rc_error': RC_ERROR,
54
+ 'rc_started': RC_STARTED,
55
+ 'rc_stopped': RC_STOPPED,
56
+ 'rc_changed': RC_CHANGED,
57
+ }
58
+
59
+ if link.get_if_attr('IFLA_INFO_SLAVE_KIND') == 'vrf':
60
+ template_vars['vrf'] = link.settings.get('master')
61
+
62
+ args_list = []
63
+ for k, v in args.items():
64
+ args_list.append(f'export IFS_ARG_{k.upper()}={quote(v)}')
65
+ template_vars['args'] = "\n".join(args_list)
66
+
67
+ try:
68
+ fh.write(HOOK_WRAPPER.substitute(template_vars))
69
+ except KeyError as ex:
70
+ logger.error("Failed to prepare wrapper for hook {}: variable {} unknown".format(self.name, str(ex)))
71
+ return
72
+ except ValueError as ex:
73
+ logger.error("Failed to prepare wrapper for hook {}: {}".format(self.name, str(ex)))
74
+ return
75
+
76
+ try:
77
+ if do_apply:
78
+ subprocess.run(['/bin/sh', wrapper_fn, "start"], timeout=3, check=True)
79
+ else:
80
+ subprocess.run(['/bin/sh', wrapper_fn, "check-start"], timeout=3, check=True)
81
+
82
+ return RC_OK
83
+ except (FileNotFoundError, PermissionError) as ex:
84
+ logger.error("Failed executing hook {}: {}".format(self.name, str(ex)))
85
+ return RC_ERROR
86
+ except subprocess.TimeoutExpired as ex:
87
+ logger.error("Running hook {} has timed out.".format(self.name))
88
+ return RC_ERROR
89
+ except subprocess.CalledProcessError as ex:
90
+ if ex.returncode < 0:
91
+ logger.warning("Hook {} got signal {}".format(hook_name, -1 * ex.returncode))
92
+ return RC_ERROR
93
+
94
+ return ex.returncode
95
+
96
+ @staticmethod
97
+ def stop(link, hook_name, run_dir, do_apply):
98
+ wrapper_fn = f"{run_dir}/wrapper.sh"
99
+
100
+ try:
101
+ if do_apply:
102
+ subprocess.run(['/bin/sh', wrapper_fn, "stop"], timeout=3, check=True)
103
+ else:
104
+ subprocess.run(['/bin/sh', wrapper_fn, "check-stop"], timeout=3, check=True)
105
+
106
+ return RC_OK
107
+ except (FileNotFoundError, PermissionError) as ex:
108
+ logger.error("Failed executing hook {}: {}".format(hook_name, str(ex)))
109
+ return RC_ERROR
110
+ except subprocess.TimeoutExpired as ex:
111
+ logger.error("Running hook {} has timed out.".format(hook_name))
112
+ return RC_ERROR
113
+ except subprocess.CalledProcessError as ex:
114
+ if ex.returncode < 0:
115
+ logger.warning("Hook {} got signal {}".format(hook_name, -1 * ex.returncode))
116
+ return RC_ERROR
117
+
118
+ assert(run_dir.startswith(RUN_BASE_DIR))
119
+
120
+ try:
121
+ shutil.rmtree(run_dir)
122
+ except FileNotFoundError:
123
+ pass
124
+ except OSError as err:
125
+ logger.error("Failed cleanup hook rundir {}: {}".format(run_dir, str(err)))
126
+
127
+ return ex.returncode
128
+
129
+ class Hooks():
130
+ def __init__(self, ifstate):
131
+ self.hooks = {}
132
+ for hook, opts in ifstate.items():
133
+ if 'script' in opts:
134
+ self.hooks[hook] = Hook(hook, **opts)
135
+ else:
136
+ self.hooks[hook] = Hook(hook, script=hook, **opts)
137
+
138
+ def apply(self, link, do_apply):
139
+ run_dir = get_netns_run_dir('hooks', link.netns, str(link.idx))
140
+
141
+ state_fn = f"{run_dir}/state"
142
+ old_state = slurp_yaml_file(state_fn, default=[])
143
+ run_state = []
144
+
145
+ # Stop any running hooks which should not run any more or have other
146
+ # parameters - hooks are identified by all of their settings. Keep
147
+ # the rundir for any hook already running.
148
+ for entry in old_state:
149
+ if not entry.get('hook') in link.hooks:
150
+ rc = Hook.stop(link, entry["hook"]["name"], entry["rundir"], do_apply)
151
+ if rc == RC_OK:
152
+ pass
153
+ elif rc == RC_STOPPED:
154
+ logger.log_del('hooks', '- {}'.format(entry["hook"]["name"]))
155
+ else:
156
+ run_state.append(entry)
157
+ logger.log_err('hooks', '! {}'.format(entry["hook"]["name"]))
158
+ else:
159
+ hook = next((hook for hook in link.hooks if hook == entry["hook"]), None)
160
+ # tepmorary keep mapping between running and configured hook dict
161
+ hook["__rundir"] = entry["rundir"]
162
+
163
+ try:
164
+ if not link.hooks:
165
+ return
166
+
167
+ for hook in link.hooks:
168
+ if not hook["name"] in self.hooks:
169
+ logger.warning("Hook {} for {} is unknown!".format(link.settings.get('ifname'), hook))
170
+ continue
171
+
172
+ hook_run_dir = hook.get("__rundir")
173
+ # hook is not running, yet
174
+ if hook_run_dir is None:
175
+ hook_run_dir = tempfile.mkdtemp(prefix="hook_", dir=run_dir)
176
+ # hook was already running
177
+ else:
178
+ del(hook["__rundir"])
179
+ rc = self.hooks[hook["name"]].start(link, hook.get('args', {}), hook_run_dir, do_apply)
180
+
181
+ if rc == RC_OK:
182
+ logger.log_ok('hooks', '= {}'.format(hook["name"]))
183
+ elif rc == RC_STARTED:
184
+ logger.log_add('hooks', '+ {}'.format(hook["name"]))
185
+ elif rc == RC_CHANGED:
186
+ logger.log_change('hooks', '~ {}'.format(hook["name"]))
187
+ else:
188
+ logger.log_err('hooks', '! {}'.format(hook["name"]))
189
+
190
+ run_state.append({
191
+ "hook": hook,
192
+ "rundir": hook_run_dir,
193
+ })
194
+ finally:
195
+ dump_yaml_file(state_fn, run_state)
@@ -1,5 +1,4 @@
1
1
  import libifstate.link.base
2
- import libifstate.link.dsa
3
2
  import libifstate.link.physical
4
3
  import libifstate.link.tun
5
4
  import libifstate.link.veth