1# $OpenBSD: Slaacctl.py,v 1.3 2020/12/25 14:25:58 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.privacy = 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['privacy'] = self.privacy 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+privacy:\s+(\w+)", line) 77 self.index = m.group(1) 78 self.running = m.group(2) 79 self.privacy = 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+(.+), privacy: (.+)", 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['privacy'] = \ 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