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