Skip to main content
Loading...

More Python Posts

class ProposalParser:
    """A class to parse IKE and ESP proposal strings into human-readable formats.

    This class supports parsing of IKE and ESP proposals, extracting encryption, hash, PRF (for IKE),
    and Diffie-Hellman (DH) group information. It also handles the concatenation of these components
    into a structured format, indicating whether Perfect Forward Secrecy (PFS) is enabled for ESP proposals.
    The parser uses predefined mappings for DH groups, encryption algorithms, hash functions, and Pseudo-Random Functions (PRFs).
    It can process a list of proposals and return a formatted string summarizing the cryptographic parameters.

    Attributes:
        dh_mapping (dict): A mapping of Diffie-Hellman groups to their corresponding identifiers
        enc_mapping (dict): A mapping of encryption algorithms to their corresponding identifiers
        hash_mapping (dict): A mapping of hash functions to their corresponding identifiers
        prf_mapping (dict): A mapping of Pseudo-Random Functions to their corresponding identifiers
    """

    def __init__(self):
        """Initialize the parser with mappings for DH groups, encryption, hash, and PRF."""
        self.dh_mapping = {
            'MODP_768': '1',  # RFC 2409: 768-bit MODP group (Group 1), considered weak by modern standards
            'MODP_1024': '2',  # RFC 2409: 1024-bit MODP group (Group 2), considered weak today
            'MODP_1536': '5',  # RFC 3526: 1536-bit MODP group (Group 5), stronger than Groups 1 and 2
            'MODP_2048': '14',  # RFC 3526: 2048-bit MODP group (Group 14), commonly used for modern IKE
            'MODP_3072': '15',  # RFC 3526: 3072-bit MODP group (Group 15), high security
            'MODP_4096': '16',  # RFC 3526: 4096-bit MODP group (Group 16), suitable for high-security applications
            'MODP_6144': '17',  # RFC 3526: 6144-bit MODP group (Group 17), very high security, less common
            'MODP_8192': '18',  # RFC 3526: 8192-bit MODP group (Group 18), highest MODP group, rarely used
            'ECP_256': '19',  # RFC 5903: 256-bit ECP group (NIST P-256, Group 19), efficient elliptic curve
            'ECP_384': '20',  # RFC 5903: 384-bit ECP group (NIST P-384, Group 20), stronger elliptic curve
            'ECP_521': '21',  # RFC 5903: 521-bit ECP group (NIST P-521, Group 21), high-security elliptic curve
            'ECP_192': '25',  # RFC 5903: 192-bit ECP group (NIST P-192, Group 25), weaker elliptic curve
            'ECP_224': '26',  # RFC 5903: 224-bit ECP group (NIST P-224, Group 26), intermediate security
            'MODP_1024_160': '22',  # RFC 5114: 1024-bit MODP with 160-bit subgroup (Group 22), less common
            'MODP_2048_224': '23',  # RFC 5114: 2048-bit MODP with 224-bit subgroup (Group 23), enhanced security
            'MODP_2048_256': '24',  # RFC 5114: 2048-bit MODP with 256-bit subgroup (Group 24), enhanced security
            'FFDHE_2048': '256',  # RFC 7919: 2048-bit FFDHE group, secure Diffie-Hellman parameters
            'FFDHE_3072': '257',  # RFC 7919: 3072-bit FFDHE group, secure Diffie-Hellman parameters
            'FFDHE_4096': '258',  # RFC 7919: 4096-bit FFDHE group, secure Diffie-Hellman parameters
            'FFDHE_6144': '259',  # RFC 7919: 6144-bit FFDHE group, secure Diffie-Hellman parameters
            'FFDHE_8192': '260',  # RFC 7919: 8192-bit FFDHE group, secure Diffie-Hellman parameters
            'ECP_224_BP': '27',  # RFC 6460: 224-bit Brainpool ECP group, alternative elliptic curve
            'ECP_256_BP': '28',  # RFC 6460: 256-bit Brainpool ECP group, alternative elliptic curve
            'ECP_384_BP': '29',  # RFC 6460: 384-bit Brainpool ECP group, alternative elliptic curve
            'ECP_512_BP': '30',  # RFC 6460: 512-bit Brainpool ECP group, alternative elliptic curve
            'CURVE_25519': '31',  # RFC 8031: Curve25519, modern elliptic curve for high-speed cryptography
            'CURVE_448': '32',  # RFC 8031: Curve448, modern elliptic curve for high-security cryptography
        }

        self.enc_mapping = {
            'AES_CBC_128': 'AES128',  # RFC 3602: AES-CBC with 128-bit key, standard encryption for IPsec
            'AES_CBC_192': 'AES192',  # RFC 3602: AES-CBC with 192-bit key, less common but supported
            'AES_CBC_256': 'AES256',  # RFC 3602: AES-CBC with 256-bit key, widely used for high security
            'AES_GCM_16_128': 'AES128-GCM-16',  # RFC 4106: AES-GCM with 128-bit key, 16-byte ICV
            'AES_GCM_16_192': 'AES192-GCM-16',  # RFC 4106: AES-GCM with 192-bit key, 16-byte ICV
            'AES_GCM_16_256': 'AES256-GCM-16',  # RFC 4106: AES-GCM with 256-bit key, 16-byte ICV
            'AES_GCM_8_128': 'AES128-GCM-8',  # RFC 4106: AES-GCM with 128-bit key, 8-byte ICV
            'AES_GCM_8_256': 'AES256-GCM-8',  # RFC 4106: AES-GCM with 256-bit key, 8-byte ICV
            'AES_GCM_12_128': 'AES128-GCM-12',  # RFC 4106: AES-GCM with 128-bit key, 12-byte ICV
            'AES_GCM_12_256': 'AES256-GCM-12',  # RFC 4106: AES-GCM with 256-bit key, 12-byte ICV
            'AES_CCM_16_128': 'AES128-CCM-16',  # RFC 4309: AES-CCM with 128-bit key, 16-byte ICV
            'AES_CCM_16_256': 'AES256-CCM-16',  # RFC 4309: AES-CCM with 256-bit key, 16-byte ICV
            'AES_CTR_128': 'AES128-CTR',  # RFC 3686: AES-CTR with 128-bit key, stream cipher mode
            'AES_CTR_192': 'AES192-CTR',  # RFC 3686: AES-CTR with 192-bit key, stream cipher mode
            'AES_CTR_256': 'AES256-CTR',  # RFC 3686: AES-CTR with 256-bit key, stream cipher mode
            '3DES_CBC': '3DES',  # RFC 2451: Triple DES in CBC mode, legacy encryption
            'DES_CBC': 'DES',  # RFC 2405: DES in CBC mode, considered insecure today
            'CAMELLIA_CBC_128': 'CAMELLIA128',  # RFC 5529: Camellia-CBC with 128-bit key
            'CAMELLIA_CBC_256': 'CAMELLIA256',  # RFC 5529: Camellia-CBC with 256-bit key
            'CHACHA20_POLY1305': 'CHACHA20-POLY1305',  # RFC 7634: ChaCha20 with Poly1305, modern AEAD cipher
            'BLOWFISH_CBC': 'BLOWFISH',  # RFC 2451: Blowfish in CBC mode, legacy and less secure
            'CAST5_CBC': 'CAST5',  # RFC 2144: CAST-128 in CBC mode, legacy encryption
        }

        self.hash_mapping = {
            'HMAC_MD5': 'MD5',  # RFC 2403: HMAC-MD5, considered weak by modern standards
            'HMAC_MD5_96': 'MD5',  # RFC 2403: HMAC-MD5 with 96-bit truncation, weak
            'HMAC_SHA1': 'SHA1',  # RFC 2404: HMAC-SHA1, widely used but aging
            'HMAC_SHA1_96': 'SHA1',  # RFC 2404: HMAC-SHA1 with 96-bit truncation
            'HMAC_SHA2_256': 'SHA2-256',  # RFC 4868: HMAC-SHA256, strong hash function
            'HMAC_SHA2_256_128': 'SHA2-256',  # RFC 4868: HMAC-SHA256 with 128-bit truncation
            'HMAC_SHA2_384': 'SHA2-384',  # RFC 4868: HMAC-SHA384, stronger hash function
            'HMAC_SHA2_384_192': 'SHA2-384',  # RFC 4868: HMAC-SHA384 with 192-bit truncation
            'HMAC_SHA2_512': 'SHA2-512',  # RFC 4868: HMAC-SHA512, very strong hash function
            'HMAC_SHA2_512_256': 'SHA2-512',  # RFC 4868: HMAC-SHA512 with 256-bit truncation
            'HMAC_SHA3_224': 'SHA3-224',  # RFC 8446 (TLS context): SHA3-224, modern hash function, experimental for the most part
            'HMAC_SHA3_256': 'SHA3-256',  # RFC 8446 (TLS context): SHA3-256, modern hash function, experimental for the most part
            'HMAC_SHA3_384': 'SHA3-384',  # RFC 8446 (TLS context): SHA3-384, modern hash function, experimental for the most part
            'HMAC_SHA3_512': 'SHA3-512',  # RFC 8446 (TLS context): SHA3-512, modern hash function, experimental for the most part
            'AES_GMAC_128': 'GMAC-128',  # RFC 4543: AES-GMAC with 128-bit key, for authentication
            'AES_GMAC_192': 'GMAC-192',  # RFC 4543: AES-GMAC with 192-bit key, for authentication
            'AES_GMAC_256': 'GMAC-256',  # RFC 4543: AES-GMAC with 256-bit key, for authentication
            'POLY1305': 'POLY1305',  # RFC 7539: Poly1305, used with ChaCha20 for authentication
        }

        self.prf_mapping = {
            'PRF_HMAC_MD5': 'MD5',  # RFC 2403 (via IKEv1): HMAC-MD5 PRF, weak by modern standards
            'PRF_HMAC_SHA1': 'SHA1',  # RFC 2404 (via IKEv1): HMAC-SHA1 PRF, widely used but aging
            'PRF_HMAC_SHA2_256': 'SHA2-256',  # RFC 4868: HMAC-SHA256 PRF, strong and common
            'PRF_HMAC_SHA2_384': 'SHA2-384',  # RFC 4868: HMAC-SHA384 PRF, stronger PRF
            'PRF_HMAC_SHA2_512': 'SHA2-512',  # RFC 4868: HMAC-SHA512 PRF, very strong PRF
            'PRF_AES128_CMAC': 'AES128-CMAC',  # RFC 4494: AES-CMAC with 128-bit key, secure PRF
            'PRF_AES128_XCBC': 'AES128-XCBC',  # RFC 4434: AES-XCBC PRF, alternative to CMAC
            'PRF_HMAC_SHA3_224': 'SHA3-224',  # RFC 8446 (TLS context): SHA3-224 PRF, modern algorithm, experimental for the most part
            'PRF_HMAC_SHA3_256': 'SHA3-256',  # RFC 8446 (TLS context): SHA3-256 PRF, modern algorithm, experimental for the most part
            'PRF_HMAC_SHA3_384': 'SHA3-384',  # RFC 8446 (TLS context): SHA3-384 PRF, modern algorithm, experimental for the most part
            'PRF_HMAC_SHA3_512': 'SHA3-512',  # RFC 8446 (TLS context): SHA3-512 PRF, modern algorithm, experimental for the most part
        }

    def _categorize_component(self, component, is_ike):
        """Categorize a proposal component into encryption, hash, PRF, or DH group."""
        enc_keywords = ['AES_CBC', 'AES_GCM', 'AES_CTR', 'CHACHA20', 'BLOWFISH', 'CAST5', 'DES', '3DES', 'CAMELLIA']
        hash_keywords = ['HMAC_SHA', 'HMAC_MD5', 'POLY1305', 'AES_GMAC']
        dh_keywords = ['MODP_', 'ECP_', 'FFDHE_', 'CURVE_']

        if (component in self.enc_mapping or any(s in component for s in enc_keywords)):
            return 'encryption', component
        elif is_ike and 'PRF_' in component:
            return 'prf', component
        elif (component in self.hash_mapping or any(s in component for s in hash_keywords)) and not component.startswith("PRF_"):
            return 'hash', component
        elif (component in self.dh_mapping or any(s in component for s in dh_keywords)):
            return 'dh_group', component
        elif component == 'NO_EXT_SEQ':
            return 'skip', component
        else:
            return 'unknown', component

    def _map_encryption(self, enc_components, result):
        """Map encryption components to their corresponding identifiers."""
        for enc in enc_components:
            mapped_enc = self.enc_mapping.get(enc, 'Unknown')
            if mapped_enc != 'Unknown' and mapped_enc not in result['encryption']:
                result['encryption'].append(mapped_enc)

    def _map_hash_and_prf(self, enc_components, hash_components, prf_components, result):
        """Map hash and PRF components, handling AEAD ciphers."""
        # Map hash components (skip for AEAD ciphers like AES-GCM, AES-CCM, CHACHA20-POLY1305)
        if not any(enc.startswith('AES_GCM') or enc.startswith('AES_CCM') or enc == 'CHACHA20_POLY1305' for enc in enc_components):
            for hash_val in hash_components:
                mapped_hash = self.hash_mapping.get(hash_val, 'Unknown')
                if mapped_hash not in result['hash']:
                    result['hash'].append(mapped_hash)
        else:
            result['hash'].append('None')

        # Map PRF components
        for prf in prf_components:
            mapped_prf = self.prf_mapping.get(prf, 'Unknown')
            if mapped_prf not in result['prf']:
                result['prf'].append(mapped_prf)

    def _map_dh_group(self, dh_components, result):
        """Map Diffie-Hellman group components to their corresponding identifiers."""
        for dh in dh_components:
            mapped_dh = self.dh_mapping.get(dh)
            if mapped_dh != 'None' and mapped_dh not in result['dh_group']:
                result['dh_group'].append(mapped_dh)

    def parse_ike_proposal(self, proposal):
        """
        Parse an IKE or ESP proposal string into a structured format.

        Args:
            proposal (str): The proposal string, e.g., "IKE:AES_CBC_256/HMAC_SHA2_256/PRF_HMAC_SHA2_256/MODP_2048"

        Returns:
            dict: A dictionary with keys 'encryption', 'hash', 'prf', and 'dh_group'
        """
        # Split the proposal into components based on '/'
        components = proposal.split('/')

        result = {
            'encryption': [],
            'hash': [],
            'prf': [],
            'dh_group': []
        }

        is_ike = proposal.startswith('IKE:')
        is_esp = proposal.startswith('ESP:')

        # Remove IKE or ESP prefix if present for easier parsing
        if is_ike or is_esp:
            components[0] = components[0].replace('IKE:', '').replace('ESP:', '')

        enc_components = []
        hash_components = []
        prf_components = []
        dh_components = []
        unknown_components = []

        # Categorize components
        for component in components:
            category, value = self._categorize_component(component, is_ike)
            if category == 'encryption':
                enc_components.append(value)
            elif category == 'hash':
                hash_components.append(value)
            elif category == 'prf':
                prf_components.append(value)
            elif category == 'dh_group':
                dh_components.append(value)
            elif category == 'unknown':
                unknown_components.append(value)

        # Map components to their identifiers
        self._map_encryption(enc_components, result)
        self._map_hash_and_prf(enc_components, hash_components, prf_components, result)
        self._map_dh_group(dh_components, result)

        # Handle ESP case (no PRF for ESP proposals)
        if is_esp:
            result['prf'] = ['None']

        # Set defaults if no valid components found
        if not result['encryption']:
            result['encryption'] = ['Unknown']
        if not result['hash']:
            result['hash'] = ['None']
        if not result['prf']:
            result['prf'] = ['None']
        if not result['dh_group']:
            result['dh_group'] = ['None']

        return result

    def collect_components(self, proposals):
        """Collect unique encryption, hash, PRF, and DH group components from a list of proposals."""
        enc_set = set()
        hash_set = set()
        prf_set = set()
        dh_set = set()
        all_aead = True

        for proposal in proposals:
            parsed = self.parse_ike_proposal(proposal.strip())
            enc_set.update(parsed['encryption'])
            
            # Check if the proposal uses a non-AEAD cipher
            if not any('GCM' in enc or 'CCM' in enc or 'CHACHA20' in enc for enc in parsed['encryption']):
                hash_set.update(parsed['hash'])
                all_aead = False
            prf_set.update(parsed['prf'])
            if parsed['dh_group'] != ['None']:
                dh_set.update(parsed['dh_group'])

        # If all proposals are AEAD, set hash to "None"
        if all_aead and not hash_set:
            hash_set.add('None')

        return enc_set, hash_set, prf_set, dh_set

    def format_output(self, enc_set, hash_set, prf_set, dh_set, is_ike):
        """Format the collected components into a human-readable string."""
        # Convert sets to sorted lists
        enc_list = sorted(list(enc_set))
        hash_list = sorted(list(hash_set))
        prf_list = sorted(list(prf_set))
        dh_list = sorted(list(dh_set), key=lambda x: int(x))

        # Determine PFS status for ESP proposals only
        pfs_status = "PFS: Enabled" if dh_set and not is_ike else "PFS: None"

        # Format output as a single concatenated string
        enc_part = f"Encryption: {', '.join(enc_list)}" if enc_list else "Encryption: None"
        hash_part = f"Hash: {', '.join(hash_list)}" if hash_list else "Hash: None"
        dh_part = f"DH Group(s): {', '.join(dh_list)}" if dh_list else "DH Group(s): None"
        prf_part = f"PRF: {', '.join(prf_list)}" if prf_list else "PRF: None"

        # Return formatted string based on whether it's an IKE or ESP proposal
        if is_ike:
            return f"{enc_part} {hash_part} {prf_part} {dh_part}"
        else:
            return f"{enc_part} {hash_part} {dh_part} {pfs_status}"

    def process_proposals(self, proposal_list):
        """
        Process a list of IKE or ESP proposals, concatenating encryption, hash, PRF (for IKE only),
        and DH group values, and indicate whether PFS is enabled for ESP proposals only.

        Args:
            proposal_list (str): Comma-separated string of IKE or ESP proposals

        Returns:
            str: Formatted string with concatenated encryption, hash, PRF (for IKE), DH groups, and PFS status (for ESP)
        """
        if proposal_list is None:
            return "No proposals provided"

        if not proposal_list:
            return "No proposals provided"

        if not (proposal_list.startswith('IKE:') or proposal_list.startswith('ESP:')):
            return "Invalid proposal format. Proposals must be of type 'IKE' for Phase 1 or 'ESP' for Phase 2"

        proposal_list = proposal_list.replace(',', ', ')
        proposals = proposal_list.strip().split(', ')
        is_ike = any(proposal.startswith('IKE:') for proposal in proposals)

        enc_set, hash_set, prf_set, dh_set = self.collect_components(proposals)
        return self.format_output(enc_set, hash_set, prf_set, dh_set, is_ike)
