1
0
mirror of https://github.com/JoelBender/bacpypes synced 2025-09-28 22:15:23 +08:00
bacpypes/py27/bacpypes/netservice.py
2015-07-17 09:24:05 -04:00

751 lines
28 KiB
Python
Executable File

#!/usr/bin/python
"""
Network Service
"""
from copy import copy as _copy
from .debugging import ModuleLogger, DebugContents, bacpypes_debugging
from .errors import ConfigurationError
from .comm import Client, Server, bind, \
ServiceAccessPoint, ApplicationServiceElement
from .pdu import Address, LocalBroadcast, LocalStation, PDU, RemoteStation
from .npdu import IAmRouterToNetwork, NPDU, WhoIsRouterToNetwork, npdu_types
from .apdu import APDU as _APDU
# some debugging
_debug = 0
_log = ModuleLogger(globals())
# router status values
ROUTER_AVAILABLE = 0 # normal
ROUTER_BUSY = 1 # router is busy
ROUTER_DISCONNECTED = 2 # could make a connection, but hasn't
ROUTER_UNREACHABLE = 3 # cannot route
#
# NetworkReference
#
class NetworkReference:
"""These objects map a network to a router."""
def __init__(self, net, router, status):
self.network = net
self.router = router
self.status = status
#
# RouterReference
#
class RouterReference(DebugContents):
"""These objects map a router; the adapter to talk to it,
its address, and a list of networks that it routes to."""
_debug_contents = ('adapter-', 'address', 'networks', 'status')
def __init__(self, adapter, addr, nets, status):
self.adapter = adapter
self.address = addr # local station relative to the adapter
self.networks = nets # list of remote networks
self.status = status # status as presented by the router
#
# NetworkAdapter
#
@bacpypes_debugging
class NetworkAdapter(Client, DebugContents):
_debug_contents = ('adapterSAP-', 'adapterNet')
def __init__(self, sap, net, cid=None):
if _debug: NetworkAdapter._debug("__init__ %r (net=%r) cid=%r", sap, net, cid)
Client.__init__(self, cid)
self.adapterSAP = sap
self.adapterNet = net
# add this to the list of adapters for the network
sap.adapters.append(self)
def confirmation(self, pdu):
"""Decode upstream PDUs and pass them up to the service access point."""
if _debug: NetworkAdapter._debug("confirmation %r (net=%r)", pdu, self.adapterNet)
npdu = NPDU(user_data=pdu.pduUserData)
npdu.decode(pdu)
self.adapterSAP.process_npdu(self, npdu)
def process_npdu(self, npdu):
"""Encode NPDUs from the service access point and send them downstream."""
if _debug: NetworkAdapter._debug("process_npdu %r (net=%r)", npdu, self.adapterNet)
pdu = PDU(user_data=npdu.pduUserData)
npdu.encode(pdu)
self.request(pdu)
def EstablishConnectionToNetwork(self, net):
pass
def DisconnectConnectionToNetwork(self, net):
pass
#
# NetworkServiceAccessPoint
#
@bacpypes_debugging
class NetworkServiceAccessPoint(ServiceAccessPoint, Server, DebugContents):
_debug_contents = ('adapters++', 'routers++', 'networks+'
, 'localAdapter-', 'localAddress'
)
def __init__(self, sap=None, sid=None):
if _debug: NetworkServiceAccessPoint._debug("__init__ sap=%r sid=%r", sap, sid)
ServiceAccessPoint.__init__(self, sap)
Server.__init__(self, sid)
self.adapters = [] # list of adapters
self.routers = {} # (adapter, address) -> RouterReference
self.networks = {} # network -> RouterReference
self.localAdapter = None # which one is local
self.localAddress = None # what is the local address
def bind(self, server, net=None, address=None):
"""Create a network adapter object and bind."""
if _debug: NetworkServiceAccessPoint._debug("bind %r net=%r address=%r", server, net, address)
if (net is None) and self.adapters:
raise RuntimeError("already bound")
# create an adapter object
adapter = NetworkAdapter(self, net)
# if the address was given, make it the "local" one
if address:
self.localAdapter = adapter
self.localAddress = address
# bind to the server
bind(adapter, server)
#-----
def add_router_references(self, adapter, address, netlist):
"""Add/update references to routers."""
if _debug: NetworkServiceAccessPoint._debug("add_router_references %r %r %r", adapter, address, netlist)
# make a key for the router reference
rkey = (adapter, address)
for snet in netlist:
# see if this is spoofing an existing routing table entry
if snet in self.networks:
rref = self.networks[snet]
if rref.adapter == adapter and rref.address == address:
pass # matches current entry
else:
### check to see if this source could be a router to the new network
# remove the network from the rref
i = rref.networks.index(snet)
del rref.networks[i]
# remove the network
del self.networks[snet]
### check to see if it is OK to add the new entry
# get the router reference for this router
rref = self.routers.get(rkey, None)
if rref:
if snet not in rref.networks:
# add the network
rref.networks.append(snet)
# reference the snet
self.networks[snet] = rref
else:
# new reference
rref = RouterReference( adapter, address, [snet], 0)
self.routers[rkey] = rref
# reference the snet
self.networks[snet] = rref
def remove_router_references(self, adapter, address=None):
"""Add/update references to routers."""
if _debug: NetworkServiceAccessPoint._debug("remove_router_references %r %r", adapter, address)
delrlist = []
delnlist = []
# scan through the dictionary of router references
for rkey in self.routers.keys():
# rip apart the key
radapter, raddress = rkey
# pick all references on the adapter, optionally limited to a specific address
match = radapter is adapter
if match and address is not None:
match = (raddress == address)
if not match:
continue
# save it for deletion
delrlist.append(rkey)
delnlist.extend(self.routers[rkey].networks)
if _debug:
NetworkServiceAccessPoint._debug(" - delrlist: %r", delrlist)
NetworkServiceAccessPoint._debug(" - delnlist: %r", delnlist)
# delete the entries
for rkey in delrlist:
try:
del self.routers[rkey]
except KeyError:
if _debug: NetworkServiceAccessPoint._debug(" - rkey not in self.routers: %r", rkey)
for nkey in delnlist:
try:
del self.networks[nkey]
except KeyError:
if _debug: NetworkServiceAccessPoint._debug(" - nkey not in self.networks: %r", rkey)
#-----
def indication(self, pdu):
if _debug: NetworkServiceAccessPoint._debug("indication %r", pdu)
# make sure our configuration is OK
if (not self.adapters):
raise ConfigurationError("no adapters")
# might be able to relax this restriction
if (len(self.adapters) > 1) and (not self.localAdapter):
raise ConfigurationError("local adapter must be set")
# get the local adapter
adapter = self.localAdapter or self.adapters[0]
# build a generic APDU
apdu = _APDU(user_data=pdu.pduUserData)
pdu.encode(apdu)
if _debug: NetworkServiceAccessPoint._debug(" - apdu: %r", apdu)
# build an NPDU specific to where it is going
npdu = NPDU(user_data=pdu.pduUserData)
apdu.encode(npdu)
if _debug: NetworkServiceAccessPoint._debug(" - npdu: %r", npdu)
# the hop count always starts out big
npdu.npduHopCount = 255
# local stations given to local adapter
if (npdu.pduDestination.addrType == Address.localStationAddr):
adapter.process_npdu(npdu)
return
# local broadcast given to local adapter
if (npdu.pduDestination.addrType == Address.localBroadcastAddr):
adapter.process_npdu(npdu)
return
# global broadcast
if (npdu.pduDestination.addrType == Address.globalBroadcastAddr):
# set the destination
npdu.pduDestination = LocalBroadcast()
npdu.npduDADR = apdu.pduDestination
# send it to all of connected adapters
for xadapter in self.adapters:
xadapter.process_npdu(npdu)
return
# remote broadcast
if (npdu.pduDestination.addrType != Address.remoteBroadcastAddr) and (npdu.pduDestination.addrType != Address.remoteStationAddr):
raise RuntimeError("invalid destination address type: %s" % (npdu.pduDestination.addrType,))
dnet = npdu.pduDestination.addrNet
# if the network matches the local adapter it's local
if (dnet == adapter.adapterNet):
### log this, the application shouldn't be sending to a remote station address
### when it's a directly connected network
raise RuntimeError("addressing problem")
# check for an available path
if dnet in self.networks:
rref = self.networks[dnet]
adapter = rref.adapter
### make sure the direct connect is OK, may need to connect
### make sure the peer router is OK, may need to connect
# fix the destination
npdu.pduDestination = rref.address
npdu.npduDADR = apdu.pduDestination
# send it along
adapter.process_npdu(npdu)
return
if _debug: NetworkServiceAccessPoint._debug(" - no known path to network, broadcast to discover it")
# set the destination
npdu.pduDestination = LocalBroadcast()
npdu.npduDADR = apdu.pduDestination
# send it to all of the connected adapters
for xadapter in self.adapters:
xadapter.process_npdu(npdu)
def process_npdu(self, adapter, npdu):
if _debug: NetworkServiceAccessPoint._debug("process_npdu %r %r", adapter, npdu)
# make sure our configuration is OK
if (not self.adapters):
raise ConfigurationError("no adapters")
if (len(self.adapters) > 1) and (not self.localAdapter):
raise ConfigurationError("local adapter must be set")
# check for source routing
if npdu.npduSADR and (npdu.npduSADR.addrType != Address.nullAddr):
# see if this is attempting to spoof a directly connected network
snet = npdu.npduSADR.addrNet
for xadapter in self.adapters:
if (xadapter is not adapter) and (snet == xadapter.adapterNet):
NetworkServiceAccessPoint._warning("spoof?")
### log this
return
# make a key for the router reference
rkey = (adapter, npdu.pduSource)
# see if this is spoofing an existing routing table entry
if snet in self.networks:
rref = self.networks[snet]
if rref.adapter == adapter and rref.address == npdu.pduSource:
pass # matches current entry
else:
if _debug: NetworkServiceAccessPoint._debug(" - replaces entry")
### check to see if this source could be a router to the new network
# remove the network from the rref
i = rref.networks.index(snet)
del rref.networks[i]
# remove the network
del self.networks[snet]
# get the router reference for this router
rref = self.routers.get(rkey)
if rref:
if snet not in rref.networks:
# add the network
rref.networks.append(snet)
# reference the snet
self.networks[snet] = rref
else:
# new reference
rref = RouterReference( adapter, npdu.pduSource, [snet], 0)
self.routers[rkey] = rref
# reference the snet
self.networks[snet] = rref
# check for destination routing
if (not npdu.npduDADR) or (npdu.npduDADR.addrType == Address.nullAddr):
processLocally = (not self.localAdapter) or (adapter is self.localAdapter) or (npdu.npduNetMessage is not None)
forwardMessage = False
elif npdu.npduDADR.addrType == Address.remoteBroadcastAddr:
if not self.localAdapter:
return
if (npdu.npduDADR.addrNet == adapter.adapterNet):
### log this, attempt to route to a network the device is already on
return
processLocally = (npdu.npduDADR.addrNet == self.localAdapter.adapterNet)
forwardMessage = True
elif npdu.npduDADR.addrType == Address.remoteStationAddr:
if not self.localAdapter:
return
if (npdu.npduDADR.addrNet == adapter.adapterNet):
### log this, attempt to route to a network the device is already on
return
processLocally = (npdu.npduDADR.addrNet == self.localAdapter.adapterNet) \
and (npdu.npduDADR.addrAddr == self.localAddress.addrAddr)
forwardMessage = not processLocally
elif npdu.npduDADR.addrType == Address.globalBroadcastAddr:
processLocally = True
forwardMessage = True
else:
NetworkServiceAccessPoint._warning("invalid destination address type: %s", npdu.npduDADR.addrType)
return
if _debug:
NetworkServiceAccessPoint._debug(" - processLocally: %r", processLocally)
NetworkServiceAccessPoint._debug(" - forwardMessage: %r", forwardMessage)
# application or network layer message
if npdu.npduNetMessage is None:
if processLocally and self.serverPeer:
# decode as a generic APDU
apdu = _APDU(user_data=npdu.pduUserData)
apdu.decode(_copy(npdu))
if _debug: NetworkServiceAccessPoint._debug(" - apdu: %r", apdu)
# see if it needs to look routed
if (len(self.adapters) > 1) and (adapter != self.localAdapter):
# combine the source address
if not npdu.npduSADR:
apdu.pduSource = RemoteStation( adapter.adapterNet, npdu.pduSource.addrAddr )
else:
apdu.pduSource = npdu.npduSADR
# map the destination
if not npdu.npduDADR:
apdu.pduDestination = self.localAddress
elif npdu.npduDADR.addrType == Address.globalBroadcastAddr:
apdu.pduDestination = npdu.npduDADR
elif npdu.npduDADR.addrType == Address.remoteBroadcastAddr:
apdu.pduDestination = LocalBroadcast()
else:
apdu.pduDestination = self.localAddress
else:
# combine the source address
if npdu.npduSADR:
apdu.pduSource = npdu.npduSADR
else:
apdu.pduSource = npdu.pduSource
# pass along global broadcast
if npdu.npduDADR and npdu.npduDADR.addrType == Address.globalBroadcastAddr:
apdu.pduDestination = npdu.npduDADR
else:
apdu.pduDestination = npdu.pduDestination
if _debug:
NetworkServiceAccessPoint._debug(" - apdu.pduSource: %r", apdu.pduSource)
NetworkServiceAccessPoint._debug(" - apdu.pduDestination: %r", apdu.pduDestination)
# pass upstream to the application layer
self.response(apdu)
if not forwardMessage:
return
else:
if processLocally:
if npdu.npduNetMessage not in npdu_types:
if _debug: NetworkServiceAccessPoint._debug(" - unknown npdu type: %r", npdu.npduNetMessage)
return
# do a deeper decode of the NPDU
xpdu = npdu_types[npdu.npduNetMessage](user_data=npdu.pduUserData)
xpdu.decode(_copy(npdu))
# pass to the service element
self.sap_request(adapter, xpdu)
if not forwardMessage:
return
# make sure we're really a router
if (len(self.adapters) == 1):
return
# make sure it hasn't looped
if (npdu.npduHopCount == 0):
return
# build a new NPDU to send to other adapters
newpdu = _copy(npdu)
# clear out the source and destination
newpdu.pduSource = None
newpdu.pduDestination = None
# decrease the hop count
newpdu.npduHopCount -= 1
# set the source address
if not npdu.npduSADR:
newpdu.npduSADR = RemoteStation( adapter.adapterNet, npdu.pduSource.addrAddr )
else:
newpdu.npduSADR = npdu.npduSADR
# if this is a broadcast it goes everywhere
if npdu.npduDADR.addrType == Address.globalBroadcastAddr:
newpdu.pduDestination = LocalBroadcast()
for xadapter in self.adapters:
if (xadapter is not adapter):
xadapter.process_npdu(newpdu)
return
if (npdu.npduDADR.addrType == Address.remoteBroadcastAddr) \
or (npdu.npduDADR.addrType == Address.remoteStationAddr):
dnet = npdu.npduDADR.addrNet
# see if this should go to one of our directly connected adapters
for xadapter in self.adapters:
if dnet == xadapter.adapterNet:
if _debug: NetworkServiceAccessPoint._debug(" - found direct connect via %r", xadapter)
if (npdu.npduDADR.addrType == Address.remoteBroadcastAddr):
newpdu.pduDestination = LocalBroadcast()
else:
newpdu.pduDestination = LocalStation(npdu.npduDADR.addrAddr)
# last leg in routing
newpdu.npduDADR = None
# send the packet downstream
xadapter.process_npdu(newpdu)
return
# see if we know how to get there
if dnet in self.networks:
rref = self.networks[dnet]
newpdu.pduDestination = rref.address
### check to make sure the router is OK
### check to make sure the network is OK, may need to connect
if _debug: NetworkServiceAccessPoint._debug(" - newpdu: %r", newpdu)
# send the packet downstream
rref.adapter.process_npdu(newpdu)
return
### queue this message for reprocessing when the response comes back
# try to find a path to the network
xnpdu = WhoIsRouterToNetwork(dnet)
xnpdu.pduDestination = LocalBroadcast()
# send it to all of the connected adapters
for xadapter in self.adapters:
# skip the horse it rode in on
if (xadapter is adapter):
continue
### make sure the adapter is OK
self.sap_indication(xadapter, xnpdu)
### log this, what to do?
return
def sap_indication(self, adapter, npdu):
if _debug: NetworkServiceAccessPoint._debug("sap_indication %r %r", adapter, npdu)
# encode it as a generic NPDU
xpdu = NPDU(user_data=npdu.pduUserData)
npdu.encode(xpdu)
npdu._xpdu = xpdu
# tell the adapter to process the NPDU
adapter.process_npdu(xpdu)
def sap_confirmation(self, adapter, npdu):
if _debug: NetworkServiceAccessPoint._debug("sap_confirmation %r %r", adapter, npdu)
# encode it as a generic NPDU
xpdu = NPDU(user_data=npdu.pduUserData)
npdu.encode(xpdu)
npdu._xpdu = xpdu
# tell the adapter to process the NPDU
adapter.process_npdu(xpdu)
#
# NetworkServiceElement
#
@bacpypes_debugging
class NetworkServiceElement(ApplicationServiceElement):
def __init__(self, eid=None):
if _debug: NetworkServiceElement._debug("__init__ eid=%r", eid)
ApplicationServiceElement.__init__(self, eid)
def indication(self, adapter, npdu):
if _debug: NetworkServiceElement._debug("indication %r %r", adapter, npdu)
# redirect
fn = npdu.__class__.__name__
if hasattr(self, fn):
getattr(self, fn)(adapter, npdu)
def confirmation(self, adapter, npdu):
if _debug: NetworkServiceElement._debug("confirmation %r %r", adapter, npdu)
# redirect
fn = npdu.__class__.__name__
if hasattr(self, fn):
getattr(self, fn)(adapter, npdu)
#-----
def WhoIsRouterToNetwork(self, adapter, npdu):
if _debug: NetworkServiceElement._debug("WhoIsRouterToNetwork %r %r", adapter, npdu)
# reference the service access point
sap = self.elementService
if _debug: NetworkServiceElement._debug(" - sap: %r", sap)
# if we're not a router, skip it
if len(sap.adapters) == 1:
if _debug: NetworkServiceElement._debug(" - not a router")
return
if npdu.wirtnNetwork is None:
# requesting all networks
if _debug: NetworkServiceElement._debug(" - requesting all networks")
# build a list of reachable networks
netlist = []
# start with directly connected networks
for xadapter in sap.adapters:
if (xadapter is not adapter):
netlist.append(xadapter.adapterNet)
# build a list of other available networks
for net, rref in sap.networks.items():
if rref.adapter is not adapter:
### skip those marked unreachable
### skip those that are not available
netlist.append(net)
if netlist:
if _debug: NetworkServiceElement._debug(" - found these: %r", netlist)
# build a response
iamrtn = IAmRouterToNetwork(netlist, user_data=npdu.pduUserData)
iamrtn.pduDestination = npdu.pduSource
# send it back
self.response(adapter, iamrtn)
else:
# requesting a specific network
if _debug: NetworkServiceElement._debug(" - requesting specific network: %r", npdu.wirtnNetwork)
# start with directly connected networks
for xadapter in sap.adapters:
if (xadapter is not adapter) and (npdu.wirtnNetwork == xadapter.adapterNet):
if _debug: NetworkServiceElement._debug(" - found it directly connected")
# build a response
iamrtn = IAmRouterToNetwork([npdu.wirtnNetwork], user_data=npdu.pduUserData)
iamrtn.pduDestination = npdu.pduSource
# send it back
self.response(adapter, iamrtn)
break
else:
# check for networks I know about
if npdu.wirtnNetwork in sap.networks:
rref = sap.networks[npdu.wirtnNetwork]
if rref.adapter is adapter:
if _debug: NetworkServiceElement._debug(" - same net as request")
else:
if _debug: NetworkServiceElement._debug(" - found on adapter: %r", rref.adapter)
# build a response
iamrtn = IAmRouterToNetwork([npdu.wirtnNetwork], user_data=npdu.pduUserData)
iamrtn.pduDestination = npdu.pduSource
# send it back
self.response(adapter, iamrtn)
else:
if _debug: NetworkServiceElement._debug(" - forwarding request to other adapters")
# build a request
whoisrtn = WhoIsRouterToNetwork(npdu.wirtnNetwork, user_data=npdu.pduUserData)
whoisrtn.pduDestination = LocalBroadcast()
# if the request had a source, forward it along
if npdu.npduSADR:
whoisrtn.npduSADR = npdu.npduSADR
else:
whoisrtn.npduSADR = RemoteStation(adapter.adapterNet, npdu.pduSource.addrAddr)
if _debug: NetworkServiceElement._debug(" - whoisrtn: %r", whoisrtn)
# send it to all of the (other) adapters
for xadapter in sap.adapters:
if xadapter is not adapter:
if _debug: NetworkServiceElement._debug(" - sending on adapter: %r", xadapter)
self.request(xadapter, whoisrtn)
def IAmRouterToNetwork(self, adapter, npdu):
if _debug: NetworkServiceElement._debug("IAmRouterToNetwork %r %r", adapter, npdu)
# pass along to the service access point
self.elementService.add_router_references(adapter, npdu.pduSource, npdu.iartnNetworkList)
def ICouldBeRouterToNetwork(self, adapter, npdu):
if _debug: NetworkServiceElement._debug("ICouldBeRouterToNetwork %r %r", adapter, npdu)
# reference the service access point
# sap = self.elementService
def RejectMessageToNetwork(self, adapter, npdu):
if _debug: NetworkServiceElement._debug("RejectMessageToNetwork %r %r", adapter, npdu)
# reference the service access point
# sap = self.elementService
def RouterBusyToNetwork(self, adapter, npdu):
if _debug: NetworkServiceElement._debug("RouterBusyToNetwork %r %r", adapter, npdu)
# reference the service access point
# sap = self.elementService
def RouterAvailableToNetwork(self, adapter, npdu):
if _debug: NetworkServiceElement._debug("RouterAvailableToNetwork %r %r", adapter, npdu)
# reference the service access point
# sap = self.elementService
def InitializeRoutingTable(self, adapter, npdu):
if _debug: NetworkServiceElement._debug("InitializeRoutingTable %r %r", adapter, npdu)
# reference the service access point
# sap = self.elementService
def InitializeRoutingTableAck(self, adapter, npdu):
if _debug: NetworkServiceElement._debug("InitializeRoutingTableAck %r %r", adapter, npdu)
# reference the service access point
# sap = self.elementService
def EstablishConnectionToNetwork(self, adapter, npdu):
if _debug: NetworkServiceElement._debug("EstablishConnectionToNetwork %r %r", adapter, npdu)
# reference the service access point
# sap = self.elementService
def DisconnectConnectionToNetwork(self, adapter, npdu):
if _debug: NetworkServiceElement._debug("DisconnectConnectionToNetwork %r %r", adapter, npdu)
# reference the service access point
# sap = self.elementService