xref: /openbsd/regress/sbin/slaacd/Slaacctl.py (revision 097a140d)
1# $OpenBSD: Slaacctl.py,v 1.4 2021/04/14 12:32:56 bluhm Exp $
2
3# Copyright (c) 2017 Florian Obser <florian@openbsd.org>
4# Copyright (c) 2020 Alexander Bluhm <bluhm@openbsd.org>
5#
6# Permission to use, copy, modify, and distribute this software for any
7# purpose with or without fee is hereby granted, provided that the above
8# copyright notice and this permission notice appear in all copies.
9#
10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
18import pprint
19import subprocess
20import re
21
22class ShowInterface(object):
23	def __init__(self, ifname, sock, debug=0):
24		self.ifname = ifname
25		self.sock = sock
26		self.debug = debug
27		self.index = None
28		self.running = None
29		self.temporary = None
30		self.lladdr = None
31		self.linklocal = None
32		self.RAs = []
33		self.addr_proposals = []
34		self.def_router_proposals = []
35		self.rdns_proposals = []
36		self.out = subprocess.check_output(['slaacctl', '-s', self.sock,
37		    'sh', 'in', self.ifname], encoding='UTF-8')
38		self.parse(self.out)
39
40	def __str__(self):
41		rep = dict()
42		iface = dict()
43		rep[self.ifname] = iface
44		iface['index'] = self.index
45		iface['running'] = self.running
46		iface['temporary'] = self.temporary
47		iface['lladdr'] = self.lladdr
48		iface['linklocal'] = self.linklocal
49		iface['RAs'] = self.RAs
50		iface['addr_proposals'] = self.addr_proposals
51		iface['def_router_proposals'] = self.def_router_proposals
52		iface['rdns_proposals'] = self.rdns_proposals
53		return (pprint.pformat(rep, indent=4))
54
55	def parse(self, str):
56		state = 'START'
57		ra = None
58		prefix = None
59		addr_proposal = None
60		def_router_proposal = None
61		rdns_proposal = None
62		lines = str.splitlines()
63		for line in lines:
64			if self.debug == 1:
65				print(line)
66			if re.match("^\s*$", line):
67				pass
68			elif state == 'START':
69				ifname = re.match("^(\w+):", line).group(1)
70				if ifname != self.ifname:
71					raise ValueError("unexpected interface "
72					    + "name: " + ifname)
73				state = 'IFINFO'
74			elif state == 'IFINFO':
75				m = re.match("^\s+index:\s+(\d+)\s+running:"
76				    + "\s+(\w+)\s+temporary:\s+(\w+)", line)
77				self.index = m.group(1)
78				self.running = m.group(2)
79				self.temporary = m.group(3)
80				state = 'IFLLADDR'
81			elif state == 'IFLLADDR':
82				self.lladdr = re.match("^\s+lladdr:\s+(.*)",
83				    line).group(1)
84				state = 'IFLINKLOCAL'
85			elif state == 'IFLINKLOCAL':
86				self.linklocal = re.match("^\s+inet6:\s+(.*)",
87				    line).group(1)
88				state = 'IFDONE'
89			elif state == 'IFDONE':
90				is_ra = re.match("^\s+Router Advertisement "
91				    + "from\s+(.*)", line)
92				is_addr_proposal = re.match("^\s+Address "
93				    + "proposals", line)
94				if is_ra:
95					ra = dict()
96					ra['prefixes'] = []
97					ra['rdns'] = []
98					ra['search'] = []
99					ra['from'] = is_ra.group(1)
100					self.RAs.append(ra)
101					state = 'RASTART'
102				elif is_addr_proposal:
103					state = 'ADDRESS_PROPOSAL'
104			elif state == 'RASTART':
105				m = re.match("\s+received:\s+(.*);\s+(\d+)s "
106				    + "ago", line)
107				ra['received'] = m.group(1)
108				ra['ago'] = m.group(2)
109				state = 'RARECEIVED'
110			elif state == 'RARECEIVED':
111				m = re.match("\s+Cur Hop Limit:\s+(\d+), M: "
112				    + "(\d+), O: (\d+), "
113				    + "Router Lifetime:\s+(\d+)s", line)
114				ra['cur_hop_limit'] = m.group(1)
115				ra['M'] = m.group(2)
116				ra['O'] = m.group(3)
117				ra['lifetime'] = m.group(4)
118				state = 'RACURHOPLIMIT'
119			elif state == 'RACURHOPLIMIT':
120				ra['preference'] = re.match("^\s+Default "
121				    + "Router Preference:\s+(.*)",
122				    line).group(1)
123				state = 'RAPREFERENCE'
124			elif state == 'RAPREFERENCE':
125				m = re.match("^\s+Reachable Time:\s+(\d+)ms, "
126				    + "Retrans Timer:\s+(\d+)ms", line)
127				ra['reachable_time'] = m.group(1)
128				ra['retrans_timer'] = m.group(2)
129				state = 'RAOPTIONS'
130			elif state == 'RAOPTIONS':
131				is_addr_proposal = re.match("^\s+Address "
132				    + "proposals", line)
133				is_rdns = re.match("^\s+rdns: (.*), "
134				    + "lifetime:\s+(\d+)", line)
135				is_search = re.match("^\s+search: (.*), "
136				    + "lifetime:\s+(\d+)", line)
137				is_prefix = re.match("^\s+prefix:\s+(.*)", line)
138				if is_addr_proposal:
139					state = 'ADDRESS_PROPOSAL'
140				elif is_prefix:
141					prefix = dict()
142					ra['prefixes'].append(prefix)
143					prefix['prefix'] = is_prefix.group(1)
144					state = 'PREFIX'
145				elif is_rdns:
146					rdns = dict()
147					ra['rdns'].append(rdns)
148					rdns['addr'] = is_rdns.group(1)
149					rdns['lifetime'] = is_rdns.group(2)
150					state = 'RAOPTIONS'
151				elif is_search:
152					search = dict()
153					ra['search'].append(search)
154					search['search'] = is_search.group(1)
155					search['lifetime'] = is_search.group(2)
156					state = 'RAOPTIONS'
157			elif state == 'PREFIX':
158				m = re.match("^\s+On-link: (\d+), "
159				    + "Autonomous address-configuration: "
160				    + "(\d+)", line)
161				prefix['on_link'] = m.group(1)
162				prefix['autonomous'] = m.group(2)
163				state = 'PREFIX_ONLINK'
164			elif state == 'PREFIX_ONLINK':
165				m = re.match("^\s+vltime:\s+(\d+|infinity), "
166				    + "pltime:\s+(\d+|infinity)", line)
167				prefix['vltime'] = m.group(1)
168				prefix['pltime'] = m.group(2)
169				state = 'RAOPTIONS'
170			elif state == 'ADDRESS_PROPOSAL':
171				is_id = re.match("^\s+id:\s+(\d+), "
172				    + "state:\s+(.+), temporary: (.+)", line)
173				is_defrouter = re.match("\s+Default router "
174				    + "proposals", line)
175				if is_id:
176					addr_proposal = dict()
177					self.addr_proposals.append(
178					    addr_proposal)
179					addr_proposal['id'] = is_id.group(1)
180					addr_proposal['state'] = is_id.group(2)
181					addr_proposal['temporary'] = \
182					    is_id.group(3)
183					state = 'ADDRESS_PROPOSAL_LIFETIME'
184				elif is_defrouter:
185					state = 'DEFAULT_ROUTER'
186			elif state == 'ADDRESS_PROPOSAL_LIFETIME':
187				m = re.match("^\s+vltime:\s+(\d+), "
188				    + "pltime:\s+(\d+), "
189				    + "timeout:\s+(\d+)s", line)
190				addr_proposal['vltime'] = m.group(1)
191				addr_proposal['pltime'] = m.group(2)
192				addr_proposal['timeout'] = m.group(3)
193				state = 'ADDRESS_PROPOSAL_UPDATED'
194			elif state == 'ADDRESS_PROPOSAL_UPDATED':
195				m = re.match("^\s+updated:\s+(.+);\s+(\d+)s "
196				    + "ago", line)
197				addr_proposal['updated'] = m.group(1)
198				addr_proposal['updated_ago'] = m.group(2)
199				state = 'ADDRESS_PROPOSAL_ADDR_PREFIX'
200			elif state == 'ADDRESS_PROPOSAL_ADDR_PREFIX':
201				m = re.match("^\s+(.+), (.+)", line)
202				addr_proposal['addr'] = m.group(1)
203				addr_proposal['prefix'] = m.group(2)
204				state = 'ADDRESS_PROPOSAL'
205			elif state == 'DEFAULT_ROUTER':
206				is_id = re.match("^\s+id:\s+(\d+), "
207				    + "state:\s+(.+)", line)
208				is_rdns = re.match("\s+rDNS proposals", line)
209				if is_id:
210					def_router_proposal = dict()
211					self.def_router_proposals.append(
212					    def_router_proposal)
213					def_router_proposal['id'] = \
214					    is_id.group(1)
215					def_router_proposal['state'] = \
216					    is_id.group(2)
217					state = 'DEFAULT_ROUTER_PROPOSAL'
218				elif is_rdns:
219					state = 'RDNS'
220				else:
221					state = 'DONE'
222			elif state == 'DEFAULT_ROUTER_PROPOSAL':
223				m = re.match("^\s+router: (.+)", line)
224				def_router_proposal['router'] = m.group(1)
225				state = 'DEFAULT_ROUTER_PROPOSAL_ROUTER'
226			elif state == 'DEFAULT_ROUTER_PROPOSAL_ROUTER':
227				m = re.match("^\s+router lifetime:\s+(\d)",
228				    line)
229				def_router_proposal['lifetime'] = m.group(1)
230				state = 'DEFAULT_ROUTER_PROPOSAL_LIFETIME'
231			elif state == 'DEFAULT_ROUTER_PROPOSAL_LIFETIME':
232				m = re.match("^\s+Preference: (.+)", line)
233				def_router_proposal['pref'] = m.group(1)
234				state = 'DEFAULT_ROUTER_PROPOSAL_PREF'
235			elif state == 'DEFAULT_ROUTER_PROPOSAL_PREF':
236				m = re.match("^\s+updated: ([^;]+); (\d+)s ago,"
237				    + " timeout:\s+(\d+)", line)
238				def_router_proposal['updated'] = m.group(1)
239				def_router_proposal['ago'] = m.group(2)
240				def_router_proposal['timeout'] = m.group(3)
241				state = 'DEFAULT_ROUTER'
242			elif state == 'RDNS':
243				is_id = re.match("^\s+id:\s+(\d+), "
244				    + "state:\s+(.+)", line)
245				if is_id:
246					rdns_proposal = dict();
247					rdns_proposal['rdns'] = []
248					self.rdns_proposals.append(
249					    rdns_proposal)
250					rdns_proposal['id'] = is_id.group(1)
251					rdns_proposal['state'] = is_id.group(2)
252					state = 'RDNS_PROPOSAL'
253				else:
254					state = 'DONE'
255			elif state == 'RDNS_PROPOSAL':
256				m = re.match("^\s+router: (.+)", line)
257				rdns_proposal['router'] = m.group(1)
258				state = 'RDNS_PROPOSAL_ROUTER'
259			elif state == 'RDNS_PROPOSAL_ROUTER':
260				m = re.match("^\s+rdns lifetime:\s+(\d)",
261				    line)
262				rdns_proposal['lifetime'] = m.group(1)
263				state = 'RDNS_LIFETIME'
264			elif state == 'RDNS_LIFETIME':
265				m = re.match("^\s+rdns:", line)
266				if m:
267					state = 'RDNS_RDNS'
268			elif state == 'RDNS_RDNS':
269				is_upd = re.match("^\s+updated: ([^;]+); "
270				    + "(\d+)s ago, timeout:\s+(\d+)", line)
271				is_rdns = re.match("^\s+([0-9a-fA-F]{1,4}.*)",
272				    line)
273				if is_upd:
274					rdns_proposal['updated'] = \
275					    is_upd.group(1)
276					rdns_proposal['ago'] = is_upd.group(2)
277					rdns_proposal['timeout'] = \
278					    is_upd.group(3)
279					state = 'DONE'
280				elif is_rdns:
281					rdns_proposal['rdns'].append(
282					    is_rdns.group(1))
283					state = 'RDNS_RDNS'
284			elif state == 'DONE':
285				raise ValueError("got additional data: "
286				    + "{0}".format(line))
287