# Example usage
if __name__ == "__main__":
    parser = ProposalParser()

    # unknown_hash = "IKE:AES_CBC_256/HMAC_SHA22222/PRF_HMAC_SHA2_256/MODP_2048"
    # none_hash = "IKE:AES_CBC_256/PRF_HMAC_SHA2_256/MODP_2048"

    # # Outputs: Encryption: AES256 Hash: None PRF: SHA2-256 DH Group(s): 14
    # print(parser.process_proposals(unknown_hash))

    # # Outputs: Encryption: AES256 Hash: None PRF: SHA2-256 DH Group(s): 14
    # print(parser.process_proposals(none_hash))

    proposal = "IKE:AES_GCM_16_256/MODP_2048/NO_EXT_SEQ"

    print(parser.process_proposals(proposal))
import subprocess
import json
import re


def parse_bgp_routes(bgp_output):
    """
    Parse BGP route table output and return structured data.
    
    Args:
        bgp_output (str): Raw BGP table output
        
    Returns:
        dict: Structured BGP routes data
    """
    routes = []
    
    # Split into lines and find the route entries
    lines = bgp_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:
        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
        # Note: LocPrf column may be empty, so we'll set it to 0 for all records
        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 for all records as requested
            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 ""
            
            route = {
                "network": network,
                "nextHop": next_hop,
                "metric": metric,
                "locPrf": loc_prf,
                "weight": weight,
                "path": path
            }
            routes.append(route)
    
    return {"routes": routes}


