Skip to main content

Recent Posts

import subprocess
import logging
import re
from typing import Dict, Any, List, Optional


class BGPRouter:
    """BGP Router class for parsing and managing BGP route information."""
    
    def __init__(self, local_asn: str = '65412'):
        """Initialize BGP router with local ASN.
        
        Args:
            local_asn: Local BGP ASN number
        """
        self.local_asn = local_asn
        
    def _normalize_network_cidr(self, network: str) -> str:
        """Normalize network address by adding appropriate CIDR notation.
        
        Args:
            network: Network address (e.g., "172.31.0.0" or "172.16.0.1/32")
            
        Returns:
            Network address with appropriate CIDR notation
        """
        if '/' in network:
            return network
            
        try:
            octets = network.split('.')
            if len(octets) != 4:
                return network  # Invalid IP format or IPv6, return as-is

            # Determine CIDR based on trailing zero pattern

            # Check for default route
            if network == '0.0.0.0':
                return '0.0.0.0/0'

            if octets[1:] == ['0', '0', '0']:
                return f"{network}/8"

            if octets[2:] == ['0', '0']:
                return f"{network}/16"

            if octets[3] == '0':
                return f"{network}/24"
 
        except (ValueError, IndexError):
            return network
        

    def _parse_as_path(self, path_info: str) -> str:
        """Extract AS path from BGP path information.
        
        Args:
            path_info: Raw path information from BGP output
            
        Returns:
            Cleaned AS path string
        """
        path_info = path_info.strip()
        
        # Handle internal routes
        if path_info == 'i':
            return self.local_asn
            
        # Extract AS numbers using list comprehension
        path_parts = path_info.split()
        as_numbers = [part for part in path_parts if part.isdigit()]
        
        return ' '.join(as_numbers)

    def _parse_route_line(self, line: str) -> Optional[Dict[str, Any]]:
        """Parse a single BGP route line.
        
        Args:
            line: BGP route line from show command output
            
        Returns:
            Route dictionary or None if parsing fails
        """
        pattern = r'^\*>\s+(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(.+)$'
        match = re.match(pattern, line)
        if not match:
            return None
            
        network, next_hop, metric_str, weight_str, path_info = match.groups()
        
        try:
            return {
                "network": self._normalize_network_cidr(network),
                "nextHopIp": next_hop,
                "med": int(metric_str),
                "localPref": 100, # Always 100 for learned routes on PEs
                "weight": int(weight_str),
                "asPath": self._parse_as_path(path_info)
            }
        except ValueError as e:
            logging.warning(f"Failed to parse route line '{line}': {e}")
            return None

    def _find_route_start_index(self, lines: List[str]) -> Optional[int]:
        """Find the index where BGP routes start in the output.
        
        Args:
            lines: List of output lines
            
        Returns:
            Index of first route line or None if not found
        """
        for i, line in enumerate(lines):
            if 'Network' in line and 'Next Hop' in line:
                return i + 1
        return None

    def _get_all_bgp_routes(self) -> Dict[str, List[Dict[str, Any]]]:
        """Parse BGP route table output and return structured data.
        
        Returns:
            Dictionary containing list of BGP routes
        """
        try:
            output = """BGP table version is 0, local router ID is 169.254.112.97
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
            r RIB-failure, S Stale, R Removed
Origin codes: i - IGP, e - EGP, ? - incomplete

Network          Next Hop            Metric LocPrf Weight Path
*> 0.0.0.0          169.254.112.98        0             0 65000 i
*> 172.16.0.1/32    169.254.112.98        0             0 65000 i
*> 172.16.0.2/32    169.254.112.98        0             0 65000 65114 49449 i
*> 172.16.0.3/32    169.254.112.98        0             0 65000 i
*> 172.16.0.4/32    169.254.112.98        0             0 65000 i
*> 172.16.0.5/32    169.254.112.98        0             0 65000 i
*> 172.16.0.112/32  169.254.112.98        0             0 65000 i
*> 172.16.0.113/32  169.254.112.98        0             0 65000 i
*> 172.16.0.114/32  169.254.112.98        0             0 65000 i
*> 172.16.0.115/32  169.254.112.98        0             0 65000 i
*> 172.16.0.116/32  169.254.112.98        0             0 65000 i
*> 172.16.0.117/32  169.254.112.98        0             0 65000 i
*> 172.16.0.118/32  169.254.112.98        0             0 65000 i
*> 172.16.0.119/32  169.254.112.98        0             0 65000 i
*> 172.16.0.120/32  169.254.112.98        0             0 65000 i
*> 172.16.0.121/32  169.254.112.98        0             0 65000 i
*> 172.31.0.0       169.254.112.97        100           32768 i
*> 172.0.0.0        169.254.112.97        100           32768 i
*> 172.1.1.0        169.254.112.97        100           32768 i
*> 172.31.0.1/32    169.254.112.97        100           32768 i
*> 172.31.0.2/32    169.254.112.97        100           32768 i
*> 172.31.0.3/32    169.254.112.97        100           32768 i
*> 172.31.0.4/32    169.254.112.97        100           32768 i
*> 2001:db8:2::/64  fe80::2               100           32768 i
*> 2001:db8:2::1/128 fe80::2              100           32768 i
*> 2001:db8:2::2/128 fe80::2              100           32768 i
*> 2001:db8:2::3/128 fe80::2              100           32768 i
*> 2001:db8:2::4/128 fe80::2              100           32768 i

Total number of prefixes 3233"""
            
            # Check if command output is valid
            if not output or not output.strip():
                logging.warning("BGP command returned empty output")
                return {"routes": []}
            
            lines = output.strip().split('\n')
            route_start_idx = self._find_route_start_index(lines)
            
            if route_start_idx is None:
                logging.warning("BGP output does not contain expected header format")
                return {"routes": []}
            
            # Parse routes using list comprehension and filter
            route_lines = [
                line.strip() for line in lines[route_start_idx:]
                if line.strip() and line.strip().startswith('*>') 
                and not line.strip().startswith('Total number')
            ]
            
            # Parse routes and convert to dictionaries
            routes = []
            for line in route_lines:
                route = self._parse_route_line(line)
                if route is not None:
                    routes.append(route)
            
            return {"routes": routes}
            
        except Exception as e:
            logging.error(f"Failed to get BGP routes: {e}")
            return {"routes": []}
    
    def get_specific_network(self, bgp_properties: Dict[str, Any], prefix: str) -> Dict[str, Any]:
        """Find specific network in BGP properties.
        
        Args:
            bgp_properties: BGP properties dictionary
            prefix: Network prefix to search for
            
        Returns:
            Route information dictionary or empty dict if not found
        """
        if not bgp_properties or not isinstance(bgp_properties, dict):
            logging.warning(f"Invalid BGP properties: {type(bgp_properties)}")
            return {}
            
        routes = bgp_properties.get("routes", [])
        logging.debug(f"Searching for prefix '{prefix}' in {len(routes)} routes")
        
        # Use next() with generator expression for efficient search
        try:
            route = next(
                route for route in routes 
                if route.get("network") == prefix
            )
            logging.debug(f"Found matching route for prefix '{prefix}': {route}")
            return route
        except StopIteration:
            logging.debug(f"No route found for prefix '{prefix}'")
            return {}


