""" Copyright 2017 Glen Harmon """ import os import ipaddress import logging import importlib import socket import sublime from .html_helper import Html from .selection_utility import SelectionUtility from .variables import ip Iana = importlib.import_module('Network Tech.lib.iana').Iana cache = importlib.import_module('Network Tech.lib.utilities').cache # from network_tech.lib.iana import Iana logger = logging.getLogger('network_tech.search.network.network') iana = Iana(os.path.sep.join(['Network Tech', 'iana.cache'])) def _ipv6_mac(network): """ Get the MAC address from an auto generated IPv6 address """ # Example address used in comments: fe80::a021:27ff:fe00:d8 address = ipaddress.ip_interface(network).ip # Fully written out fe80::a021:27ff:fe00:d8 → fe80:0:0:0:a021:27ff:fe00:d8 # Remove the first 64 bits binary_digits = bin(int(address))[2:].zfill(128) removed_prefix = binary_digits[64:] # We now have a021:27ff:fe00:d8 # Verify it is auto generated, bits 24-40 are 'fffe': if hex(int(removed_prefix[24:40], 2)).lower() != '0xfffe': return None # Flip bit 6 using a mask flipped_bit = '0' if removed_prefix[6] == '1' else '1' bit_flipped = removed_prefix[0:6] + flipped_bit + removed_prefix[7:] # Remove the inserted ff:fe in the middle mac_in_binary = bit_flipped[0:24] + bit_flipped[40:] mac_in_hex = hex(int(mac_in_binary, 2)) mac_in_hex_zero_padded = mac_in_hex[2:].zfill(12) mac_parts = list() for i in range(0, len(mac_in_hex_zero_padded), 4): mac_parts.append( mac_in_hex_zero_padded[i:i + 4] ) return ".".join(mac_parts) class Network: prefix_removals = [ 'host', 'mask', 'range' ] @classmethod def info(cls, network): """ Returns HTML formated information about the network """ content = '' if network.network.num_addresses == 1: content = cls._info_address(network) else: content = cls._info_network(network) return content # elif isinstance(network, (ipaddress.IPv4Address, ipaddress.IPv6Address)): # pass @classmethod def _info_address(cls, ip): content = Html.div('IP: {}'.format(ip.ip)) if ip.is_link_local: content += ''.join([ Html.div('Link Local Address'), ]) link_local_mac = _ipv6_mac(ip) if link_local_mac is not None: content += ''.join([ Html.div('Auto Generated from MAC: {}'.format(link_local_mac)), ]) return content @classmethod def _info_network(cls, network): content = '' neighbors = cls.get_neighbors(network) logger.debug('Neighbors {}'.format(len(neighbors))) before, _, after = cls.get_neighbors(network) network_address = str(network.network.network_address) broadcast_address = str(network.network.broadcast_address) if network_address != broadcast_address: if network.version == 4: content = ''.join([ Html.div('Network: {}'.format(network.network)), Html.div('Broadcast: {}'.format(broadcast_address)), Html.div('# Addresses: {}'.format(network.network.num_addresses)), Html.div('Masks:'), Html.unordered_list(Network.masks(network)), ]) else: content = ''.join([ Html.div('Network: {}/{}'.format(network_address, network.network.prefixlen)), ]) if network.is_link_local: content += ''.join([ Html.div('Link Local Address'), ]) link_local_mac = _ipv6_mac(network) if link_local_mac is not None: content += ''.join([ Html.div('Auto Generated from MAC: {}'.format(link_local_mac)), ]) if before or after: content += Html.div('Neighboring Networks') if after: content += Html.div(' Next: {}'.format(after.network)) if before: content += Html.div(' Previous: {}'.format(before.network)) return content @classmethod def rir(cls, network): rir = iana.get_registrar(network) if rir is not None: content = Html.div('RIR: {}'.format(rir)) else: content = '' return content @classmethod @cache.memory(expire_minutes=5, is_class_method=True) def ptr_lookup(cls, network): ip = str(ipaddress.ip_interface(network).ip) try: primary_hostname, alias_hostnames, other_ips = socket.gethostbyaddr(ip) except socket.herror as e: logger.debug('DNS Reverse Lookup Error {}'.format(e)) return Html.div('DNS: n/a') content = Html.div( 'DNS: {}'.format( socket.getfqdn(primary_hostname) ) ) if alias_hostnames: content += Html.div('DNS Aliases:') for hostname in alias_hostnames: fqdn_hostname = socket.getfqdn(hostname) logger.debug('Alias {} FQDN {}'.format(hostname, fqdn_hostname)) content += Html.div(fqdn_hostname) return content @classmethod def _neighboring_network(cls, interface, after=True): prefix = interface.network.prefixlen network = interface.network try: neighbor = network.broadcast_address + 1 if after else network.network_address - 1 except ipaddress.AddressValueError: return None return ipaddress.ip_interface('{}/{}'.format(neighbor, prefix)) @classmethod def get_neighbors(cls, networks, neighbors=1): if not isinstance(networks, list): networks = [networks] if len(networks) == 0: raise ValueError('No network defined') before = cls._neighboring_network(networks[0], after=False) after = cls._neighboring_network(networks[-1], after=True) networks.insert(0, before) networks.append(after) remaining_neighbors = neighbors - 1 if remaining_neighbors > 0: networks = cls.get_neighbors(networks, neighbors=remaining_neighbors) return networks @classmethod def get_network_on_cursor(cls, region, view): network = None selection_functions = [ lambda view, region: SelectionUtility.word(view, region), lambda view, region: SelectionUtility.left_word(view, region), lambda view, region: SelectionUtility.right_word(view, region), lambda view, region: SelectionUtility.left_word(view, region, repeat=2), lambda view, region: SelectionUtility.right_word(view, region, repeat=2), lambda view, region: SelectionUtility.right_word( view, SelectionUtility.left_word(view, region).begin(), repeat=2 ), ] for index, selection_function in enumerate(selection_functions): selected = selection_function(view, region) network_region = view.substr(selected) current_network = cls.get(network_region) if current_network: logger.debug('Selection function #{} found network {} in text "{}". '.format( index + 1, current_network, network_region, )) if network is None: network = current_network elif current_network.network.prefixlen < network.network.prefixlen: network = current_network return str(network) if network else '' @classmethod def masks(cls, interface): return [ '/' + str(interface.network.prefixlen), str(interface.netmask), str(interface.hostmask), ] @classmethod def contains(cls, group, member): return int(group.network.network_address) <= int(member.network.network_address) and \ int(group.network.broadcast_address) >= int(member.network.broadcast_address) @classmethod def clean(cls, network_text): for remove in cls.prefix_removals: network_text = network_text.replace(remove, '') network_text = network_text.strip() network_text = network_text.replace(' ', ' ') return network_text @classmethod def _get_from_re_match(cls, network_text): network = None match = ip.v4.network.search(network_text) if match: ip_address = match.group('ip') prefix_length = match.group('prefix_length') netmask = match.group('netmask') wildcard = match.group('wildcard') mask = prefix_length or netmask or wildcard try: if mask: network = ipaddress.ip_interface('/'.join([ip_address, mask])) else: network = ipaddress.ip_interface(ip_address) except ValueError: pass logger.debug('Network regexp match: "{}" from {}'.format(network, match.group())) return network match = ip.v4.host.search(network_text) if match: ip_address = match.group('ip') network = ipaddress.ip_interface(ip_address) logger.debug('Host regexp match: "{}" from {}'.format(network, match.group())) return network match = ip.v6.network.search(network_text) if match: network = ipaddress.ip_interface(match.group(0)) logger.debug('Host regexp match: "{}" from {}'.format(network, match.group())) return network match = ip.v6.host.search(network_text) if match: network = ipaddress.ip_interface(match.group(0)) logger.debug('Host regexp match: "{}" from {}'.format(network, match.group())) return network return network @classmethod def get(cls, network_text): network_text = cls.clean(network_text) # network_text = '/'.join(network_text.split()) # logger.debug('bang: {}'.format(network_text)) network = cls._get_from_re_match(network_text) # try: # network = ipaddress.ip_interface(network_text) # except ValueError: # network = None return network @classmethod def clean_region(cls, view, region): text = view.substr(region) for remove in cls.prefix_removals: if text.startswith(remove): cleaned = text.replace(remove, '').strip() removed_characters = len(text) - len(cleaned) return sublime.Region(region.begin() + removed_characters, region.end()) return region @classmethod def clean_regions(cls, view, regions): cleaned = list() for region in regions: cleaned = cls.clean_region(view, region) return cleaned