def _get_dhcp_leases(self): if not self._dhcp_leases: lease_d = get_dhclient_d() if lease_d: lease_files = [ file for file in os.listdir(lease_d) if file.endswith('.leases') or file.endswith('.lease') ] for lf in [os.path.join(lease_d, f) for f in lease_files]: with open(lf, 'r') as lease_f: lease_data = lease_f.read() self._dhcp_leases.extend( parse_dhclient_leases_file(lease_data)) netif_leases_d = '/run/systemd/netif/leases/' netif = [file for file in os.listdir(netif_leases_d)] for ifindex in netif: if_file = os.path.join(netif_leases_d, ifindex) netif_lease = None with open(if_file, 'r') as lease_f: netif_lease = parse_networkd_lease_file(lease_f.read()) if netif_lease: netif_lease["interface"] = socket.if_indextoname( int(ifindex)) self._dhcp_leases.append(netif_lease) # Use network-manager snap lease location network_manager_leases_d = '/run/NetworkManager/dhcp/' if os.path.exists(network_manager_leases_d): leases = [ file for file in os.listdir(network_manager_leases_d) ] for index in leases: lease_file = os.path.join(network_manager_leases_d, index) nm_lease = None with open(lease_file, 'r') as lease_f: # network-manager lease files use the same format as # neworkd nm_lease = parse_networkd_lease_file(lease_f.read()) if nm_lease: nm_lease["interface"] = socket.if_indextoname( int(ifindex)) self._dhcp_leases.append(nm_lease) return self._dhcp_leases
def _repr_pktinfo(self): """What repr(self.pktinfo) would be if that were not a plain untyped bytestring""" addr, interface = _in6_pktinfo.unpack_from(self.pktinfo) if interface == 0: interface = "" else: try: interface = "%" + socket.if_indextoname(interface) except Exception as e: interface = "%%%d(%s)" % (interface, e) return "%s%s" % (self._strip_v4mapped(addr), interface)
def from_probe_data(cls, netlink_data, udev_data): # This is a bit of a hack, but sometimes the interface has # already been renamed by udev by the time we get here, so we # can't use netlink_data['name'] to go poking about in # /sys/class/net. name = socket.if_indextoname(netlink_data['ifindex']) return cls(addresses={}, type=_compute_type(name, netlink_data['arptype']), udev_data=udev_data, netlink_data=netlink_data, bond=_get_bonding(name, netlink_data['flags']), bridge=_get_bridging(name))
def _plainaddress(self): """Return the IP adress part of the sockaddr in IPv4 notation if it is mapped, otherwise the plain v6 address including the interface identifier if set.""" if self.sockaddr[3] != 0: scopepart = "%" + socket.if_indextoname(self.sockaddr[3]) else: scopepart = "" if '%' in self.sockaddr[0]: # Fix for Python 3.6 and earlier that reported the scope information # in the IP literal (3.7 consistently expresses it in the tuple slot 3) scopepart = "" return self._strip_v4mapped(self.sockaddr[0]) + scopepart
def __init__(self, net_info, configuration): self._net_info = net_info self._configuration = configuration if self.type == 'vlan': self._configuration['id'] = net_info.netlink_data['vlan_id'] self._configuration['link'] = if_indextoname( net_info.netlink_data['vlan_link']) if self.type == 'bond': bond = self._net_info.bond self._configuration['interfaces'] = bond['slaves'] params = {'mode': bond['mode']} if bond['mode'] in ['balance-xor', '802.3ad', 'balance-tlb']: params['transmit-hash-policy'] = bond['xmit_hash_policy'] if bond['mode'] == '802.3ad': params['lacp-rate'] = bond['lacp_rate'] self._configuration['parameters'] = params
def get_unicast_info(ip_dst): """ Obtain unicast info regarding IP ip_dst, such as RPC, if it is directly connected and root interface index """ metric_administrative_distance = 0xFFFFFFFF metric_cost = 0xFFFFFFFF is_directly_connected = False oif = None with UnicastRouting.lock: unicast_route = UnicastRouting.get_route(ip_dst) if unicast_route is not None: oif = unicast_route.get("oif") next_hop = unicast_route["gateway"] multipaths = unicast_route["multipath"] #prefsrc = unicast_route.get("prefsrc") #rpf_node = ip_dst if (next_hop is None and prefsrc is not None) else next_hop rpf_node = next_hop if next_hop is not None else ip_dst if ipaddress.ip_address(ip_dst).version == 4: highest_ip = ipaddress.ip_address("0.0.0.0") else: highest_ip = ipaddress.ip_address("::") for m in multipaths: if m.get("gateway", None) is None: oif = m.get('oif') rpf_node = ip_dst break elif ipaddress.ip_address(m["gateway"]) > highest_ip: highest_ip = ipaddress.ip_address(m["gateway"]) oif = m.get('oif') rpf_node = m["gateway"] metric_administrative_distance = unicast_route["proto"] metric_cost = unicast_route["priority"] metric_cost = metric_cost if metric_cost is not None else 0 is_directly_connected = rpf_node == ip_dst interface_name = None if oif is None else if_indextoname(int(oif)) from hpimdm import Main if ipaddress.ip_address(ip_dst).version == 4: rpf_if = Main.kernel.vif_name_to_index_dic.get(interface_name) else: rpf_if = Main.kernel_v6.vif_name_to_index_dic.get(interface_name) return (metric_administrative_distance, metric_cost, is_directly_connected, rpf_if)
def get_unicast_info(ip_dst): metric_administrative_distance = 0xFFFFFFFF metric_cost = 0xFFFFFFFF rpf_node = ip_dst oif = None mask = 0 with UnicastRouting.lock: unicast_route = UnicastRouting.get_route(ip_dst) if unicast_route is not None: oif = unicast_route.get("oif") next_hop = unicast_route["gateway"] multipaths = unicast_route["multipath"] #prefsrc = unicast_route.get("prefsrc") #rpf_node = ip_dst if (next_hop is None and prefsrc is not None) else next_hop rpf_node = next_hop if next_hop is not None else ip_dst if ipaddress.ip_address(ip_dst).version == 4: highest_ip = ipaddress.ip_address("0.0.0.0") else: highest_ip = ipaddress.ip_address("::") for m in multipaths: if m.get("gateway", None) is None: oif = m.get('oif') rpf_node = ip_dst break elif ipaddress.ip_address(m["gateway"]) > highest_ip: highest_ip = ipaddress.ip_address(m["gateway"]) oif = m.get('oif') rpf_node = m["gateway"] metric_administrative_distance = unicast_route["proto"] metric_cost = unicast_route["priority"] metric_cost = metric_cost if metric_cost is not None else 0 mask = unicast_route["dst_len"] interface_name = None if oif is None else if_indextoname(int(oif)) from pimdm import Main if ipaddress.ip_address(ip_dst).version == 4: rpf_if = Main.kernel.vif_name_to_index_dic.get(interface_name) else: rpf_if = Main.kernel_v6.vif_name_to_index_dic.get(interface_name) return (metric_administrative_distance, metric_cost, rpf_node, rpf_if, mask)
def recvmsg ( self, timeout = 0 ): ( r, w, x ) = select.select ( [self.__sock], [], [], timeout ) if not r: return None (packet, ancdata, msg_flags, address) = self.__sock.recvmsg ( 65536, 1024 ) # packet = NDP packet # address = (host, port, flowinfo, scopeid) src = address[0].split("%")[0] dest = None iface_id = address[3] # ancdata = (cmsg_level, cmsg_type, cmsg_data) # cmsg_data = struct in6_pktinfo{ struct in6_addr ipi6_addr; int ipi6_ifindex;} for (cmsg_level, cmsg_type, cmsg_data) in ancdata: if cmsg_level == socket.IPPROTO_IPV6 and cmsg_type == socket.IPV6_PKTINFO and cmsg_data: dest = socket.inet_ntop ( socket.AF_INET6, cmsg_data[:-4] ) iface_id = int.from_bytes(cmsg_data[-4:], byteorder=sys.byteorder) break #LOG.debug("NDP packet received through interface " + str(iface_id)) iface = socket.if_indextoname(iface_id) return NdpMsg.from_packet ( packet, src, dest, iface )
def netif(self): """Textual interface identifier of the explicitly configured remote interface, or the interface identifier reported in an incoming link-local message. None if not set.""" index = self.sockaddr[3] return socket.if_indextoname(index) if index else None
def handle(self): """ request handling happens here """ # empty dummy handler self.response = '' # raw address+interface, used for requests monitoring client_address = deepcopy(self.client_address[0].split('%')[0]) try: interface = socket.if_indextoname(self.client_address[3]) except OSError: # the interface index is 0 if sent to localhost - # even if 'lo' has the index 1 interface = '' # avoid processing requests of unknown clients which cannot be found in the neighbor cache table # only makes sense if classes are not ignored and thus the neighbor cache is used if not cfg.IGNORE_MAC and cfg.IGNORE_UNKNOWN_CLIENTS: if client_address in requests_blacklist: return False # check if we are limiting requests if cfg.REQUEST_LIMIT: if cfg.REQUEST_LIMIT_IDENTIFICATION == 'llip': # avoid further processing if client is known to be bad if client_address in requests_blacklist: return False # add client to requests tracker if not known, otherwise raise counter if client_address not in requests: requests[client_address] = Request(client_address) else: requests[client_address].count += 1 # otherwise a MAC address else: llip = decompress_ip6(client_address) if llip in collected_macs: mac = deepcopy(collected_macs[llip].mac) if mac in requests_blacklist: return False # add client to requests tracker if not known, otherwise raise counter if mac not in requests: requests[mac] = Request(mac) else: requests[mac].count += 1 del llip try: # convert raw message into ascii-bytes raw_bytes = binascii.hexlify(self.request[0]).decode() # local connection is a control message # for BSD there might be different localhost addresses if client_address == LOCALHOST and interface in LOCALHOST_INTERFACES: self.is_control_message = True # do nothing if interface is not configured if interface not in cfg.INTERFACE and not self.is_control_message: return False # bad or too short message is thrown away if not len(raw_bytes) > 8: pass elif self.is_control_message: self.control_message(raw_bytes) else: message_type = int(raw_bytes[0:2], 16) transaction_id = raw_bytes[2:8] raw_bytes_options = raw_bytes[8:] options = {} while len(raw_bytes_options) > 0: # option type and length are 2 bytes each option = int(raw_bytes_options[0:4], 16) length = int(raw_bytes_options[4:8], 16) # *2 because 2 bytes make 1 char value = raw_bytes_options[8:8 + length * 2] # Microsoft behaves a little bit different than the other # clients - in RENEW and REBIND request multiple addresses of an # IAID are not requested all in one option type 3 but # come in several options of type 3 what leads to some confusion # so allow all IA_OPTION (NA, TA, PD) to exist more than once # and create them as list if option not in IA_OPTIONS: options[option] = value else: if option in options: # if options list already exists append value options[option].append(value) else: # otherwise create list and put value in options[option] = [value] # cut off bytes worked on raw_bytes_options = raw_bytes_options[8 + length * 2:] # only valid messages will be processed if message_type in CONST.MESSAGE_DICT: client_llip = decompress_ip6(client_address) # 2. create Transaction object if not yet done identifier = client_llip + transaction_id if identifier not in transactions: transactions[identifier] = Transaction( transaction_id, client_llip, interface, message_type, options) # shortcut to transactions[identifier] transaction = transactions[identifier] # add client MAC address to transaction object if transaction.client_llip in collected_macs and not cfg.IGNORE_MAC: transaction.mac = collected_macs[ transaction.client_llip].mac else: # shortcut to transactions[identifier] transaction = transactions[identifier] transaction.timestamp = timer.time transaction.last_message_received_type = message_type # log incoming messages log.info( f'{CONST.MESSAGE_DICT[message_type]} | ' f'transaction: {transaction.id}{transaction.get_options_string()}' ) # 3. answer requests # check if client sent a valid DUID (alphanumeric) if transaction.duid.isalnum(): # if request was not addressed to multicast do nothing but logging if transaction.interface == '': log.info( f'transaction: {transaction.id} | Multicast necessary ' f'but message came from {colonify_ip6(transaction.client_llip)}' ) # reset transaction counter transaction.counter = 0 else: # client will get answer if its LLIP & MAC is known if transaction.client_llip not in collected_macs: if not cfg.IGNORE_MAC: # complete MAC collection - will make most sense on Linux # and its native neighborcache access collect_macs(timer.time) # when still no trace of the client in neighbor cache then send silly signal back if transaction.client_llip not in collected_macs: # if not known send status code option failure to get # LLIP/MAC mapping from neighbor cache # status code 'Success' sounds silly but works best self.build_response( CONST.MESSAGE.REPLY, transaction, [CONST.OPTION.STATUS_CODE], status=CONST.STATUS.SUCCESS) # complete MAC collection collect_macs(timer.time) # if client cannot be found in collected MACs if transaction.client_llip not in collected_macs: if cfg.IGNORE_UNKNOWN_CLIENTS and client_address in requests: if requests[ client_address].count > 1: requests_blacklist[ client_address] = Request( client_address) log.info( f"Blacklisting unknown client {client_address}" ) return False # try to add client MAC address to transaction object try: transaction.mac = collected_macs[ transaction.client_llip].mac except KeyError: # MAC not yet found :-( if cfg.LOG_MAC_LLIP: log.info( f'transaction: {transaction.id} | mac address for ' f'llip {colonify_ip6(transaction.client_llip)} unknown' ) # if finally there is some info about the client or MACs # it plays no role try to answer the request if transaction.client_llip in collected_macs or cfg.IGNORE_MAC: if not cfg.IGNORE_MAC: if transaction.mac == DUMMY_MAC: transaction.mac = collected_macs[ transaction.client_llip].mac # ADVERTISE # if last request was a SOLICIT send an ADVERTISE (type 2) back if transaction.last_message_received_type == CONST.MESSAGE.SOLICIT \ and not transaction.rapid_commit: # preference option (7) is for free self.build_response( CONST.MESSAGE.ADVERTISE, transaction, transaction.ia_options + [CONST.OPTION.PREFERENCE] + transaction.options_request) # store leases for addresses and lock advertised address volatile_store.store( deepcopy(transaction), timer.time) # REQUEST # if last request was a REQUEST (type 3) send a REPLY (type 7) back elif transaction.last_message_received_type == CONST.MESSAGE.REQUEST or \ (transaction.last_message_received_type == CONST.MESSAGE.SOLICIT and transaction.rapid_commit): # preference option (7) is for free # if RapidCommit was set give it back if not transaction.rapid_commit: self.build_response( CONST.MESSAGE.REPLY, transaction, transaction.ia_options + [CONST.OPTION.PREFERENCE] + transaction.options_request) else: self.build_response( CONST.MESSAGE.REPLY, transaction, transaction.ia_options + [CONST.OPTION.PREFERENCE] + [CONST.OPTION.RAPID_COMMIT] + transaction.options_request) # store leases for addresses volatile_store.store( deepcopy(transaction), timer.time) # run external script for setting a route to the delegated prefix if CONST.OPTION.IA_PD in transaction.ia_options: modify_route(transaction, 'up') if cfg.DNS_UPDATE: dns_update(transaction) # CONFIRM # if last request was a CONFIRM (4) send a REPLY (type 7) back # Due to problems with different clients they will get a not-available-reply # but the next ADVERTISE will offer them the last known and still active # lease. This makes sense in case of fixed MAC-based, addresses, ranges and # ID-based addresses, Random addresses will be recalculated elif transaction.last_message_received_type == CONST.MESSAGE.CONFIRM: # the RFC 3315 is a little bit confusing regarding CONFIRM # messages so it won't hurt to simply let the client # solicit addresses again via answering 'NotOnLink' # thus client is forced in every case to solicit a new address which # might as well be the old one or a new if prefix has changed self.build_response( CONST.MESSAGE.REPLY, transaction, [CONST.OPTION.STATUS_CODE], status=CONST.STATUS. PREFIX_NOT_APPROPRIATE_FOR_LINK) # RENEW # if last request was a RENEW (type 5) send a REPLY (type 7) back elif transaction.last_message_received_type == CONST.MESSAGE.RENEW: self.build_response( CONST.MESSAGE.REPLY, transaction, transaction.ia_options + [CONST.OPTION.PREFERENCE] + transaction.options_request) # store leases for addresses volatile_store.store( deepcopy(transaction), timer.time) if cfg.DNS_UPDATE: dns_update(transaction) # REBIND # if last request was a REBIND (type 6) send a REPLY (type 7) back elif transaction.last_message_received_type == CONST.MESSAGE.REBIND: self.build_response( CONST.MESSAGE.REPLY, transaction, transaction.ia_options + [CONST.OPTION.PREFERENCE] + transaction.options_request) # store leases for addresses volatile_store.store( deepcopy(transaction), timer.time) # RELEASE # if last request was a RELEASE (type 8) send a REPLY (type 7) back elif transaction.last_message_received_type == CONST.MESSAGE.RELEASE: # build client to be able to delete it from DNS if transaction.client is None: transaction.client = Client( transaction) if cfg.DNS_UPDATE: for a in transaction.addresses: dns_delete(transaction, address=a, action='release') for a in transaction.addresses: # free lease volatile_store.release_lease( a, timer.time) for p in transaction.prefixes: # free prefix - without length volatile_store.release_prefix( p.split('/')[0], timer.time) # delete route to formerly requesting client modify_route(transaction, 'down') # send status code option (type 13) with success (type 0) self.build_response( CONST.MESSAGE.REPLY, transaction, [CONST.OPTION.STATUS_CODE], status=CONST.STATUS.SUCCESS) # DECLINE # if last request was a DECLINE (type 9) send a REPLY (type 7) back elif transaction.last_message_received_type == CONST.MESSAGE.DECLINE: # maybe has to be refined - now only a status code 'NoBinding' is answered self.build_response( CONST.MESSAGE.REPLY, transaction, [CONST.OPTION.STATUS_CODE], status=CONST.STATUS.NO_BINDING) # INFORMATION-REQUEST # if last request was an INFORMATION-REQUEST (type 11) send a REPLY (type 7) back elif transaction.last_message_received_type == CONST.MESSAGE.INFORMATION_REQUEST: self.build_response( CONST.MESSAGE.REPLY, transaction, transaction.options_request) # general error - statuscode 1 'Failure' else: # send Status Code Option (type 13) with status code 'UnspecFail' self.build_response( CONST.MESSAGE.REPLY, transaction, [CONST.OPTION.STATUS_CODE], status=CONST.STATUS.FAILURE) # count requests of transaction # if there will be too much something went wrong # may be evaluated to reset the whole transaction transaction.counter += 1 except Exception as err: traceback.print_exc(file=sys.stdout) sys.stdout.flush() log.error( f'handle(): {str(err)} | caused by: {client_address} | transaction: {transaction.id}' ) return None
def nat(request): if request.method == "POST" \ and not GeneralSettings.objects.filter(key="NETWORK_CONFIGURED"): form = NatForm(request.POST, interfaces=if_nameindex()) if form.is_valid(): # if_indextoname(1) -> name of the network ip_network = form.cleaned_data.get('ip_network') interface_index = form.cleaned_data.get('interface') netmask = str(IPNetwork(ip_network).netmask) gateway_interface = if_indextoname(int(interface_index)) network_name = form.cleaned_data.get('network_name') bridge_name = form.cleaned_data.get('bridge_name') host_ip = form.cleaned_data.get('host_ip') dhcp_start = form.cleaned_data.get('dhcp_start') dhcp_end = form.cleaned_data.get('dhcp_end') if aplibvirt.checkIfNetworkExists(virt_conn, network_name): messages.error(request, "Network with that name already exists!") return redirect("/sys/nat/") result_dict = { 'netmask': netmask, 'ifname': bridge_name, 'net_name': network_name, 'gateway': gateway_interface, 'host_ip': host_ip, 'dhcp_start': dhcp_start, 'dhcp_end': dhcp_end, } # Copy template and fill it nat_virsh_file = render_to_string("nat.xml", result_dict) if aplibvirt.createNetwork(virt_conn, nat_virsh_file): for key, value in result_dict.items(): full_key = 'NETWORK_CONFIGURATION_' + key.upper() if GeneralSettings.objects.filter(key=full_key): tmp = GeneralSettings.objects.get(key=full_key) else: tmp = GeneralSettings() tmp.key = full_key tmp.value = value tmp.save() del tmp tmp = GeneralSettings() tmp.key = "NETWORK_CONFIGURED" tmp.value = True tmp.save() del tmp return redirect("/sys/nat/") else: messages.error( request, "Fatal error occured during network definition.") else: form = NatForm(request.POST, interfaces=if_nameindex()) return render(request, "panel/nat.html", {'form': form}) elif GeneralSettings.objects.filter(key="NETWORK_CONFIGURED"): net_config_dict = { 'netmask': None, 'ifname': None, 'net_name': None, 'gateway': None, 'host_ip': None, 'dhcp_start': None, 'dhcp_end': None, } if request.method == "POST": if request.POST.get('delete', '') == 'config': net_name = GeneralSettings.objects.get( key="NETWORK_CONFIGURATION_NET_NAME").value aplibvirt.deleteNetwork(virt_conn, net_name) for key in net_config_dict.keys(): net_config_dict[key] = GeneralSettings.objects.get( key="NETWORK_CONFIGURATION_" + key.upper()).delete() GeneralSettings.objects.get(key="NETWORK_CONFIGURED").delete() return redirect("/sys/nat/") else: for key in net_config_dict.keys(): net_config_dict[key] = GeneralSettings.objects.filter( key="NETWORK_CONFIGURATION_" + key.upper())[0].value return render(request, "panel/nat.html", { 'form': None, 'net_config': net_config_dict, }) form = NatForm(interfaces=if_nameindex()) return render(request, "panel/nat.html", {'form': form})
def socket_if_indextoname(index): if index < 0: return None return socket.if_indextoname(index)
# (число с плавающей точкой) s.sethostname(name) # Задайте имя хоста машины. Это приведет к вызову OSError, если у # вас недостаточно прав. s.if_nameindex() # Вернёт список информации о сетевом интерфейсе (индекс, имя). # OSError, если системный вызов завершается с ошибкой. s.if_nametoindex(if_name) # Вернёт номер индекса сетевого интерфейса, соответствующий # имени интерфейса. OSError, если интерфейс с данным именем не # существует. s.if_indextoname(if_index) # Вернёт имя сетевого интерфейса, соответствующее номеру индекса # интерфейса. OSError, если интерфейс с данным индексом не существует. # socket objects ::::::::::::::::::::::::::::::::::::::::::::::::::::::: s = socket(..., ...) s.accept() # блокирует приложение до тех пор, пока не придет сообщение от клиента. # Функция возвращает кортеж из двух параметров – объект самого соединения # и адрес клиента. # Пассивно принимает клиентский запрос на установление соединения TCP, # находясь в состоянии ожидания до поступления запроса на установления # (блакирующий режим)