def main():
    """Main function to demonstrate BGP router functionality."""
    router = BGPRouter()
    
    # Get all BGP routes
    bgp_routes = router._get_all_bgp_routes()
    print(f"Parsed {len(bgp_routes['routes'])} BGP routes")
    print(bgp_routes)
    
    # # Display first few routes
    # for i, route in enumerate(bgp_routes['routes'][:3]):
    #     print(f"Route {i+1}: {route}")
    
    # # Search for specific prefix
    # specific_prefix = "172.16.0.1/32"
    # route_info = router.get_specific_network(bgp_routes, specific_prefix)
    
    # if route_info:
    #     print(f"Found route for {specific_prefix}: {route_info}")
    # else:
    #     print(f"No route found for {specific_prefix}")


if __name__ == "__main__":
    main()
import subprocess
import logging
import re
from typing import Dict, Any

class BGPRouter():
    def __init__(self):
        self.headend_config = type('obj', (object,), {'local_bgp_asn': '65412'})  # Mock config object

    def _normalize_network_cidr(self, network: str) -> str:
        """Normalize network address by adding appropriate CIDR notation
           when networks dont already contain them
        
        Args:
            network: Network address (e.g., "172.31.0.0" or "172.16.0.1/32")
            
        Returns:
            Network address with appropriate CIDR notation
        """
        if '/' in network:
            return network
            
        try:
            octets = network.split('.')
            if len(octets) != 4:
                return network  # Invalid IP format or IPv6, return as-is
                
            # Determine CIDR based on trailing zero pattern
            if octets[1:] == ['0', '0', '0']:
                return f"{network}/8"
            elif octets[2:] == ['0', '0']:
                return f"{network}/16"
            elif octets[3] == '0':
                return f"{network}/24"
            else:
                return f"{network}/32"
                
        except (ValueError, IndexError):
            return network

    def _get_all_bgp_attributes(self) -> Dict[str, Any]:
        """
        Parse BGP route table output and return structured data.
            
        Returns:
            dict: Structured BGP routes data
        """
        routes = []

        try:
            output = """BGP table version is 0, local router ID is 169.254.112.97
    Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
                r RIB-failure, S Stale, R Removed
    Origin codes: i - IGP, e - EGP, ? - incomplete

    Network          Next Hop            Metric LocPrf Weight Path
    *> 172.16.0.1/32    169.254.112.98        0             0 65000 i
    *> 172.16.0.2/32    169.254.112.98        0             0 65000 65114 49449 i
    *> 172.16.0.3/32    169.254.112.98        0             0 65000 i
    *> 172.16.0.4/32    169.254.112.98        0             0 65000 i
    *> 172.16.0.5/32    169.254.112.98        0             0 65000 i
    *> 172.16.0.112/32  169.254.112.98        0             0 65000 i
    *> 172.16.0.113/32  169.254.112.98        0             0 65000 i
    *> 172.16.0.114/32  169.254.112.98        0             0 65000 i
    *> 172.16.0.115/32  169.254.112.98        0             0 65000 i
    *> 172.16.0.116/32  169.254.112.98        0             0 65000 i
    *> 172.16.0.117/32  169.254.112.98        0             0 65000 i
    *> 172.16.0.118/32  169.254.112.98        0             0 65000 i
    *> 172.16.0.119/32  169.254.112.98        0             0 65000 i
    *> 172.16.0.120/32  169.254.112.98        0             0 65000 i
    *> 172.16.0.121/32  169.254.112.98        0             0 65000 i
    *> 172.31.0.0       169.254.112.97        100           32768 i
    *> 172.31.0.1/32    169.254.112.97        100           32768 i
    *> 172.31.0.2/32    169.254.112.97        100           32768 i
    *> 172.31.0.3/32    169.254.112.97        100           32768 i
    *> 172.31.0.4/32    169.254.112.97        100           32768 i
    *> 2001:db8:2::/64  fe80::2                  100           32768 i
    *> 2001:db8:2::1/128 fe80::2              100           32768 i
    *> 2001:db8:2::2/128 fe80::2              100           32768 i
    *> 2001:db8:2::3/128 fe80::2              100           32768 i
    *> 2001:db8:2::4/128 fe80::2              100           32768 i


    Total number of prefixes 3233"""
            
            # Check if command output is valid
            if not output or not output.strip():
                logging.warning("BGP command returned empty output")
                return {"routes": routes}
            
            # Split into lines and find the route entries
            lines = output.strip().split('\n')
            
            # Find the header line to identify where routes start
            route_start_idx = None
            for i, line in enumerate(lines):
                if 'Network' in line and 'Next Hop' in line:
                    route_start_idx = i + 1
                    break
            
            if route_start_idx is None:
                logging.warning("BGP output does not contain expected header format")
                return {"routes": routes}
            
            # Parse each route line
            for line in lines[route_start_idx:]:
                line = line.strip()
                
                # Skip empty lines and summary lines
                if not line or line.startswith('Total number'):
                    continue
                    
                # Skip lines that don't start with route status indicators
                if not line.startswith('*>'):
                    continue
                
                # Parse the route line using regex
                # Pattern matches: *> network next_hop metric [locprf] weight path
                pattern = r'^\*>\s+(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(.+)$'
                match = re.match(pattern, line)
                
                if match:
                    network = self._normalize_network_cidr(match.group(1))
                    next_hop = match.group(2)
                    metric = int(match.group(3))
                    # Set local preference to 100 as we always use 100 for learned routes on PEs
                    loc_prf = 100
                    weight = int(match.group(4))
                    path_info = match.group(5).strip()
                    
                    # Extract AS path (remove origin code)
                    path_parts = path_info.split()
                    as_path = []
                    for part in path_parts:
                        if part.isdigit():
                            as_path.append(part)
                    
                    path = ' '.join(as_path) if as_path else ""

                    # Set path to headend config local_bgp_asn if AS path is only "i" (internal route)
                    if path_info.strip() == "i":
                        path = str(self.headend_config.local_bgp_asn)
                        
                    route = {
                        "network": network,
                        "nextHopIp": next_hop,
                        "med": metric,
                        "localPref": loc_prf,
                        "weight": weight,
                        "asPath": path
                    }
                    routes.append(route)
            
            return {"routes": routes}
                    
        except Exception as e:
            logging.error(f"Failed to get all BGP prefixes: {e}")
            # Always return a valid dictionary structure even on failure
            return {"routes": routes}


    def _get_specific_network(self, bgp_properties, prefix):
        # Add null check and structure validation
        if not bgp_properties or not isinstance(bgp_properties, dict):
            logging.warning(f"BGP properties is None or invalid type: {type(bgp_properties)}")
            return {}
            
        if "routes" not in bgp_properties:
            logging.warning(f"BGP properties missing 'routes' key: {bgp_properties}")
            return {}
        
        routes = bgp_properties["routes"]
        logging.debug(f"Searching for prefix '{prefix}' in {len(routes)} BGP routes")
        
        # Search through routes for matching prefix
        for route in routes:
            if route.get("network") == prefix:
                logging.debug(f"Found matching route for prefix '{prefix}': {route}")
                return route
        
        # Return empty dict instead of None to avoid null attributes
        return {}

