1from __future__ import absolute_import 2import traceback 3import sys 4import re 5import os 6 7from ciscoconfparse.errors import DynamicAddressException 8 9from ciscoconfparse.ccp_util import ( 10 _IPV6_REGEX_STR_COMPRESSED1, 11 _IPV6_REGEX_STR_COMPRESSED2, 12) 13from ciscoconfparse.ccp_util import _IPV6_REGEX_STR_COMPRESSED3 14from ciscoconfparse.ccp_util import CiscoRange, IPv4Obj, IPv6Obj 15from ciscoconfparse.ccp_abc import BaseCfgLine 16 17### HUGE UGLY WARNING: 18### Anything in models_cisco.py could change at any time, until I remove this 19### warning. I have good reason to believe that these methods are stable and 20### function correctly, but I've been wrong before. There are no unit tests 21### for this functionality yet, so I consider all this code alpha quality. 22### 23### Use models_cisco.py at your own risk. You have been warned :-) 24""" models_cisco.py - Parse, Query, Build, and Modify IOS-style configurations 25 26 Copyright (C) 2020-2021 David Michael Pennington at Cisco Systems 27 Copyright (C) 2019 David Michael Pennington at ThousandEyes 28 Copyright (C) 2014-2019 David Michael Pennington at Samsung Data Services 29 30 This program is free software: you can redistribute it and/or modify 31 it under the terms of the GNU General Public License as published by 32 the Free Software Foundation, either version 3 of the License, or 33 (at your option) any later version. 34 35 This program is distributed in the hope that it will be useful, 36 but WITHOUT ANY WARRANTY; without even the implied warranty of 37 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 38 GNU General Public License for more details. 39 40 You should have received a copy of the GNU General Public License 41 along with this program. If not, see <http://www.gnu.org/licenses/>. 42 43 If you need to contact the author, you can do so by emailing: 44 mike [~at~] pennington [/dot\] net 45""" 46 47MAX_VLAN = 4094 48 49## 50##------------- IOS Configuration line object 51## 52 53 54class IOSCfgLine(BaseCfgLine): 55 """An object for a parsed IOS-style configuration line. 56 :class:`~models_cisco.IOSCfgLine` objects contain references to other 57 parent and child :class:`~models_cisco.IOSCfgLine` objects. 58 59 Notes 60 ----- 61 Originally, :class:`~models_cisco.IOSCfgLine` objects were only 62 intended for advanced ciscoconfparse users. As of ciscoconfparse 63 version 0.9.10, *all users* are strongly encouraged to prefer the 64 methods directly on :class:`~models_cisco.IOSCfgLine` objects. 65 Ultimately, if you write scripts which call methods on 66 :class:`~models_cisco.IOSCfgLine` objects, your scripts will be much 67 more efficient than if you stick strictly to the classic 68 :class:`~ciscoconfparse.CiscoConfParse` methods. 69 70 Parameters 71 ---------- 72 text : str 73 A string containing a text copy of the IOS configuration line. :class:`~ciscoconfparse.CiscoConfParse` will automatically identify the parent and children (if any) when it parses the configuration. 74 comment_delimiter : str 75 A string which is considered a comment for the configuration format. Since this is for Cisco IOS-style configurations, it defaults to ``!``. 76 77 Attributes 78 ---------- 79 text : str 80 A string containing the parsed IOS configuration statement 81 linenum : int 82 The line number of this configuration statement in the original config; default is -1 when first initialized. 83 parent : (:class:`~models_cisco.IOSCfgLine()`) 84 The parent of this object; defaults to ``self``. 85 children : list 86 A list of ``IOSCfgLine()`` objects which are children of this object. 87 child_indent : int 88 An integer with the indentation of this object's children 89 indent : int 90 An integer with the indentation of this object's ``text`` oldest_ancestor (bool): A boolean indicating whether this is the oldest ancestor in a family 91 is_comment : bool 92 A boolean indicating whether this is a comment 93 94 Returns 95 ------- 96 An instance of :class:`~models_cisco.IOSCfgLine`. 97 98 """ 99 100 def __init__(self, *args, **kwargs): 101 """Accept an IOS line number and initialize family relationship 102 attributes""" 103 super(IOSCfgLine, self).__init__(*args, **kwargs) 104 105 @classmethod 106 def is_object_for(cls, line="", re=re): 107 ## Default object, for now 108 return True 109 110 @property 111 def is_intf(self): 112 # Includes subinterfaces 113 """Returns a boolean (True or False) to answer whether this 114 :class:`~models_cisco.IOSCfgLine` is an interface; subinterfaces 115 also return True. 116 117 Returns 118 ------- 119 bool 120 121 Examples 122 -------- 123 This example illustrates use of the method. 124 125 .. code-block:: python 126 :emphasize-lines: 18,21 127 128 >>> from ciscoconfparse import CiscoConfParse 129 >>> config = [ 130 ... '!', 131 ... 'interface Serial1/0', 132 ... ' ip address 1.1.1.1 255.255.255.252', 133 ... '!', 134 ... 'interface ATM2/0', 135 ... ' no ip address', 136 ... '!', 137 ... 'interface ATM2/0.100 point-to-point', 138 ... ' ip address 1.1.1.5 255.255.255.252', 139 ... ' pvc 0/100', 140 ... ' vbr-nrt 704 704', 141 ... '!', 142 ... ] 143 >>> parse = CiscoConfParse(config) 144 >>> obj = parse.find_objects('^interface\sSerial')[0] 145 >>> obj.is_intf 146 True 147 >>> obj = parse.find_objects('^interface\sATM')[0] 148 >>> obj.is_intf 149 True 150 >>> 151 """ 152 # intf_regex = r'^interface\s+(\S+.+)' 153 # if self.re_match(intf_regex): 154 if self.text[0:10] == "interface " and self.text[10] != " ": 155 return True 156 return False 157 158 @property 159 def is_subintf(self): 160 """Returns a boolean (True or False) to answer whether this 161 :class:`~models_cisco.IOSCfgLine` is a subinterface. 162 163 Returns 164 ------- 165 bool 166 167 Examples 168 -------- 169 This example illustrates use of the method. 170 171 .. code-block:: python 172 :emphasize-lines: 18,21 173 174 >>> from ciscoconfparse import CiscoConfParse 175 >>> config = [ 176 ... '!', 177 ... 'interface Serial1/0', 178 ... ' ip address 1.1.1.1 255.255.255.252', 179 ... '!', 180 ... 'interface ATM2/0', 181 ... ' no ip address', 182 ... '!', 183 ... 'interface ATM2/0.100 point-to-point', 184 ... ' ip address 1.1.1.5 255.255.255.252', 185 ... ' pvc 0/100', 186 ... ' vbr-nrt 704 704', 187 ... '!', 188 ... ] 189 >>> parse = CiscoConfParse(config) 190 >>> obj = parse.find_objects(r'^interface\sSerial')[0] 191 >>> obj.is_subintf 192 False 193 >>> obj = parse.find_objects(r'^interface\sATM')[0] 194 >>> obj.is_subintf 195 True 196 >>> 197 """ 198 intf_regex = r"^interface\s+(\S+?\.\d+)" 199 if self.re_match(intf_regex): 200 return True 201 return False 202 203 _VIRTUAL_INTF_REGEX_STR = ( 204 r"""^interface\s+(Loopback|Vlan|Tunnel|Dialer|Virtual-Template|Port-Channel)""" 205 ) 206 _VIRTUAL_INTF_REGEX = re.compile(_VIRTUAL_INTF_REGEX_STR, re.I) 207 208 @property 209 def is_virtual_intf(self): 210 if self.re_match(self._VIRTUAL_INTF_REGEX): 211 return True 212 return False 213 214 @property 215 def is_loopback_intf(self): 216 """Returns a boolean (True or False) to answer whether this 217 :class:`~models_cisco.IOSCfgLine` is a loopback interface. 218 219 Returns 220 ------- 221 bool 222 223 Examples 224 -------- 225 This example illustrates use of the method. 226 227 .. code-block:: python 228 :emphasize-lines: 13,16 229 230 >>> from ciscoconfparse import CiscoConfParse 231 >>> config = [ 232 ... '!', 233 ... 'interface FastEthernet1/0', 234 ... ' ip address 1.1.1.1 255.255.255.252', 235 ... '!', 236 ... 'interface Loopback0', 237 ... ' ip address 1.1.1.5 255.255.255.255', 238 ... '!', 239 ... ] 240 >>> parse = CiscoConfParse(config) 241 >>> obj = parse.find_objects(r'^interface\sFast')[0] 242 >>> obj.is_loopback_intf 243 False 244 >>> obj = parse.find_objects(r'^interface\sLoop')[0] 245 >>> obj.is_loopback_intf 246 True 247 >>> 248 """ 249 intf_regex = r"^interface\s+(\Soopback)" 250 if self.re_match(intf_regex): 251 return True 252 return False 253 254 @property 255 def is_ethernet_intf(self): 256 """Returns a boolean (True or False) to answer whether this 257 :class:`~models_cisco.IOSCfgLine` is an ethernet interface. 258 Any ethernet interface (10M through 10G) is considered an ethernet 259 interface. 260 261 Returns 262 ------- 263 bool 264 265 Examples 266 -------- 267 This example illustrates use of the method. 268 269 .. code-block:: python 270 :emphasize-lines: 18,21 271 272 >>> from ciscoconfparse import CiscoConfParse 273 >>> config = [ 274 ... '!', 275 ... 'interface FastEthernet1/0', 276 ... ' ip address 1.1.1.1 255.255.255.252', 277 ... '!', 278 ... 'interface ATM2/0', 279 ... ' no ip address', 280 ... '!', 281 ... 'interface ATM2/0.100 point-to-point', 282 ... ' ip address 1.1.1.5 255.255.255.252', 283 ... ' pvc 0/100', 284 ... ' vbr-nrt 704 704', 285 ... '!', 286 ... ] 287 >>> parse = CiscoConfParse(config) 288 >>> obj = parse.find_objects('^interface\sFast')[0] 289 >>> obj.is_ethernet_intf 290 True 291 >>> obj = parse.find_objects('^interface\sATM')[0] 292 >>> obj.is_ethernet_intf 293 False 294 >>> 295 """ 296 intf_regex = r"^interface\s+(.*?\Sthernet)" 297 if self.re_match(intf_regex): 298 return True 299 return False 300 301 @property 302 def intf_in_portchannel(self): 303 """Return a boolean indicating whether this port is configured in a port-channel 304 305 Returns 306 ------- 307 bool 308 """ 309 retval = self.re_match_iter_typed( 310 r"^\s*channel-group\s+(\d+)", result_type=bool, default=False 311 ) 312 return retval 313 314 @property 315 def portchannel_number(self): 316 """Return an integer for the port-channel which it's configured in. Return -1 if it's not configured in a port-channel 317 318 Returns 319 ------- 320 bool 321 """ 322 retval = self.re_match_iter_typed( 323 r"^\s*channel-group\s+(\d+)", result_type=int, default=-1 324 ) 325 return retval 326 327 @property 328 def is_portchannel_intf(self): 329 """Return a boolean indicating whether this port is a port-channel intf 330 331 Returns 332 ------- 333 bool 334 """ 335 return "channel" in self.name.lower() 336 337 338## 339##------------- IOS Interface ABC 340## 341 342# Valid method name substitutions: 343# switchport -> switch 344# spanningtree -> stp 345# interfce -> intf 346# address -> addr 347# default -> def 348 349 350class BaseIOSIntfLine(IOSCfgLine): 351 def __init__(self, *args, **kwargs): 352 super(BaseIOSIntfLine, self).__init__(*args, **kwargs) 353 self.ifindex = None # Optional, for user use 354 self.default_ipv4_addr_object = IPv4Obj("127.0.0.1/32", strict=False) 355 356 def __repr__(self): 357 if not self.is_switchport: 358 try: 359 ipv4_addr_object = self.ipv4_addr_object 360 except DynamicAddressException: 361 ipv4_addr_object = None 362 363 if ipv4_addr_object is None: 364 addr = "IPv4 dhcp" 365 elif ipv4_addr_object == self.default_ipv4_addr_object: 366 addr = "No IPv4" 367 else: 368 ip = str(self.ipv4_addr_object.ip) 369 prefixlen = str(self.ipv4_addr_object.prefixlen) 370 addr = "{0}/{1}".format(ip, prefixlen) 371 return "<%s # %s '%s' info: '%s'>" % ( 372 self.classname, 373 self.linenum, 374 self.name, 375 addr, 376 ) 377 else: 378 return "<%s # %s '%s' info: 'switchport'>" % ( 379 self.classname, 380 self.linenum, 381 self.name, 382 ) 383 384 def _build_abbvs(self): 385 """Build a set of valid abbreviations (lowercased) for the interface""" 386 retval = set([]) 387 port_type_chars = self.port_type.lower() 388 subinterface_number = self.subinterface_number 389 for sep in ["", " "]: 390 for ii in range(1, len(port_type_chars) + 1): 391 retval.add( 392 "{0}{1}{2}".format(port_type_chars[0:ii], sep, subinterface_number) 393 ) 394 return retval 395 396 def reset(self, atomic=True): 397 # Insert build_reset_string() before this line... 398 self.insert_before(self.build_reset_string(), atomic=atomic) 399 400 def build_reset_string(self): 401 # IOS interfaces are defaulted like this... 402 return "default " + self.text 403 404 @property 405 def verbose(self): 406 if not self.is_switchport: 407 return ( 408 "<%s # %s '%s' info: '%s' (child_indent: %s / len(children): %s / family_endpoint: %s)>" 409 % ( 410 self.classname, 411 self.linenum, 412 self.text, 413 self.ipv4_addr_object or "No IPv4", 414 self.child_indent, 415 len(self.children), 416 self.family_endpoint, 417 ) 418 ) 419 else: 420 return ( 421 "<%s # %s '%s' info: 'switchport' (child_indent: %s / len(children): %s / family_endpoint: %s)>" 422 % ( 423 self.classname, 424 self.linenum, 425 self.text, 426 self.child_indent, 427 len(self.children), 428 self.family_endpoint, 429 ) 430 ) 431 432 @classmethod 433 def is_object_for(cls, line="", re=re): 434 return False 435 436 ##------------- Basic interface properties 437 438 @property 439 def abbvs(self): 440 """A python set of valid abbreviations (lowercased) for the interface""" 441 return self._build_abbvs() 442 443 _INTF_NAME_RE_STR = r"^interface\s+(\S+[0-9\/\.\s]+)\s*" 444 _INTF_NAME_REGEX = re.compile(_INTF_NAME_RE_STR) 445 446 @property 447 def name(self): 448 """Return the interface name as a string, such as 'GigabitEthernet0/1' 449 450 Returns 451 ------- 452 str 453 The interface name as a string, or '' if the object is not an interface. 454 455 Examples 456 -------- 457 This example illustrates use of the method. 458 459 .. code-block:: python 460 :emphasize-lines: 17,20,23 461 462 >>> from ciscoconfparse import CiscoConfParse 463 >>> config = [ 464 ... '!', 465 ... 'interface FastEthernet1/0', 466 ... ' ip address 1.1.1.1 255.255.255.252', 467 ... '!', 468 ... 'interface ATM2/0', 469 ... ' no ip address', 470 ... '!', 471 ... 'interface ATM2/0.100 point-to-point', 472 ... ' ip address 1.1.1.5 255.255.255.252', 473 ... ' pvc 0/100', 474 ... ' vbr-nrt 704 704', 475 ... '!', 476 ... ] 477 >>> parse = CiscoConfParse(config, factory=True) 478 >>> obj = parse.find_objects('^interface\sFast')[0] 479 >>> obj.name 480 'FastEthernet1/0' 481 >>> obj = parse.find_objects('^interface\sATM')[0] 482 >>> obj.name 483 'ATM2/0' 484 >>> obj = parse.find_objects('^interface\sATM')[1] 485 >>> obj.name 486 'ATM2/0.100' 487 >>> 488 """ 489 if not self.is_intf: 490 return "" 491 name = self.re_match(self._INTF_NAME_REGEX).strip() 492 return name 493 494 @property 495 def port(self): 496 """Return the interface's port number 497 498 Returns 499 ------- 500 int 501 The interface number. 502 503 Examples 504 -------- 505 This example illustrates use of the method. 506 507 .. code-block:: python 508 :emphasize-lines: 17,20 509 510 >>> from ciscoconfparse import CiscoConfParse 511 >>> config = [ 512 ... '!', 513 ... 'interface FastEthernet1/0', 514 ... ' ip address 1.1.1.1 255.255.255.252', 515 ... '!', 516 ... 'interface ATM2/0', 517 ... ' no ip address', 518 ... '!', 519 ... 'interface ATM2/0.100 point-to-point', 520 ... ' ip address 1.1.1.5 255.255.255.252', 521 ... ' pvc 0/100', 522 ... ' vbr-nrt 704 704', 523 ... '!', 524 ... ] 525 >>> parse = CiscoConfParse(config, factory=True) 526 >>> obj = parse.find_objects('^interface\sFast')[0] 527 >>> obj.port 528 0 529 >>> obj = parse.find_objects('^interface\sATM')[0] 530 >>> obj.port 531 0 532 >>> 533 """ 534 return self.ordinal_list[-1] 535 536 @property 537 def port_type(self): 538 """Return Loopback, ATM, GigabitEthernet, Virtual-Template, etc... 539 540 Returns 541 ------- 542 str 543 The port type. 544 545 Examples 546 -------- 547 This example illustrates use of the method. 548 549 .. code-block:: python 550 :emphasize-lines: 17,20 551 552 >>> from ciscoconfparse import CiscoConfParse 553 >>> config = [ 554 ... '!', 555 ... 'interface FastEthernet1/0', 556 ... ' ip address 1.1.1.1 255.255.255.252', 557 ... '!', 558 ... 'interface ATM2/0', 559 ... ' no ip address', 560 ... '!', 561 ... 'interface ATM2/0.100 point-to-point', 562 ... ' ip address 1.1.1.5 255.255.255.252', 563 ... ' pvc 0/100', 564 ... ' vbr-nrt 704 704', 565 ... '!', 566 ... ] 567 >>> parse = CiscoConfParse(config, factory=True) 568 >>> obj = parse.find_objects('^interface\sFast')[0] 569 >>> obj.port_type 570 'FastEthernet' 571 >>> obj = parse.find_objects('^interface\sATM')[0] 572 >>> obj.port_type 573 'ATM' 574 >>> 575 """ 576 port_type_regex = r"^interface\s+([A-Za-z\-]+)" 577 return self.re_match(port_type_regex, group=1, default="") 578 579 @property 580 def ordinal_list(self): 581 """Return a tuple of numbers representing card, slot, port for this interface. If you call ordinal_list on GigabitEthernet2/25.100, you'll get this python tuple of integers: (2, 25). If you call ordinal_list on GigabitEthernet2/0/25.100 you'll get this python list of integers: (2, 0, 25). This method strips all subinterface information in the returned value. 582 583 Returns 584 ------- 585 tuple 586 A tuple of port numbers as integers. 587 588 Warnings 589 -------- 590 ordinal_list should silently fail (returning an empty python list) if the interface doesn't parse correctly 591 592 Examples 593 -------- 594 This example illustrates use of the method. 595 596 .. code-block:: python 597 :emphasize-lines: 17,20 598 599 >>> from ciscoconfparse import CiscoConfParse 600 >>> config = [ 601 ... '!', 602 ... 'interface FastEthernet1/0', 603 ... ' ip address 1.1.1.1 255.255.255.252', 604 ... '!', 605 ... 'interface ATM2/0', 606 ... ' no ip address', 607 ... '!', 608 ... 'interface ATM2/0.100 point-to-point', 609 ... ' ip address 1.1.1.5 255.255.255.252', 610 ... ' pvc 0/100', 611 ... ' vbr-nrt 704 704', 612 ... '!', 613 ... ] 614 >>> parse = CiscoConfParse(config, factory=True) 615 >>> obj = parse.find_objects('^interface\sFast')[0] 616 >>> obj.ordinal_list 617 (1, 0) 618 >>> obj = parse.find_objects('^interface\sATM')[0] 619 >>> obj.ordinal_list 620 (2, 0) 621 >>> 622 """ 623 if not self.is_intf: 624 return () 625 else: 626 intf_number = self.interface_number 627 if intf_number: 628 return tuple([int(ii) for ii in intf_number.split("/")]) 629 else: 630 return () 631 632 @property 633 def interface_number(self): 634 """Return a string representing the card, slot, port for this interface. If you call interface_number on GigabitEthernet2/25.100, you'll get this python string: '2/25'. If you call interface_number on GigabitEthernet2/0/25.100 you'll get this python string '2/0/25'. This method strips all subinterface information in the returned value. 635 636 Returns 637 ------- 638 str 639 640 Warnings 641 -------- 642 interface_number should silently fail (returning an empty python string) if the interface doesn't parse correctly 643 644 Examples 645 -------- 646 This example illustrates use of the method. 647 648 .. code-block:: python 649 :emphasize-lines: 17,20 650 651 >>> from ciscoconfparse import CiscoConfParse 652 >>> config = [ 653 ... '!', 654 ... 'interface FastEthernet1/0', 655 ... ' ip address 1.1.1.1 255.255.255.252', 656 ... '!', 657 ... 'interface ATM2/0', 658 ... ' no ip address', 659 ... '!', 660 ... 'interface ATM2/0.100 point-to-point', 661 ... ' ip address 1.1.1.5 255.255.255.252', 662 ... ' pvc 0/100', 663 ... ' vbr-nrt 704 704', 664 ... '!', 665 ... ] 666 >>> parse = CiscoConfParse(config, factory=True) 667 >>> obj = parse.find_objects('^interface\sFast')[0] 668 >>> obj.interface_number 669 '1/0' 670 >>> obj = parse.find_objects('^interface\sATM')[-1] 671 >>> obj.interface_number 672 '2/0' 673 >>> 674 """ 675 if not self.is_intf: 676 return "" 677 else: 678 intf_regex = r"^interface\s+[A-Za-z\-]+\s*(\d+.*?)(\.\d+)*(\s\S+)*\s*$" 679 intf_number = self.re_match(intf_regex, group=1, default="") 680 return intf_number 681 682 @property 683 def subinterface_number(self): 684 """Return a string representing the card, slot, port for this interface or subinterface. If you call subinterface_number on GigabitEthernet2/25.100, you'll get this python string: '2/25.100'. If you call interface_number on GigabitEthernet2/0/25 you'll get this python string '2/0/25'. This method strips all subinterface information in the returned value. 685 686 Returns 687 ------- 688 str 689 690 Warnings 691 -------- 692 subinterface_number should silently fail (returning an empty python string) if the interface doesn't parse correctly 693 694 Examples 695 -------- 696 This example illustrates use of the method. 697 698 .. code-block:: python 699 :emphasize-lines: 17,20 700 701 >>> from ciscoconfparse import CiscoConfParse 702 >>> config = [ 703 ... '!', 704 ... 'interface FastEthernet1/0', 705 ... ' ip address 1.1.1.1 255.255.255.252', 706 ... '!', 707 ... 'interface ATM2/0', 708 ... ' no ip address', 709 ... '!', 710 ... 'interface ATM2/0.100 point-to-point', 711 ... ' ip address 1.1.1.5 255.255.255.252', 712 ... ' pvc 0/100', 713 ... ' vbr-nrt 704 704', 714 ... '!', 715 ... ] 716 >>> parse = CiscoConfParse(config, factory=True) 717 >>> obj = parse.find_objects('^interface\sFast')[0] 718 >>> obj.subinterface_number 719 '1/0' 720 >>> obj = parse.find_objects('^interface\sATM')[-1] 721 >>> obj.subinterface_number 722 '2/0.100' 723 >>> 724 """ 725 if not self.is_intf: 726 return "" 727 else: 728 subintf_regex = r"^interface\s+[A-Za-z\-]+\s*(\d+.*?\.?\d?)(\s\S+)*\s*$" 729 subintf_number = self.re_match(subintf_regex, group=1, default="") 730 return subintf_number 731 732 @property 733 def description(self): 734 """Return the current interface description string. 735 736 """ 737 retval = self.re_match_iter_typed( 738 r"^\s*description\s+(\S.+)$", result_type=str, default="" 739 ) 740 return retval 741 742 @property 743 def manual_bandwidth(self): 744 retval = self.re_match_iter_typed( 745 r"^\s*bandwidth\s+(\d+)$", result_type=int, default=0 746 ) 747 return retval 748 749 @property 750 def manual_delay(self): 751 retval = self.re_match_iter_typed( 752 r"^\s*delay\s+(\d+)$", result_type=int, default=0 753 ) 754 return retval 755 756 @property 757 def manual_holdqueue_out(self): 758 """Return the current hold-queue out depth, if default return 0""" 759 retval = self.re_match_iter_typed( 760 r"^\s*hold-queue\s+(\d+)\s+out$", result_type=int, default=0 761 ) 762 return retval 763 764 @property 765 def manual_holdqueue_in(self): 766 """Return the current hold-queue in depth, if default return 0""" 767 retval = self.re_match_iter_typed( 768 r"^\s*hold-queue\s+(\d+)\s+in$", result_type=int, default=0 769 ) 770 return retval 771 772 @property 773 def manual_encapsulation(self): 774 retval = self.re_match_iter_typed( 775 r"^\s*encapsulation\s+(\S+)", result_type=str, default="" 776 ) 777 return retval 778 779 @property 780 def has_mpls(self): 781 retval = self.re_match_iter_typed( 782 r"^\s*(mpls\s+ip)$", result_type=bool, default=False 783 ) 784 return retval 785 786 @property 787 def ipv4_addr_object(self): 788 """Return a ccp_util.IPv4Obj object representing the address on this interface; if there is no address, return IPv4Obj('127.0.0.1/32')""" 789 try: 790 return IPv4Obj("%s/%s" % (self.ipv4_addr, self.ipv4_netmask)) 791 except DynamicAddressException as e: 792 raise DynamicAddressException(e) 793 except: 794 return self.default_ipv4_addr_object 795 796 @property 797 def ipv4_network_object(self): 798 """Return an ccp_util.IPv4Obj object representing the subnet on this interface; if there is no address, return ccp_util.IPv4Obj('127.0.0.1/32')""" 799 return self.ip_network_object 800 801 @property 802 def ip_network_object(self): 803 # Simplified on 2014-12-02 804 try: 805 return IPv4Obj( 806 "{0}/{1}".format(self.ipv4_addr, self.ipv4_netmask), strict=False 807 ) 808 except DynamicAddressException as e: 809 raise DynamicAddressException(e) 810 except (Exception) as e: 811 return self.default_ipv4_addr_object 812 813 @property 814 def has_autonegotiation(self): 815 if not self.is_ethernet_intf: 816 return False 817 elif self.is_ethernet_intf and ( 818 self.has_manual_speed or self.has_manual_duplex 819 ): 820 return False 821 elif self.is_ethernet_intf: 822 return True 823 else: 824 raise ValueError 825 826 @property 827 def has_manual_speed(self): 828 retval = self.re_match_iter_typed( 829 r"^\s*speed\s+(\d+)$", result_type=bool, default=False 830 ) 831 return retval 832 833 @property 834 def has_manual_duplex(self): 835 retval = self.re_match_iter_typed( 836 r"^\s*duplex\s+(\S.+)$", result_type=bool, default=False 837 ) 838 return retval 839 840 @property 841 def has_manual_carrierdelay(self): 842 """Return a python boolean for whether carrier delay is manually configured on the interface""" 843 return bool(self.manual_carrierdelay) 844 845 @property 846 def manual_carrierdelay(self): 847 """Return the manual carrier delay (in seconds) of the interface as a python float. If there is no explicit carrier delay, return 0.0""" 848 cd_seconds = self.re_match_iter_typed( 849 r"^\s*carrier-delay\s+(\d+)$", result_type=float, default=0.0 850 ) 851 cd_msec = self.re_match_iter_typed( 852 r"^\s*carrier-delay\s+msec\s+(\d+)$", result_type=float, default=0.0 853 ) 854 855 if cd_seconds > 0.0: 856 return cd_seconds 857 elif cd_msec > 0.0: 858 return cd_msec / 1000.0 859 else: 860 return 0.0 861 862 @property 863 def has_manual_clock_rate(self): 864 return bool(self.manual_clock_rate) 865 866 @property 867 def manual_clock_rate(self): 868 """Return the clock rate of the interface as a python integer. If there is no explicit clock rate, return 0""" 869 retval = self.re_match_iter_typed( 870 r"^\s*clock\s+rate\s+(\d+)$", result_type=int, default=0 871 ) 872 return retval 873 874 @property 875 def manual_mtu(self): 876 ## Due to the diverse platform defaults, this should be the 877 ## only mtu information I plan to support 878 """Returns a integer value for the manual MTU configured on an 879 :class:`~models_cisco.IOSIntfLine` object. Interfaces without a 880 manual MTU configuration return 0. 881 882 Returns 883 ------- 884 int 885 886 Examples 887 -------- 888 This example illustrates use of the method. 889 890 .. code-block:: python 891 :emphasize-lines: 18,21 892 893 >>> from ciscoconfparse import CiscoConfParse 894 >>> config = [ 895 ... '!', 896 ... 'interface FastEthernet1/0', 897 ... ' ip address 1.1.1.1 255.255.255.252', 898 ... '!', 899 ... 'interface ATM2/0', 900 ... ' mtu 4470', 901 ... ' no ip address', 902 ... '!', 903 ... 'interface ATM2/0.100 point-to-point', 904 ... ' ip address 1.1.1.5 255.255.255.252', 905 ... ' pvc 0/100', 906 ... ' vbr-nrt 704 704', 907 ... '!', 908 ... ] 909 >>> parse = CiscoConfParse(config, factory=True) 910 >>> obj = parse.find_objects('^interface\sFast')[0] 911 >>> obj.manual_mtu 912 0 913 >>> obj = parse.find_objects('^interface\sATM')[0] 914 >>> obj.manual_mtu 915 4470 916 >>> 917 """ 918 retval = self.re_match_iter_typed( 919 r"^\s*mtu\s+(\d+)$", result_type=int, default=0 920 ) 921 return retval 922 923 @property 924 def manual_mpls_mtu(self): 925 ## Due to the diverse platform defaults, this should be the 926 ## only mtu information I plan to support 927 retval = self.re_match_iter_typed( 928 r"^\s*mpls\s+mtu\s+(\d+)$", result_type=int, default=0 929 ) 930 return retval 931 932 @property 933 def manual_ip_mtu(self): 934 ## Due to the diverse platform defaults, this should be the 935 ## only mtu information I plan to support 936 retval = self.re_match_iter_typed( 937 r"^\s*ip\s+mtu\s+(\d+)$", result_type=int, default=0 938 ) 939 return retval 940 941 @property 942 def manual_speed(self): 943 retval = self.re_match_iter_typed( 944 r"^\s*speed\s+(\d+)$", result_type=int, default=0 945 ) 946 return retval 947 948 @property 949 def manual_duplex(self): 950 retval = self.re_match_iter_typed( 951 r"^\s*duplex\s+(\S.+)$", result_type=str, default="" 952 ) 953 return retval 954 955 @property 956 def has_manual_mtu(self): 957 return bool(self.manual_mtu) 958 959 @property 960 def has_manual_mpls_mtu(self): 961 return bool(self.manual_mpls_mtu) 962 963 @property 964 def has_manual_ip_mtu(self): 965 return bool(self.manual_ip_mtu) 966 967 @property 968 def is_shutdown(self): 969 retval = self.re_match_iter_typed( 970 r"^\s*(shut\S*)\s*$", result_type=bool, default=False 971 ) 972 return retval 973 974 @property 975 def has_vrf(self): 976 return bool(self.vrf) 977 978 @property 979 def vrf(self): 980 retval = self.re_match_iter_typed( 981 r"^\s*(ip\s+)*vrf\sforwarding\s(\S+)$", result_type=str, group=2, default="" 982 ) 983 return retval 984 985 @property 986 def ip_addr(self): 987 return self.ipv4_addr 988 989 @property 990 def ipv4_addr(self): 991 """Return a string with the interface's IPv4 address, or '' if there is none""" 992 retval = self.re_match_iter_typed( 993 r"^\s+ip\s+address\s+(\d+\.\d+\.\d+\.\d+)\s+\d+\.\d+\.\d+\.\d+\s*$", 994 result_type=str, 995 default="", 996 ) 997 condition1 = self.re_match_iter_typed( 998 r"^\s+ip\s+address\s+(dhcp)\s*$", result_type=str, default="" 999 ) 1000 if condition1.lower() == "dhcp": 1001 error = "Cannot parse address from a dhcp interface: {0}".format(self.name) 1002 raise DynamicAddressException(error) 1003 else: 1004 return retval 1005 1006 @property 1007 def ipv4_netmask(self): 1008 """Return a string with the interface's IPv4 netmask, or '' if there is none""" 1009 retval = self.re_match_iter_typed( 1010 r"^\s+ip\s+address\s+\d+\.\d+\.\d+\.\d+\s+(\d+\.\d+\.\d+\.\d+)\s*$", 1011 result_type=str, 1012 default="", 1013 ) 1014 return retval 1015 1016 @property 1017 def ipv4_masklength(self): 1018 """Return an integer with the interface's IPv4 mask length, or 0 if there is no IP address on the interace""" 1019 ipv4_addr_object = self.ipv4_addr_object 1020 if ipv4_addr_object != self.default_ipv4_addr_object: 1021 return ipv4_addr_object.prefixlen 1022 return 0 1023 1024 def is_abbreviated_as(self, val): 1025 """Test whether `val` is a good abbreviation for the interface""" 1026 if val.lower() in self.abbvs: 1027 return True 1028 return False 1029 1030 def in_ipv4_subnet(self, ipv4network=IPv4Obj("0.0.0.0/32", strict=False)): 1031 """Accept an argument for the :class:`~ccp_util.IPv4Obj` to be 1032 considered, and return a boolean for whether this interface is within 1033 the requested :class:`~ccp_util.IPv4Obj`. 1034 1035 Parameters 1036 ---------- 1037 ipv4network : :class:`~ccp_util.IPv4Obj` 1038 An object to compare against IP addresses configured on this :class:`~models_cisco.IOSIntfLine` object. 1039 1040 Returns 1041 ------- 1042 bool 1043 If there is an ip address, or None if there is no ip address. 1044 1045 Examples 1046 -------- 1047 This example illustrates use of the method. 1048 1049 .. code-block:: python 1050 :emphasize-lines: 21,23 1051 1052 >>> from ciscoconfparse.ccp_util import IPv4Obj 1053 >>> from ciscoconfparse import CiscoConfParse 1054 >>> config = [ 1055 ... '!', 1056 ... 'interface Serial1/0', 1057 ... ' ip address 1.1.1.1 255.255.255.252', 1058 ... '!', 1059 ... 'interface ATM2/0', 1060 ... ' no ip address', 1061 ... '!', 1062 ... 'interface ATM2/0.100 point-to-point', 1063 ... ' ip address 1.1.1.5 255.255.255.252', 1064 ... ' pvc 0/100', 1065 ... ' vbr-nrt 704 704', 1066 ... '!', 1067 ... ] 1068 >>> parse = CiscoConfParse(config, factory=True) 1069 >>> obj = parse.find_objects('^interface\sSerial')[0] 1070 >>> obj 1071 <IOSIntfLine # 1 'Serial1/0' info: '1.1.1.1/30'> 1072 >>> obj.in_ipv4_subnet(IPv4Obj('1.1.1.0/24', strict=False)) 1073 True 1074 >>> obj.in_ipv4_subnet(IPv4Obj('2.1.1.0/24', strict=False)) 1075 False 1076 >>> 1077 """ 1078 if not (str(self.ipv4_addr_object.ip) == "127.0.0.1"): 1079 try: 1080 # Return a boolean for whether the interface is in that 1081 # network and mask 1082 return self.ipv4_network_object in ipv4network 1083 except (Exception) as e: 1084 raise ValueError( 1085 "FATAL: %s.in_ipv4_subnet(ipv4network={0}) is an invalid arg: {1}".format( 1086 ipv4network, e 1087 ) 1088 ) 1089 else: 1090 return None 1091 1092 def in_ipv4_subnets(self, subnets=None): 1093 """Accept a set or list of ccp_util.IPv4Obj objects, and return a boolean for whether this interface is within the requested subnets.""" 1094 if subnets is None: 1095 raise ValueError( 1096 "A python list or set of ccp_util.IPv4Obj objects must be supplied" 1097 ) 1098 for subnet in subnets: 1099 tmp = self.in_ipv4_subnet(ipv4network=subnet) 1100 if self.ipv4_addr_object in subnet: 1101 return tmp 1102 return tmp 1103 1104 @property 1105 def has_no_icmp_unreachables(self): 1106 ## NOTE: I have no intention of checking self.is_shutdown here 1107 ## People should be able to check the sanity of interfaces 1108 ## before they put them into production 1109 1110 ## Interface must have an IP addr to respond 1111 if self.ipv4_addr == "": 1112 return False 1113 1114 retval = self.re_match_iter_typed( 1115 r"^\s*no\sip\s(unreachables)\s*$", result_type=bool, default=False 1116 ) 1117 return retval 1118 1119 @property 1120 def has_no_icmp_redirects(self): 1121 ## NOTE: I have no intention of checking self.is_shutdown here 1122 ## People should be able to check the sanity of interfaces 1123 ## before they put them into production 1124 1125 ## Interface must have an IP addr to respond 1126 if self.ipv4_addr == "": 1127 return False 1128 1129 retval = self.re_match_iter_typed( 1130 r"^\s*no\sip\s(redirects)\s*$", result_type=bool, default=False 1131 ) 1132 return retval 1133 1134 @property 1135 def has_no_ip_proxyarp(self): 1136 ## NOTE: I have no intention of checking self.is_shutdown here 1137 ## People should be able to check the sanity of interfaces 1138 ## before they put them into production 1139 """Return a boolean for whether no ip proxy-arp is configured on the 1140 interface. 1141 1142 Returns 1143 ------- 1144 bool 1145 1146 Examples 1147 -------- 1148 This example illustrates use of the method. 1149 1150 .. code-block:: python 1151 :emphasize-lines: 12 1152 1153 >>> from ciscoconfparse.ccp_util import IPv4Obj 1154 >>> from ciscoconfparse import CiscoConfParse 1155 >>> config = [ 1156 ... '!', 1157 ... 'interface FastEthernet1/0', 1158 ... ' ip address 1.1.1.1 255.255.255.252', 1159 ... ' no ip proxy-arp', 1160 ... '!', 1161 ... ] 1162 >>> parse = CiscoConfParse(config, factory=True) 1163 >>> obj = parse.find_objects('^interface\sFast')[0] 1164 >>> obj.has_no_ip_proxyarp 1165 True 1166 >>> 1167 """ 1168 1169 ## Interface must have an IP addr to respond 1170 if self.ipv4_addr == "": 1171 return False 1172 1173 ## By default, Cisco IOS answers proxy-arp 1174 ## By default, Nexus disables proxy-arp 1175 ## By default, IOS-XR disables proxy-arp 1176 retval = self.re_match_iter_typed( 1177 r"^\s*no\sip\s(proxy-arp)\s*$", result_type=bool, default=False 1178 ) 1179 return retval 1180 1181 @property 1182 def has_ip_pim_dense_mode(self): 1183 ## NOTE: I have no intention of checking self.is_shutdown here 1184 ## People should be able to check the sanity of interfaces 1185 ## before they put them into production 1186 1187 ## Interface must have an IP addr to run PIM 1188 if self.ipv4_addr == "": 1189 return False 1190 1191 retval = self.re_match_iter_typed( 1192 r"^\s*(ip\spim\sdense-mode)\s*$", result_type=bool, default=False 1193 ) 1194 return retval 1195 1196 @property 1197 def has_ip_pim_sparse_mode(self): 1198 ## NOTE: I have no intention of checking self.is_shutdown here 1199 ## People should be able to check the sanity of interfaces 1200 ## before they put them into production 1201 1202 ## Interface must have an IP addr to run PIM 1203 if self.ipv4_addr == "": 1204 return False 1205 1206 retval = self.re_match_iter_typed( 1207 r"^\s*(ip\spim\ssparse-mode)\s*$", result_type=bool, default=False 1208 ) 1209 return retval 1210 1211 @property 1212 def has_ip_pim_sparsedense_mode(self): 1213 ## NOTE: I have no intention of checking self.is_shutdown here 1214 ## People should be able to check the sanity of interfaces 1215 ## before they put them into production 1216 1217 ## Interface must have an IP addr to run PIM 1218 if self.ipv4_addr == "": 1219 return False 1220 1221 retval = self.re_match_iter_typed( 1222 r"^\s*(ip\spim\ssparse-dense-mode)\s*$", result_type=bool, default=False 1223 ) 1224 return retval 1225 1226 @property 1227 def manual_arp_timeout(self): 1228 """Return an integer with the current interface ARP timeout, if there isn't one set, return 0. If there is no IP address, return -1""" 1229 ## NOTE: I have no intention of checking self.is_shutdown here 1230 ## People should be able to check the sanity of interfaces 1231 ## before they put them into production 1232 1233 ## Interface must have an IP addr to respond 1234 if self.ipv4_addr == "": 1235 return -1 1236 1237 ## By default, Cisco IOS defaults to 4 hour arp timers 1238 ## By default, Nexus defaults to 15 minute arp timers 1239 retval = self.re_match_iter_typed( 1240 r"^\s*arp\s+timeout\s+(\d+)\s*$", result_type=int, default=0 1241 ) 1242 return retval 1243 1244 @property 1245 def has_ip_helper_addresses(self): 1246 """Return a True if the intf has helper-addresses; False if not""" 1247 if len(self.ip_helper_addresses) > 0: 1248 return True 1249 return False 1250 1251 @property 1252 def ip_helper_addresses(self): 1253 """Return a list of dicts with IP helper-addresses. Each helper-address is in a dictionary. The dictionary is in this format: 1254 1255 Examples 1256 -------- 1257 1258 .. code-block:: python 1259 :emphasize-lines: 11 1260 1261 >>> from ciscoconfparse import CiscoConfParse 1262 >>> config = [ 1263 ... '!', 1264 ... 'interface FastEthernet1/1', 1265 ... ' ip address 1.1.1.1 255.255.255.0', 1266 ... ' ip helper-address 172.16.20.12', 1267 ... ' ip helper-address 172.19.185.91', 1268 ... '!', 1269 ... ] 1270 >>> parse = CiscoConfParse(config) 1271 >>> obj = parse.find_objects('^interface\sFastEthernet1/1$')[0] 1272 >>> obj.ip_helper_addresses 1273 [{'addr': '172.16.20.12', 'vrf': '', 'global': False}, {'addr': '172.19.185.91', 'vrf': '', 'global': False}] 1274 >>>""" 1275 retval = list() 1276 for child in self.children: 1277 if "helper-address" in child.text: 1278 addr = child.re_match_typed( 1279 r"ip\s+helper-address\s.*?(\d+\.\d+\.\d+\.\d+)" 1280 ) 1281 global_addr = child.re_match_typed( 1282 r"ip\s+helper-address\s+(global)", result_type=bool, default=False 1283 ) 1284 vrf = child.re_match_typed( 1285 r"ip\s+helper-address\s+vrf\s+(\S+)", default="" 1286 ) 1287 retval.append({"addr": addr, "vrf": vrf, "global": bool(global_addr)}) 1288 return retval 1289 1290 @property 1291 def is_switchport(self): 1292 retval = self.re_match_iter_typed( 1293 r"^\s*(switchport)\s*", result_type=bool, default=False 1294 ) 1295 return retval 1296 1297 @property 1298 def has_manual_switch_access(self): 1299 retval = self.re_match_iter_typed( 1300 r"^\s*(switchport\smode\s+access)\s*$", result_type=bool, default=False 1301 ) 1302 return retval 1303 1304 @property 1305 def has_manual_switch_trunk_encap(self): 1306 return bool(self.manual_switch_trunk_encap) 1307 1308 @property 1309 def manual_switch_trunk_encap(self): 1310 retval = self.re_match_iter_typed( 1311 r"^\s*(switchport\s+trunk\s+encap\s+(\S+))\s*$", result_type=str, default="" 1312 ) 1313 return retval 1314 1315 @property 1316 def has_manual_switch_trunk(self): 1317 retval = self.re_match_iter_typed( 1318 r"^\s*(switchport\s+mode\s+trunk)\s*$", result_type=bool, default=False 1319 ) 1320 return retval 1321 1322 @property 1323 def has_switch_portsecurity(self): 1324 if not self.is_switchport: 1325 return False 1326 ## IMPORTANT: Cisco IOS will not enable port-security on the port 1327 ## unless 'switch port-security' (with no other options) 1328 ## is in the configuration 1329 retval = self.re_match_iter_typed( 1330 r"^\s*(switchport\sport-security)\s*$", result_type=bool, default=False 1331 ) 1332 return retval 1333 1334 @property 1335 def has_switch_stormcontrol(self): 1336 if not self.is_switchport: 1337 return False 1338 retval = self.re_match_iter_typed( 1339 r"^\s*(storm-control)\s*$", result_type=bool, default=False 1340 ) 1341 return retval 1342 1343 @property 1344 def has_dtp(self): 1345 if not self.is_switchport: 1346 return False 1347 1348 ## Not using self.re_match_iter_typed, because I want to 1349 ## be sure I build the correct API for regex_match is False, and 1350 ## default value is True 1351 for obj in self.children: 1352 switch = obj.re_match(r"^\s*(switchport\snoneg\S*)\s*$") 1353 if not (switch is None): 1354 return False 1355 return True 1356 1357 @property 1358 def access_vlan(self): 1359 """Return an integer with the access vlan number. Return 1, if the switchport has no explicit vlan configured; return 0 if the port isn't a switchport""" 1360 if self.is_switchport: 1361 default_val = 1 1362 else: 1363 default_val = 0 1364 retval = self.re_match_iter_typed( 1365 r"^\s*switchport\s+access\s+vlan\s+(\d+)$", 1366 result_type=int, 1367 default=default_val, 1368 ) 1369 return retval 1370 1371 @property 1372 def trunk_vlans_allowed(self): 1373 """Return a CiscoRange() with the list of allowed vlan numbers (as int). Return 0 if the port isn't a switchport in trunk mode""" 1374 1375 # The default values... 1376 if self.is_switchport and not self.has_manual_switch_access: 1377 retval = CiscoRange("1-{0}".format(MAX_VLAN), result_type=int) 1378 else: 1379 return 0 1380 1381 ## Iterate over switchport trunk statements 1382 for obj in self.children: 1383 1384 ## For every child object, check whether the vlan list is modified 1385 abs_str = obj.re_match_typed( 1386 "^\s+switchport\s+trunk\s+allowed\s+vlan\s(all|none|\d.*?)$", 1387 default="_nomatch_", 1388 result_type=str, 1389 ).lower() 1390 add_str = obj.re_match_typed( 1391 "^\s+switchport\s+trunk\s+allowed\s+vlan\s+add\s+(\d.*?)$", 1392 default="_nomatch_", 1393 result_type=str, 1394 ).lower() 1395 exc_str = obj.re_match_typed( 1396 "^\s+switchport\s+trunk\s+allowed\s+vlan\s+except\s+(\d.*?)$", 1397 default="_nomatch_", 1398 result_type=str, 1399 ).lower() 1400 rem_str = obj.re_match_typed( 1401 "^\s+switchport\s+trunk\s+allowed\s+vlan\s+remove\s+(\d.*?)$", 1402 default="_nomatch_", 1403 result_type=str, 1404 ).lower() 1405 1406 ## Build a vdict for each vlan modification statement 1407 vdict = { 1408 "absolute_str": abs_str, 1409 "add_str": add_str, 1410 "except_str": exc_str, 1411 "remove_str": rem_str, 1412 } 1413 1414 ## Analyze each vdict in sequence and apply to retval sequentially 1415 for key, val in vdict.items(): 1416 if val != "_nomatch_": 1417 ## absolute in the key overrides previous values 1418 if "absolute" in key: 1419 if val.lower() == "all": 1420 retval = CiscoRange( 1421 "1-{0}".format(MAX_VLAN), result_type=int 1422 ) 1423 elif val.lower() == "none": 1424 retval = CiscoRange(result_type=int) 1425 else: 1426 retval = CiscoRange(val, result_type=int) 1427 elif "add" in key: 1428 retval.append(val) 1429 elif "except" in key: 1430 retval = CiscoRange("1-{0}".format(MAX_VLAN), result_type=int) 1431 retval.remove(val) 1432 elif "remove" in key: 1433 retval.remove(val) 1434 1435 return retval 1436 1437 @property 1438 def native_vlan(self): 1439 """Return an integer with the native vlan number. Return 1, if the switchport has no explicit native vlan configured; return 0 if the port isn't a switchport""" 1440 if self.is_switchport: 1441 default_val = 1 1442 else: 1443 default_val = 0 1444 retval = self.re_match_iter_typed( 1445 r"^\s*switchport\s+trunk\s+native\s+vlan\s+(\d+)$", 1446 result_type=int, 1447 default=default_val, 1448 ) 1449 return retval 1450 1451 ##------------- CDP 1452 1453 @property 1454 def has_manual_disable_cdp(self): 1455 retval = self.re_match_iter_typed( 1456 r"^\s*(no\s+cdp\s+enable\s*)", result_type=bool, default=False 1457 ) 1458 return retval 1459 1460 ##------------- EoMPLS 1461 1462 @property 1463 def has_xconnect(self): 1464 return bool(self.xconnect_vc) 1465 1466 @property 1467 def xconnect_vc(self): 1468 retval = self.re_match_iter_typed( 1469 r"^\s*xconnect\s+\S+\s+(\d+)\s+\S+", result_type=int, default=0 1470 ) 1471 return retval 1472 1473 ##------------- HSRP 1474 1475 @property 1476 def has_ip_hsrp(self): 1477 return bool(self.hsrp_ip_addr) 1478 1479 @property 1480 def hsrp_ip_addr(self): 1481 ## NOTE: I have no intention of checking self.is_shutdown here 1482 ## People should be able to check the sanity of interfaces 1483 ## before they put them into production 1484 1485 ## For API simplicity, I always assume there is only one hsrp 1486 ## group on the interface 1487 if self.ipv4_addr == "": 1488 return "" 1489 1490 retval = self.re_match_iter_typed( 1491 r"^\s*standby\s+(\d+\s+)*ip\s+(\S+)", group=2, result_type=str, default="" 1492 ) 1493 return retval 1494 1495 @property 1496 def hsrp_ip_mask(self): 1497 ## NOTE: I have no intention of checking self.is_shutdown here 1498 ## People should be able to check the sanity of interfaces 1499 ## before they put them into production 1500 1501 ## For API simplicity, I always assume there is only one hsrp 1502 ## group on the interface 1503 if self.ipv4_addr == "": 1504 return "" 1505 retval = self.re_match_iter_typed( 1506 r"^\s*standby\s+(\d+\s+)*ip\s+\S+\s+(\S+)\s*$", 1507 group=2, 1508 result_type=str, 1509 default="", 1510 ) 1511 return retval 1512 1513 @property 1514 def hsrp_group(self): 1515 ## For API simplicity, I always assume there is only one hsrp 1516 ## group on the interface 1517 retval = self.re_match_iter_typed( 1518 r"^\s*standby\s+(\d+)\s+ip\s+\S+", result_type=int, default=-1 1519 ) 1520 return retval 1521 1522 @property 1523 def hsrp_priority(self): 1524 ## For API simplicity, I always assume there is only one hsrp 1525 ## group on the interface 1526 if not self.has_ip_hsrp: 1527 return 0 # Return this if there is no hsrp on the interface 1528 retval = self.re_match_iter_typed( 1529 r"^\s*standby\s+(\d+\s+)*priority\s+(\d+)", 1530 group=2, 1531 result_type=int, 1532 default=100, 1533 ) 1534 return retval 1535 1536 @property 1537 def hsrp_hello_timer(self): 1538 ## For API simplicity, I always assume there is only one hsrp 1539 ## group on the interface 1540 1541 # FIXME: handle msec timers... 1542 retval = self.re_match_iter_typed( 1543 r"^\s*standby\s+(\d+\s+)*timers\s+(\d+)\s+\d+", 1544 group=2, 1545 result_type=float, 1546 default=0.0, 1547 ) 1548 return retval 1549 1550 @property 1551 def hsrp_hold_timer(self): 1552 ## For API simplicity, I always assume there is only one hsrp 1553 ## group on the interface 1554 1555 # FIXME: this should be a float (in case of msec timers) 1556 retval = self.re_match_iter_typed( 1557 r"^\s*standby\s+(\d+\s+)*timers\s+\d+\s+(\d+)", 1558 group=2, 1559 result_type=float, 1560 default=0.0, 1561 ) 1562 return retval 1563 1564 @property 1565 def has_hsrp_track(self): 1566 return bool(self.hsrp_track) 1567 1568 @property 1569 def hsrp_track(self): 1570 ## For API simplicity, I always assume there is only one hsrp 1571 ## group on the interface 1572 retval = self.re_match_iter_typed( 1573 r"^\s*standby\s+(\d+\s+)*track\s(\S+.+?)\s+\d+\s*", 1574 group=2, 1575 result_type=str, 1576 default="", 1577 ) 1578 return retval 1579 1580 @property 1581 def has_hsrp_usebia(self): 1582 ## For API simplicity, I always assume there is only one hsrp 1583 ## group on the interface 1584 retval = self.re_match_iter_typed( 1585 r"^\s*standby\s+(\d+\s+)*(use-bia)", 1586 group=2, 1587 result_type=bool, 1588 default=False, 1589 ) 1590 return retval 1591 1592 @property 1593 def has_hsrp_preempt(self): 1594 ## For API simplicity, I always assume there is only one hsrp 1595 ## group on the interface 1596 retval = self.re_match_iter_typed( 1597 r"^\s*standby\s+(\d+\s+)*(use-bia)", 1598 group=2, 1599 result_type=bool, 1600 default=False, 1601 ) 1602 return retval 1603 1604 @property 1605 def hsrp_authentication_md5_keychain(self): 1606 ## For API simplicity, I always assume there is only one hsrp 1607 ## group on the interface 1608 retval = self.re_match_iter_typed( 1609 r"^\s*standby\s+(\d+\s+)*authentication\s+md5\s+key-chain\s+(\S+)", 1610 group=2, 1611 result_type=str, 1612 default="", 1613 ) 1614 return retval 1615 1616 @property 1617 def has_hsrp_authentication_md5(self): 1618 keychain = self.hsrp_authentication_md5_keychain 1619 return bool(keychain) 1620 1621 @property 1622 def hsrp_authentication_cleartext(self): 1623 pass 1624 1625 ##------------- MAC ACLs 1626 1627 @property 1628 def has_mac_accessgroup_in(self): 1629 if not self.is_switchport: 1630 return False 1631 return bool(self.mac_accessgroup_in) 1632 1633 @property 1634 def has_mac_accessgroup_out(self): 1635 if not self.is_switchport: 1636 return False 1637 return bool(self.mac_accessgroup_out) 1638 1639 @property 1640 def mac_accessgroup_in(self): 1641 retval = self.re_match_iter_typed( 1642 r"^\s*mac\saccess-group\s+(\S+)\s+in\s*$", result_type=str, default="" 1643 ) 1644 return retval 1645 1646 @property 1647 def mac_accessgroup_out(self): 1648 retval = self.re_match_iter_typed( 1649 r"^\s*mac\saccess-group\s+(\S+)\s+out\s*$", result_type=str, default="" 1650 ) 1651 return retval 1652 1653 ##------------- IPv4 ACLs 1654 1655 @property 1656 def has_ip_accessgroup_in(self): 1657 return bool(self.ipv4_accessgroup_in) 1658 1659 @property 1660 def has_ip_accessgroup_out(self): 1661 return bool(self.ipv4_accessgroup_out) 1662 1663 @property 1664 def has_ipv4_accessgroup_in(self): 1665 return bool(self.ipv4_accessgroup_in) 1666 1667 @property 1668 def has_ipv4_accessgroup_out(self): 1669 return bool(self.ipv4_accessgroup_out) 1670 1671 @property 1672 def ip_accessgroup_in(self): 1673 return self.ipv4_accessgroup_in 1674 1675 @property 1676 def ip_accessgroup_out(self): 1677 return self.ipv4_accessgroup_out 1678 1679 @property 1680 def ipv4_accessgroup_in(self): 1681 retval = self.re_match_iter_typed( 1682 r"^\s*ip\saccess-group\s+(\S+)\s+in\s*$", result_type=str, default="" 1683 ) 1684 return retval 1685 1686 @property 1687 def ipv4_accessgroup_out(self): 1688 retval = self.re_match_iter_typed( 1689 r"^\s*ip\saccess-group\s+(\S+)\s+out\s*$", result_type=str, default="" 1690 ) 1691 return retval 1692 1693 1694## 1695##------------- IOS Interface Object 1696## 1697 1698 1699class IOSIntfLine(BaseIOSIntfLine): 1700 def __init__(self, *args, **kwargs): 1701 """Accept an IOS line number and initialize family relationship 1702 attributes 1703 1704 Warnings 1705 -------- 1706 All :class:`~models_cisco.IOSIntfLine` methods are still considered beta-quality, until this notice is removed. The behavior of APIs on this object could change at any time. 1707 """ 1708 super(IOSIntfLine, self).__init__(*args, **kwargs) 1709 self.feature = "interface" 1710 1711 @classmethod 1712 def is_object_for(cls, line="", re=re): 1713 intf_regex = r"^interface\s+(\S+.+)" 1714 if re.search(intf_regex, line): 1715 return True 1716 return False 1717 1718 1719## 1720##------------- IOS Interface Globals 1721## 1722 1723 1724class IOSIntfGlobal(BaseCfgLine): 1725 def __init__(self, *args, **kwargs): 1726 super(IOSIntfGlobal, self).__init__(*args, **kwargs) 1727 self.feature = "interface global" 1728 1729 def __repr__(self): 1730 return "<%s # %s '%s'>" % (self.classname, self.linenum, self.text) 1731 1732 @classmethod 1733 def is_object_for(cls, line="", re=re): 1734 if re.search( 1735 "^(no\s+cdp\s+run)|(logging\s+event\s+link-status\s+global)|(spanning-tree\sportfast\sdefault)|(spanning-tree\sportfast\sbpduguard\sdefault)", 1736 line, 1737 ): 1738 return True 1739 return False 1740 1741 @property 1742 def has_cdp_disabled(self): 1743 if self.re_search("^no\s+cdp\s+run\s*"): 1744 return True 1745 return False 1746 1747 @property 1748 def has_intf_logging_def(self): 1749 if self.re_search("^logging\s+event\s+link-status\s+global"): 1750 return True 1751 return False 1752 1753 @property 1754 def has_stp_portfast_def(self): 1755 if self.re_search("^spanning-tree\sportfast\sdefault"): 1756 return True 1757 return False 1758 1759 @property 1760 def has_stp_portfast_bpduguard_def(self): 1761 if self.re_search("^spanning-tree\sportfast\sbpduguard\sdefault"): 1762 return True 1763 return False 1764 1765 @property 1766 def has_stp_mode_rapidpvst(self): 1767 if self.re_search("^spanning-tree\smode\srapid-pvst"): 1768 return True 1769 return False 1770 1771 1772## 1773##------------- IOS Hostname Line 1774## 1775 1776 1777class IOSHostnameLine(BaseCfgLine): 1778 def __init__(self, *args, **kwargs): 1779 super(IOSHostnameLine, self).__init__(*args, **kwargs) 1780 self.feature = "hostname" 1781 1782 def __repr__(self): 1783 return "<%s # %s '%s'>" % (self.classname, self.linenum, self.hostname) 1784 1785 @classmethod 1786 def is_object_for(cls, line="", re=re): 1787 if re.search("^hostname", line): 1788 return True 1789 return False 1790 1791 @property 1792 def hostname(self): 1793 retval = self.re_match_typed(r"^hostname\s+(\S+)", result_type=str, default="") 1794 return retval 1795 1796 1797## 1798##------------- IOS Access Line 1799## 1800 1801 1802class IOSAccessLine(BaseCfgLine): 1803 def __init__(self, *args, **kwargs): 1804 super(IOSAccessLine, self).__init__(*args, **kwargs) 1805 self.feature = "access line" 1806 1807 def __repr__(self): 1808 return "<%s # %s '%s' info: '%s'>" % ( 1809 self.classname, 1810 self.linenum, 1811 self.name, 1812 self.range_str, 1813 ) 1814 1815 @classmethod 1816 def is_object_for(cls, line="", re=re): 1817 if re.search("^line", line): 1818 return True 1819 return False 1820 1821 @property 1822 def is_accessline(self): 1823 retval = self.re_match_typed(r"^(line\s+\S+)", result_type=str, default="") 1824 return bool(retval) 1825 1826 @property 1827 def name(self): 1828 retval = self.re_match_typed(r"^line\s+(\S+)", result_type=str, default="") 1829 # special case for IOS async lines: i.e. "line 33 48" 1830 if re.search("\d+", retval): 1831 return "" 1832 return retval 1833 1834 def reset(self, atomic=True): 1835 # Insert build_reset_string() before this line... 1836 self.insert_before(self.build_reset_string(), atomic=atomic) 1837 1838 def build_reset_string(self): 1839 # IOS interfaces are defaulted like this... 1840 return "default " + self.text 1841 1842 @property 1843 def range_str(self): 1844 return " ".join(map(str, self.line_range)) 1845 1846 @property 1847 def line_range(self): 1848 ## Return the access-line's numerical range as a list 1849 ## line con 0 => [0] 1850 ## line 33 48 => [33, 48] 1851 retval = self.re_match_typed( 1852 r"([a-zA-Z]+\s+)*(\d+\s*\d*)$", group=2, result_type=str, default="" 1853 ) 1854 tmp = map(int, retval.strip().split()) 1855 return tmp 1856 1857 def manual_exectimeout_min(self): 1858 tmp = self.parse_exectimeout 1859 return tmp[0] 1860 1861 def manual_exectimeout_sec(self): 1862 tmp = self.parse_exectimeout 1863 if len(tmp > 0): 1864 return 0 1865 return tmp[1] 1866 1867 @property 1868 def parse_exectimeout(self): 1869 retval = self.re_match_iter_typed( 1870 r"^\s*exec-timeout\s+(\d+\s*\d*)\s*$", group=1, result_type=str, default="" 1871 ) 1872 tmp = map(int, retval.strip().split()) 1873 return tmp 1874 1875 1876## 1877##------------- Base IOS Route line object 1878## 1879 1880 1881class BaseIOSRouteLine(BaseCfgLine): 1882 def __init__(self, *args, **kwargs): 1883 super(BaseIOSRouteLine, self).__init__(*args, **kwargs) 1884 1885 def __repr__(self): 1886 return "<%s # %s '%s' info: '%s'>" % ( 1887 self.classname, 1888 self.linenum, 1889 self.network_object, 1890 self.routeinfo, 1891 ) 1892 1893 @property 1894 def routeinfo(self): 1895 ### Route information for the repr string 1896 if self.tracking_object_name: 1897 return ( 1898 self.nexthop_str 1899 + " AD: " 1900 + str(self.admin_distance) 1901 + " Track: " 1902 + self.tracking_object_name 1903 ) 1904 else: 1905 return self.nexthop_str + " AD: " + str(self.admin_distance) 1906 1907 @classmethod 1908 def is_object_for(cls, line="", re=re): 1909 return False 1910 1911 @property 1912 def vrf(self): 1913 raise NotImplementedError 1914 1915 @property 1916 def address_family(self): 1917 ## ipv4, ipv6, etc 1918 raise NotImplementedError 1919 1920 @property 1921 def network(self): 1922 raise NotImplementedError 1923 1924 @property 1925 def netmask(self): 1926 raise NotImplementedError 1927 1928 @property 1929 def admin_distance(self): 1930 raise NotImplementedError 1931 1932 @property 1933 def nexthop_str(self): 1934 raise NotImplementedError 1935 1936 @property 1937 def tracking_object_name(self): 1938 raise NotImplementedError 1939 1940 1941## 1942##------------- IOS Route line object 1943## 1944 1945_RE_IP_ROUTE = re.compile( 1946 r"""^ip\s+route 1947(?:\s+(?:vrf\s+(?P<vrf>\S+)))? # VRF detection 1948\s+ 1949(?P<prefix>\d+\.\d+\.\d+\.\d+) # Prefix detection 1950\s+ 1951(?P<netmask>\d+\.\d+\.\d+\.\d+) # Netmask detection 1952(?:\s+(?P<nh_intf>[^\d]\S+))? # NH intf 1953(?:\s+(?P<nh_addr>\d+\.\d+\.\d+\.\d+))? # NH addr 1954(?:\s+(?P<dhcp>dhcp))? # DHCP keyword (FIXME: add unit test) 1955(?:\s+(?P<global>global))? # Global keyword 1956(?:\s+(?P<ad>\d+))? # Administrative distance 1957(?:\s+(?P<mcast>multicast))? # Multicast Keyword (FIXME: add unit test) 1958(?:\s+name\s+(?P<name>\S+))? # Route name 1959(?:\s+(?P<permanent>permanent))? # Permanent Keyword (exclusive of track) 1960(?:\s+track\s+(?P<track>\d+))? # Track object (exclusive of permanent) 1961(?:\s+tag\s+(?P<tag>\d+))? # Route tag 1962""", 1963 re.VERBOSE, 1964) 1965 1966_RE_IPV6_ROUTE = re.compile( 1967 r"""^ipv6\s+route 1968(?:\s+vrf\s+(?P<vrf>\S+))? 1969(?:\s+(?P<prefix>{0})\/(?P<masklength>\d+)) # Prefix detection 1970(?: 1971 (?:\s+(?P<nh_addr1>{1})) 1972 |(?:\s+(?P<nh_intf>\S+(?:\s+\d\S*?\/\S+)?)(?:\s+(?P<nh_addr2>{2}))?) 1973) 1974(?:\s+nexthop-vrf\s+(?P<nexthop_vrf>\S+))? 1975(?:\s+(?P<ad>\d+))? # Administrative distance 1976(?:\s+(?:(?P<ucast>unicast)|(?P<mcast>multicast)))? 1977(?:\s+tag\s+(?P<tag>\d+))? # Route tag 1978(?:\s+track\s+(?P<track>\d+))? # Track object 1979(?:\s+name\s+(?P<name>\S+))? # Route name 1980""".format( 1981 _IPV6_REGEX_STR_COMPRESSED1, 1982 _IPV6_REGEX_STR_COMPRESSED2, 1983 _IPV6_REGEX_STR_COMPRESSED3, 1984 ), 1985 re.VERBOSE, 1986) 1987 1988 1989class IOSRouteLine(BaseIOSRouteLine): 1990 def __init__(self, *args, **kwargs): 1991 super(IOSRouteLine, self).__init__(*args, **kwargs) 1992 if "ipv6" in self.text[0:4]: 1993 self.feature = "ipv6 route" 1994 self._address_family = "ipv6" 1995 mm = _RE_IPV6_ROUTE.search(self.text) 1996 if not (mm is None): 1997 self.route_info = mm.groupdict() 1998 else: 1999 raise ValueError("Could not parse '{0}'".format(self.text)) 2000 else: 2001 self.feature = "ip route" 2002 self._address_family = "ip" 2003 mm = _RE_IP_ROUTE.search(self.text) 2004 if not (mm is None): 2005 self.route_info = mm.groupdict() 2006 else: 2007 raise ValueError("Could not parse '{0}'".format(self.text)) 2008 2009 @classmethod 2010 def is_object_for(cls, line="", re=re): 2011 if (line[0:8] == "ip route") or (line[0:11] == "ipv6 route "): 2012 return True 2013 return False 2014 2015 @property 2016 def vrf(self): 2017 if not (self.route_info["vrf"] is None): 2018 return self.route_info["vrf"] 2019 else: 2020 return "" 2021 2022 @property 2023 def address_family(self): 2024 ## ipv4, ipv6, etc 2025 return self._address_family 2026 2027 @property 2028 def network(self): 2029 if self._address_family == "ip": 2030 return self.route_info["prefix"] 2031 elif self._address_family == "ipv6": 2032 retval = self.re_match_typed( 2033 r"^ipv6\s+route\s+(vrf\s+)*(\S+?)\/\d+", 2034 group=2, 2035 result_type=str, 2036 default="", 2037 ) 2038 return retval 2039 2040 @property 2041 def netmask(self): 2042 if self._address_family == "ip": 2043 return self.route_info["netmask"] 2044 elif self._address_family == "ipv6": 2045 return str(self.network_object.netmask) 2046 return retval 2047 2048 @property 2049 def masklen(self): 2050 if self._address_family == "ip": 2051 return self.network_object.prefixlen 2052 elif self._address_family == "ipv6": 2053 masklen_str = self.route_info["masklength"] or "128" 2054 return int(masklen_str) 2055 2056 @property 2057 def network_object(self): 2058 try: 2059 if self._address_family == "ip": 2060 return IPv4Obj("%s/%s" % (self.network, self.netmask), strict=False) 2061 elif self._address_family == "ipv6": 2062 return IPv6Obj("%s/%s" % (self.network, self.masklen)) 2063 except: 2064 return None 2065 2066 @property 2067 def nexthop_str(self): 2068 if self._address_family == "ip": 2069 if self.next_hop_interface: 2070 return self.next_hop_interface + " " + self.next_hop_addr 2071 else: 2072 return self.next_hop_addr 2073 elif self._address_family == "ipv6": 2074 retval = self.re_match_typed( 2075 r"^ipv6\s+route\s+(vrf\s+)*\S+\s+(\S+)", 2076 group=2, 2077 result_type=str, 2078 default="", 2079 ) 2080 return retval 2081 2082 @property 2083 def next_hop_interface(self): 2084 if self._address_family == "ip": 2085 if self.route_info["nh_intf"]: 2086 return self.route_info["nh_intf"] 2087 else: 2088 return "" 2089 elif self._address_family == "ipv6": 2090 if self.route_info["nh_intf"]: 2091 return self.route_info["nh_intf"] 2092 else: 2093 return "" 2094 2095 @property 2096 def next_hop_addr(self): 2097 if self._address_family == "ip": 2098 return self.route_info["nh_addr"] or "" 2099 elif self._address_family == "ipv6": 2100 return self.route_info["nh_addr1"] or self.route_info["nh_addr2"] or "" 2101 2102 @property 2103 def global_next_hop(self): 2104 if self._address_family == "ip" and bool(self.vrf): 2105 return bool(self.route_info["global"]) 2106 elif self._address_family == "ip" and not bool(self.vrf): 2107 return True 2108 elif self._address_family == "ipv6": 2109 ## ipv6 uses nexthop_vrf 2110 raise ValueError( 2111 "[FATAL] ipv6 doesn't support a global_next_hop for '{0}'".format( 2112 self.text 2113 ) 2114 ) 2115 else: 2116 raise ValueError( 2117 "[FATAL] Could not identify global next-hop for '{0}'".format(self.text) 2118 ) 2119 2120 @property 2121 def nexthop_vrf(self): 2122 if self._address_family == "ipv6": 2123 return self.route_info["nexthop_vrf"] or "" 2124 else: 2125 raise ValueError( 2126 "[FATAL] ip doesn't support a global_next_hop for '{0}'".format( 2127 self.text 2128 ) 2129 ) 2130 2131 @property 2132 def admin_distance(self): 2133 if self.route_info["ad"]: 2134 return int(self.route_info["ad"]) 2135 else: 2136 return 1 2137 2138 @property 2139 def multicast(self): 2140 """Return whether the multicast keyword was specified""" 2141 return bool(self.route_info["mcast"]) 2142 2143 @property 2144 def unicast(self): 2145 ## FIXME It's unclear how to implement this... 2146 raise NotImplementedError 2147 2148 @property 2149 def route_name(self): 2150 if self.route_info["name"]: 2151 return self.route_info["name"] 2152 else: 2153 return "" 2154 2155 @property 2156 def permanent(self): 2157 if self._address_family == "ip": 2158 if self.route_info["permanent"]: 2159 return bool(self.route_info["permanent"]) 2160 else: 2161 return False 2162 elif self._address_family == "ipv6": 2163 raise NotImplementedError 2164 2165 @property 2166 def tracking_object_name(self): 2167 if bool(self.route_info["track"]): 2168 return self.route_info["track"] 2169 else: 2170 return "" 2171 2172 @property 2173 def tag(self): 2174 return self.route_info["tag"] or "" 2175 2176 2177################################ 2178################################ Groups ############################### 2179################################ 2180 2181 2182## 2183##------------- IOS TACACS+ Group 2184## 2185class IOSAaaGroupServerLine(BaseCfgLine): 2186 def __init__(self, *args, **kwargs): 2187 super(IOSAaaGroupServerLine, self).__init__(*args, **kwargs) 2188 self.feature = "aaa group server" 2189 2190 REGEX = r"^aaa\sgroup\sserver\s(?P<protocol>\S+)\s(?P<group>\S+)\s*$" 2191 mm = re.search(REGEX, self.text) 2192 if not (mm is None): 2193 groups = mm.groupdict() 2194 self.protocol = groups.get("protocol", "") 2195 self.group = groups.get("group", "") 2196 else: 2197 raise ValueError 2198 2199 @classmethod 2200 def is_object_for(cls, line="", re=re): 2201 if re.search(r"^aaa\sgroup\sserver", line): 2202 return True 2203 return False 2204 2205 @property 2206 def vrf(self): 2207 return self.re_match_iter_typed( 2208 r"^\s+(ip\s+)*vrf\s+forwarding\s+(\S+)", 2209 group=2, 2210 result_type=str, 2211 default="", 2212 ) 2213 2214 @property 2215 def source_interface(self): 2216 return self.re_match_iter_typed( 2217 r"^\s+ip\s+tacacs\s+source-interface\s+(\S.+?\S)\s*$", 2218 group=1, 2219 result_type=str, 2220 default="", 2221 ) 2222 2223 @property 2224 def server_private(self, re=re): 2225 retval = set([]) 2226 rgx_priv = re.compile("^\s+server-private\s+(\S+)\s") 2227 for cobj in self.children: 2228 mm = rgx_priv.search(cobj.text) 2229 if not (mm is None): 2230 retval.add(mm.group(1)) # This is the server's ip 2231 return retval 2232 2233 2234## 2235##------------- IOS AAA Lines 2236## 2237 2238 2239class IOSAaaLoginAuthenticationLine(BaseCfgLine): 2240 def __init__(self, *args, **kwargs): 2241 super(IOSAaaLoginAuthenticationLine, self).__init__(*args, **kwargs) 2242 self.feature = "aaa authentication login" 2243 2244 regex = r"^aaa\sauthentication\slogin\s(\S+)\sgroup\s(\S+)(.+?)$" 2245 self.list_name = self.re_match_typed( 2246 regex, group=1, result_type=str, default="" 2247 ) 2248 self.group = self.re_match_typed(regex, group=2, result_type=str, default="") 2249 methods_str = self.re_match_typed(regex, group=3, result_type=str, default="") 2250 self.methods = methods_str.strip().split("\s") 2251 2252 @classmethod 2253 def is_object_for(cls, line="", re=re): 2254 if re.search(r"^aaa\sauthentication\slogin", line): 2255 return True 2256 return False 2257 2258 2259class IOSAaaEnableAuthenticationLine(BaseCfgLine): 2260 def __init__(self, *args, **kwargs): 2261 super(IOSAaaEnableAuthenticationLine, self).__init__(*args, **kwargs) 2262 self.feature = "aaa authentication enable" 2263 2264 regex = r"^aaa\sauthentication\senable\s(\S+)\sgroup\s(\S+)(.+?)$" 2265 self.list_name = self.re_match_typed( 2266 regex, group=1, result_type=str, default="" 2267 ) 2268 self.group = self.re_match_typed(regex, group=2, result_type=str, default="") 2269 methods_str = self.re_match_typed(regex, group=3, result_type=str, default="") 2270 self.methods = methods_str.strip().split("\s") 2271 2272 @classmethod 2273 def is_object_for(cls, line="", re=re): 2274 if re.search(r"^aaa\sauthentication\senable", line): 2275 return True 2276 return False 2277 2278 2279class IOSAaaCommandsAuthorizationLine(BaseCfgLine): 2280 def __init__(self, *args, **kwargs): 2281 super(IOSAaaCommandsAuthorizationLine, self).__init__(*args, **kwargs) 2282 self.feature = "aaa authorization commands" 2283 2284 regex = r"^aaa\sauthorization\scommands\s(\d+)\s(\S+)\sgroup\s(\S+)(.+?)$" 2285 self.level = self.re_match_typed(regex, group=1, result_type=int, default=0) 2286 self.list_name = self.re_match_typed( 2287 regex, group=2, result_type=str, default="" 2288 ) 2289 self.group = self.re_match_typed(regex, group=3, result_type=str, default="") 2290 methods_str = self.re_match_typed(regex, group=4, result_type=str, default="") 2291 self.methods = methods_str.strip().split("\s") 2292 2293 @classmethod 2294 def is_object_for(cls, line="", re=re): 2295 if re.search(r"^aaa\sauthorization\scommands", line): 2296 return True 2297 return False 2298 2299 2300class IOSAaaCommandsAccountingLine(BaseCfgLine): 2301 def __init__(self, *args, **kwargs): 2302 super(IOSAaaCommandsAccountingLine, self).__init__(*args, **kwargs) 2303 self.feature = "aaa accounting commands" 2304 2305 regex = r"^aaa\saccounting\scommands\s(\d+)\s(\S+)\s(none|stop\-only|start\-stop)\sgroup\s(\S+)$" 2306 self.level = self.re_match_typed(regex, group=1, result_type=int, default=0) 2307 self.list_name = self.re_match_typed( 2308 regex, group=2, result_type=str, default="" 2309 ) 2310 self.record_type = self.re_match_typed( 2311 regex, group=3, result_type=str, default="" 2312 ) 2313 self.group = self.re_match_typed(regex, group=4, result_type=str, default="") 2314 2315 @classmethod 2316 def is_object_for(cls, line="", re=re): 2317 if re.search(r"^aaa\saccounting\scommands", line): 2318 return True 2319 return False 2320 2321 2322class IOSAaaExecAccountingLine(BaseCfgLine): 2323 def __init__(self, *args, **kwargs): 2324 super(IOSAaaExecAccountingLine, self).__init__(*args, **kwargs) 2325 self.feature = "aaa accounting exec" 2326 2327 regex = r"^aaa\saccounting\sexec\s(\S+)\s(none|stop\-only|start\-stop)\sgroup\s(\S+)$" 2328 self.list_name = self.re_match_typed( 2329 regex, group=1, result_type=str, default="" 2330 ) 2331 self.record_type = self.re_match_typed( 2332 regex, group=2, result_type=str, default="" 2333 ) 2334 self.group = self.re_match_typed(regex, group=3, result_type=str, default="") 2335 2336 @classmethod 2337 def is_object_for(cls, line="", re=re): 2338 if re.search(r"^aaa\saccounting\sexec", line): 2339 return True 2340 return False 2341