def get_bgp_routes_json():
    """
    Get BGP routes from sample data and return as JSON.
    
    Returns:
        str: JSON string of BGP routes
    """
    output_text = """IPv4
BGP table version is 0, local router ID is 169.254.0.185
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
*> 10.0.0.0         169.254.0.186          100             0 65100 ?
*> 10.2.0.0/16      169.254.0.185          100         32768 i
*> 10.5.0.0/16      169.254.0.185          100         32768 i
*> 10.6.0.0/16      169.254.0.185          100         32768 i
*> 10.10.0.0/16     169.254.0.185          100         32768 i
*> 10.42.0.0/16     169.254.0.185          100         32768 i
*> 10.56.0.0/16     169.254.0.185          100         32768 i
*> 10.133.0.0/21    169.254.0.185          100         32768 i
*> 10.192.2.0/23    169.254.0.186          100             0 65100 ?
*> 10.192.6.0/23    169.254.0.186          100             0 65100 ?
*> 10.208.0.0/18    169.254.0.185          100         32768 i
*> 10.210.0.0/20    169.254.0.186          100             0 65100 ?
*> 10.210.64.0/18   169.254.0.186          100             0 65100 ?
*> 10.210.128.0/20  169.254.0.186          100             0 65100 ?
*> 10.211.0.0/17    169.254.0.186          100             0 65100 ?
*> 10.211.128.0/17  169.254.0.186          100             0 65100 ?
*> 10.216.0.0/16    169.254.0.186          100             0 65100 ?
*> 10.224.64.0/19   169.254.0.185          100         32768 i
*> 10.227.128.0/18  169.254.0.185          100         32768 i
*> 10.227.192.0/18  169.254.0.185          100         32768 i
*> 10.228.64.0/18   169.254.0.185          100         32768 i
*> 10.228.128.0/18  169.254.0.185          100         32768 i
*> 10.228.192.0/18  169.254.0.185          100         32768 i
*> 10.229.64.0/18   169.254.0.185          100         32768 i
*> 10.229.128.0/18  169.254.0.185          100         32768 i
*> 10.229.192.0/18  169.254.0.185          100         32768 i
*> 10.230.0.0/18    169.254.0.185          100         32768 i
*> 10.230.64.0/18   169.254.0.185          100         32768 i
*> 10.230.128.0/18  169.254.0.185          100         32768 i
*> 10.230.192.0/18  169.254.0.185          100         32768 i
*> 10.246.48.0/20   169.254.0.185          100         32768 i
*> 10.254.192.0/18  169.254.0.185          100         32768 i
*> 10.255.0.0/18    169.254.0.185          100         32768 i
*> 10.255.64.0/18   169.254.0.185          100         32768 i
*> 10.255.128.0/18  169.254.0.185          100         32768 i
*> 10.255.192.0/19  169.254.0.185          100         32768 i
*> 10.255.224.0/19  169.254.0.185          100         32768 i
*> 100.64.0.64/27   169.254.0.185          100         32768 i
*> 100.64.0.96/27   169.254.0.185          100         32768 i
*> 100.64.0.128/27  169.254.0.185          100         32768 i
*> 100.64.0.160/27  169.254.0.185          100         32768 i
*> 172.16.0.0/12    169.254.0.186          100             0 65100 69999 21222 ?
*> 240.0.0.0/4      169.254.0.186          100             0 65100 69999 21222 ?
*> 253.128.0.0/14   169.254.0.186          100             0 65100 ?
*> 253.132.0.0/20   169.254.0.186          100             0 65100 ?

Total number of prefixes 45

IPv6
No BGP network exists"""
    
    # Parse the BGP output and return as JSON
    parsed_data = parse_bgp_routes(output_text)
    return json.dumps(parsed_data, indent=2)

def get_specific_network(prefix):
    # Get all BGP routes first
    all_routes = json.loads(get_bgp_routes_json())
    
    # Search through routes for matching prefix
    for route in all_routes["routes"]:
        if route["network"] == prefix:
            return route
            
    # Return None if no matching prefix is found
    return None

def main():
    """Main function to demonstrate the BGP parser."""
    print(get_bgp_routes_json())
    print(get_specific_network("10.42.0.0/16"))


if __name__ == "__main__":
    main()