def main():
    router = BGPRouter()
    
    # Get all BGP attributes
    bgp_routes = router._get_all_bgp_attributes()  # Added underscore here

    print(bgp_routes)
    
    # Search for specific prefix
    # specific_prefix = "172.16.0.1/32"  # Added the specific IP you requested
    # route_info = router._get_specific_network(bgp_routes, specific_prefix)
    
    # if route_info:
    #     print(f"\nFound route information for {specific_prefix}:")
    #     print(route_info)
    # else:
    #     print(f"No route information found for {specific_prefix}")

if __name__ == "__main__":
    main()

"""
{
    'routes': [{
        'network': '172.16.0.1/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.16.0.2/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000 65114 49449'
    }, {
        'network': '172.16.0.3/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.16.0.4/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.16.0.5/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.16.0.112/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.16.0.113/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.16.0.114/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.16.0.115/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.16.0.116/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.16.0.117/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.16.0.118/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.16.0.119/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.16.0.120/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.16.0.121/32',
        'nextHopIp': '169.254.112.98',
        'med': 0,
        'localPref': 100,
        'weight': 0,
        'asPath': '65000'
    }, {
        'network': '172.31.0.0/16',
        'nextHopIp': '169.254.112.97',
        'med': 100,
        'localPref': 100,
        'weight': 32768,
        'asPath': '65412'
    }, {
        'network': '172.31.0.1/32',
        'nextHopIp': '169.254.112.97',
        'med': 100,
        'localPref': 100,
        'weight': 32768,
        'asPath': '65412'
    }, {
        'network': '172.31.0.2/32',
        'nextHopIp': '169.254.112.97',
        'med': 100,
        'localPref': 100,
        'weight': 32768,
        'asPath': '65412'
    }, {
        'network': '172.31.0.3/32',
        'nextHopIp': '169.254.112.97',
        'med': 100,
        'localPref': 100,
        'weight': 32768,
        'asPath': '65412'
    }, {
        'network': '172.31.0.4/32',
        'nextHopIp': '169.254.112.97',
        'med': 100,
        'localPref': 100,
        'weight': 32768,
        'asPath': '65412'
    }, {
        'network': '2001:db8:2::',
        'nextHopIp': 'fe80::2',
        'med': 100,
        'localPref': 100,
        'weight': 32768,
        'asPath': '65412'
    }, {
        'network': '2001:db8:2::1/128',
        'nextHopIp': 'fe80::2',
        'med': 100,
        'localPref': 100,
        'weight': 32768,
        'asPath': '65412'
    }, {
        'network': '2001:db8:2::2/128',
        'nextHopIp': 'fe80::2',
        'med': 100,
        'localPref': 100,
        'weight': 32768,
        'asPath': '65412'
    }, {
        'network': '2001:db8:2::3/128',
        'nextHopIp': 'fe80::2',
        'med': 100,
        'localPref': 100,
        'weight': 32768,
        'asPath': '65412'
    }, {
        'network': '2001:db8:2::4/128',
        'nextHopIp': 'fe80::2',
        'med': 100,
        'localPref': 100,
        'weight': 32768,
        'asPath': '65412'
    }]
}
"""
9:27 ip-10-0-87-26 wx-control[3759986]: 19:39:27.786 [main] DEBUG sapsucker.enigma.SapsuckerBgpLoggingClient - Processing records from /var/state/bgp_log_delivery_json_records.txt
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]: 19:39:27.786 [main] DEBUG sapsucker.enigma.utils.FileReaderHelper$ - Reading log data from /var/state/bgp_log_delivery_json_records.txt
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]: 19:39:27.829 [main] ERROR sapsucker.enigma.SapsuckerBgpLoggingClient - Exception caught in run loop!
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]: com.fasterxml.jackson.core.io.JsonEOFException: Unexpected end-of-input within/between Object entries
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:  at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 17]
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at com.fasterxml.jackson.core.base.ParserMinimalBase._reportInvalidEOF(ParserMinimalBase.java:641)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._skipColon2(ReaderBasedJsonParser.java:2372)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._skipColon(ReaderBasedJsonParser.java:2287)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextFieldName(ReaderBasedJsonParser.java:942)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer._deserializeContainerNoRecursion(JsonNodeDeserializer.java:536)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer.deserialize(JsonNodeDeserializer.java:100)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer.deserialize(JsonNodeDeserializer.java:25)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at com.fasterxml.jackson.databind.MappingIterator.nextValue(MappingIterator.java:283)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at com.github.fge.jackson.JsonNodeReader.readNode(JsonNodeReader.java:152)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at com.github.fge.jackson.JsonNodeReader.fromReader(JsonNodeReader.java:129)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at com.github.fge.jackson.JsonLoader.fromReader(JsonLoader.java:186)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at com.github.fge.jackson.JsonLoader.fromString(JsonLoader.java:199)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at sapsucker.enigma.SapsuckerBgpLoggingClient$$anonfun$1.apply(SapsuckerBgpLoggingClient.scala:70)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at sapsucker.enigma.SapsuckerBgpLoggingClient$$anonfun$1.apply(SapsuckerBgpLoggingClient.scala:70)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:186)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at scala.collection.TraversableLike$class.map(TraversableLike.scala:234)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at scala.collection.mutable.ArrayOps$ofRef.map(ArrayOps.scala:186)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at sapsucker.enigma.SapsuckerBgpLoggingClient.start(SapsuckerBgpLoggingClient.scala:70)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at sapsucker.enigma.SapsuckerBgpLoggingClient$.main(SapsuckerBgpLoggingClient.scala:22)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]:         at sapsucker.enigma.SapsuckerBgpLoggingClient.main(SapsuckerBgpLoggingClient.scala)
Oct 09 19:39:27 ip-10-0-87-26 wx-control[3759986]: 19:39:27.830 [main] ERROR sapsucker.enigma.SapsuckerBgpLoggingClient - SapsuckerBgpLoggingClient will exit after sleeping for 1000 millis...
import subprocess
import logging
import re
from typing import Dict, Any

