#!/usr/bin/python# -*- coding: utf-8 -*-## Copyright (C) 2015 by Christian Tremblay, P.Eng <christian.tremblay@servisys.com>## Licensed under LGPLv3, see file LICENSE in this source tree."""Utility function to retrieve a functionnal IP and a correct broadcast IPaddress.Goal : not use 255.255.255.255 as a broadcast IP address as it is notaccepted by every devices (>3.8.38.1 bacnet.jar of Tridium Jace for example)"""importipaddressimportsocketimporttypingastimportplatformimportsubprocessimportreimportstruct# from bacpypes.pdu import Address as legacy_Addressfrombacpypes3.pduimportAddressfrom...core.utils.notesimportnote_and_logfrom..io.IOExceptionsimportNetworkInterfaceExceptionDEFAULT_PORT=47808
[docs]@note_and_logclassHostIP:""" Special class to identify host IP informations """def__init__(self,port:t.Optional[int]=None)->None:ip=self._findIPAddr()mask=self._findSubnetMask(ip)ifportisnotNone:self._port=portelse:self._port=DEFAULT_PORTself.interface=ipaddress.IPv4Interface(f"{ip}/{mask}")@propertydefip_address_subnet(self):""" IP Address/subnet """return"{}/{}".format(self.interface.ip.compressed,self.interface.exploded.split("/")[-1])@propertydefip_address(self):""" IP Address/subnet """returnf"{self.interface.ip.compressed}"@propertydefaddress(self)->Address:""" IP Address using bacpypes Address format """port=""ifself._port:port=f":{self._port}"returnAddress("{}/{}{}".format(self.interface.ip.compressed,self.interface.exploded.split("/")[-1],port,))@propertydefmask(self):""" Subnet mask """returnself.interface.exploded.split("/")[-1]@propertydefport(self):""" IP Port used """returnself._portdef_findIPAddr(self)->str:""" Retrieve the IP address connected to internet... used as a default IP address when defining Script :returns: IP Adress as String """s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)try:s.connect(("google.com",443))addr=s.getsockname()[0]s.close()exceptsocket.error:raiseNetworkInterfaceException("Impossible to retrieve IP, please provide one manually")returnaddrdef_old_findSubnetMask(self,ip:str)->str:""" Retrieve the broadcast IP address connected to internet... used as a default IP address when defining Script :param ip: (str) optionnal IP address. If not provided, default to getIPAddr() :returns: broadcast IP Adress as String """try:importnetifacesinterfaces=netifaces.interfaces()fornicininterfaces:addresses=netifaces.ifaddresses(nic)try:foraddressinaddresses[netifaces.AF_INET]:ifaddress["addr"]==ip:returnaddress["netmask"]exceptKeyError:passreturn"255.255.255.255"exceptImportError:self._log.warning("Netifaces not installed on your system. BAC0 can't detect the subnet.\nPlease provide subnet for now, ""we'll consider 255.255.255.0 (/24).\nYou can install netifaces using 'pip install netifaces'.")return"255.255.255.0"def_findSubnetMask(self,ip:str)->t.Optional[str]:""" Retrieve the broadcast IP address connected to internet... used as a default IP address when defining Script :param ip: (str) optional IP address. If not provided, default to getIPAddr() :returns: broadcast IP Address as String """ifplatform.system()=="Windows":try:# Run the ipconfig commandresult=subprocess.run(["ipconfig"],capture_output=True,text=True)output=result.stdout# Regular expression to match IP and subnet maskip_pattern=re.compile(r"IPv4 Address[. ]*: ([\d.]+)")mask_pattern=re.compile(r"Subnet Mask[. ]*: ([\d.]+)")# Parse the outputlines=output.splitlines()fori,lineinenumerate(lines):ip_match=ip_pattern.search(line)ifip_match:found_ip=ip_match.group(1)ifipisNoneorfound_ip==ip:# Look for the subnet mask in the next few linesforjinrange(i+1,i+6):mask_match=mask_pattern.search(lines[j])ifmask_match:returnmask_match.group(1)return"255.255.255.255"exceptExceptionase:print(f"An error occurred: {e}")returnNoneelifplatform.system()=="Linux":importfcntldefget_interface_info(ifname):s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)returnfcntl.ioctl(s.fileno(),0x891B,# SIOCGIFNETMASKstruct.pack("256s",ifname[:15].encode("utf-8")),)try:interfaces=socket.if_nameindex()forifindex,ifnameininterfaces:try:netmask=socket.inet_ntoa(get_interface_info(ifname)[20:24])ip_address=socket.inet_ntoa(fcntl.ioctl(socket.socket(socket.AF_INET,socket.SOCK_DGRAM),0x8915,# SIOCGIFADDRstruct.pack("256s",ifname[:15].encode("utf-8")),)[20:24])ifipisNoneorip_address==ip:returnnetmaskexceptIOError:passreturn"255.255.255.255"exceptExceptionase:print(f"An error occurred: {e}")returnNoneelse:print("Unsupported platform")returnNone
[docs]defvalidate_ip_address(ip:Address)->bool:result=Trues=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)try:ifnotisinstance(ip,Address):raiseValueError("Provide Address as bacpypes.Address object")s.bind(ip.addrTuple)exceptOSError:result=Falsefinally:s.close()returnresult