class BGPRouter:
    def __init__(self):
        # Initialize logging
        logging.basicConfig(level=logging.DEBUG)
        self.headend_config = type('obj', (object,), {'local_bgp_asn': '65000'})  # Mock config object

    def run_command(self, command):
        command = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, text=True)
        output = command.communicate()[0].strip()
        return output

    def _get_all_bgp_attributes(self) -> Dict[str, Any]:
        """
        Parse BGP route table output and return structured data.
            
        Returns:
            dict: Structured BGP routes data
        """
        routes = []

        try:
            output = self.run_command(f'sudo wrapper_show_ip_bgp')
            
            # Check if command output is valid
            if not output or not output.strip():
                logging.warning("BGP command returned empty output")
                return {"routes": routes}
            
            # Split into lines and find the route entries
            lines = output.strip().split('\n')
            
            # Find the header line to identify where routes start
            route_start_idx = None
            for i, line in enumerate(lines):
                if 'Network' in line and 'Next Hop' in line:
                    route_start_idx = i + 1
                    break
            
            if route_start_idx is None:
                logging.warning("BGP output does not contain expected header format")
                return {"routes": routes}
            
            # Parse each route line
            for line in lines[route_start_idx:]:
                line = line.strip()
                
                # Skip empty lines and summary lines
                if not line or line.startswith('Total number') or line.startswith('IPv6'):
                    continue
                    
                # Skip lines that don't start with route status indicators
                if not line.startswith('*>'):
                    continue
                
                # Parse the route line using regex
                # Pattern matches: *> network next_hop metric [locprf] weight path
                pattern = r'^\*>\s+(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(.+)$'
                match = re.match(pattern, line)
                
                if match:
                    network = match.group(1)
                    next_hop = match.group(2)
                    metric = int(match.group(3))
                    loc_prf = 0  # Set to 0 by default
                    weight = int(match.group(4))
                    path_info = match.group(5).strip()
                    
                    # Extract AS path (remove origin code)
                    path_parts = path_info.split()
                    as_path = []
                    for part in path_parts:
                        if part.isdigit():
                            as_path.append(part)
                    
                    path = ' '.join(as_path) if as_path else ""
                    
                    # Set local preference to 100 if AS path is only "i" (internal route)
                    if path_info.strip() == "i":
                        loc_prf = 100
                        path = str(self.headend_config.local_bgp_asn)
                        
                    route = {
                        "network": network,
                        "nextHopIp": next_hop,
                        "med": metric,
                        "localPref": loc_prf,
                        "weight": weight,
                        "asPath": path
                    }
                    routes.append(route)
            
            return {"routes": routes}
                    
        except Exception as e:
            logging.error(f"Failed to get all BGP prefixes: {e}")
            # Always return a valid dictionary structure even on failure
            return {"routes": routes}

    def _get_specific_network(self, bgp_properties, prefix):
        # Add null check and structure validation
        if not bgp_properties or not isinstance(bgp_properties, dict):
            logging.warning(f"BGP properties is None or invalid type: {type(bgp_properties)}")
            return {}
            
        if "routes" not in bgp_properties:
            logging.warning(f"BGP properties missing 'routes' key: {bgp_properties}")
            return {}
        
        routes = bgp_properties["routes"]
        logging.debug(f"Searching for prefix '{prefix}' in {len(routes)} BGP routes")
        
        # Search through routes for matching prefix
        for route in routes:
            if route.get("network") == prefix:
                logging.debug(f"Found matching route for prefix '{prefix}': {route}")
                return route
        
        # Log available routes for debugging
        available_networks = [route.get("network", "unknown") for route in routes[:5]]  # Show first 5
        return {}

def main():
    router = BGPRouter()
    
    # Get all BGP attributes
    bgp_routes = router._get_all_bgp_attributes()  # Added underscore here
    
    # Search for specific prefix
    specific_prefix = "[IP_ADDRESS]"  # Added the specific IP you requested
    route_info = router._get_specific_network(bgp_routes, specific_prefix)
    
    if route_info:
        print(f"Found route information for {specific_prefix}:")
        print(route_info)
    else:
        print(f"No route information found for {specific_prefix}")

if __name__ == "__main__":
    main()
root@pm3:~# ping -M do -s 1366 172.31.40.218
PING 172.31.40.218 (172.31.40.218) 1366(1394) bytes of data.
1374 bytes from 172.31.40.218: icmp_seq=1 ttl=124 time=64.4 ms
1374 bytes from 172.31.40.218: icmp_seq=2 ttl=124 time=62.8 ms
1374 bytes from 172.31.40.218: icmp_seq=3 ttl=124 time=62.8 ms

--- 172.31.40.218 ping statistics ---
3 packets transmitted, 3 received, +1 errors, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 62.755/63.295/64.358/0.751 ms

------------------------------------------------------------------------------------------

root@pm3:~# ping -M do -s 1370 172.31.40.218
PING 172.31.40.218 (172.31.40.218) 1370(1398) bytes of data.
1378 bytes from 172.31.40.218: icmp_seq=1 ttl=124 time=64.0 ms
1378 bytes from 172.31.40.218: icmp_seq=2 ttl=124 time=62.8 ms
1378 bytes from 172.31.40.218: icmp_seq=3 ttl=124 time=62.7 ms

--- 172.31.40.218 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 62.675/63.150/63.983/0.590 ms

------------------------------------------------------------------------------------------

root@pm3:~# ping -M do -s 1372 172.31.40.218
PING 172.31.40.218 (172.31.40.218) 1372(1400) bytes of data.
1380 bytes from 172.31.40.218: icmp_seq=1 ttl=124 time=63.9 ms
1380 bytes from 172.31.40.218: icmp_seq=2 ttl=124 time=62.7 ms
1380 bytes from 172.31.40.218: icmp_seq=3 ttl=124 time=62.5 ms

--- 172.31.40.218 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 62.501/63.030/63.936/0.643 ms

------------------------------------------------------------------------------------------

root@pm3:~# ping -M do -s 1373 172.31.40.218
PING 172.31.40.218 (172.31.40.218) 1373(1401) bytes of data.
ping: sendmsg: Message too long
ping: sendmsg: Message too long
ping: sendmsg: Message too long

--- 172.31.40.218 ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2076ms

Post Statistics