--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/apps/dnd.py Thu Apr 21 14:57:45 2011 +0100
@@ -0,0 +1,1202 @@
+#!/usr/bin/python
+
+#
+# Copyright 2006 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+
+# DTN Neighbor Discovery (over UDP Broadcast) -- A small python script
+# that will propagate DTN registration information via UDP broadcasts.
+#
+# Written by Keith Scott, The MITRE Corporation
+
+# I got tired of having to manually configure dtn daemons, expecially
+# since the combination of the windows operating system and MITRE's
+# dhcp/dynamic DNS caused machine names/addresses to change
+# when least convenient.
+
+# This script will populate the static routing tables of DTN daemons
+# with registrations (and optionally routes) it hears from its peers.
+# When advertising my local
+# registrations, I append "/*" to the local EID and prune anything
+# that would match on this route. This way if I'm running something
+# like bundlePing that generates 'transient' registrations,
+# I don't end up cluttering up everybody else's tables with 'em.
+
+# This script assumes that all machines use TCP convergence layers to
+# communicate
+
+# This script transmits UDP broadcast messages to a particular port
+# (5005 by default). You'll need to open up firewalls to let this
+# traffic through.
+
+# The UDP Messages sent are of the form:
+#
+# my DTN local EID
+# my TCP CL Listen Port
+# EID1 route1 distance1 nextHopEID1
+# EID2 route2 distance2 nextHopEID2
+# ...
+
+from socket import *
+from time import *
+import mutex
+import os
+import random
+import string
+import thread
+import re
+import getopt
+import sys
+import struct
+
+INFINITY = 100
+THE_MULTICAST_TTL = 5
+
+# The default address and port
+_DND_PORT = 5005
+_DND_ADDR = '239.0.1.99'
+
+#sendToAddresses = [_DND_ADDR]
+#sendToPort = 5005 # Port to which reg info is sent
+dtnTclConsolePort = 5050 # Port on which DTN tcl interpreter is listening
+rebroadcastRoutes = 1
+addLocalEIDWildcard = 1 # If 1, advertise a route of the form
+ # dtn://LOCAL_EID/* in addition to any
+ # registrations. Note that the wildcard route will
+ # probably override other registrations.
+myRIB = [] # list of entries
+myEID = ""
+myPort = ""
+verbose = 0
+
+# This mutex makes sure that the various threads for receiving / processing messages
+# and sending out messages don't step on each other.
+messageProcessingMutex = mutex.mutex()
+
+myListeningDTNTCPPort = ""
+
+# Here's a list of 'default' routes. If we have no other way to get to a particular
+# destination EID, make sure these are instantiated. If there's any other way to get
+# to a destination EID, make sure these are DE-instantiated so thate we don't have
+# duplicate bundles flowing over both paths.
+#
+# Right now, the link name "default" is special and is the only one that will work
+# for default routes.
+#
+defaultRoutes = [["dtn://26959-pc/*", "default"],
+ ["dtn://otherDest/*", "default"]]
+
+#
+# These are the indices of the various elements of a RIB entry.
+# The RIB is really just an annotated copy of the forwarding table.
+#
+RIB_HOST = 0 # IP address of a the next hop to the RIB_EID
+RIB_PORT = 1 # The TCPCL port of the next hop to the RIB_EID
+RIB_EID = 2 # The EID in question (a destination EID)
+RIB_DIST = 3 # Distance to the EID in question.
+RIB_TIME = 4 # Time at which this entry was last updated
+RIB_LINKNAME = 5
+RIB_NHEID = 6 # EID of the next hop (used to implement split-horizon)
+
+ROUTE_TIMEOUT = 25
+
+
+broadcastInterval = 10 # How often to broadcast, in seconds
+
+#
+# Send a message to the dtn tcl interpreter and return results
+# Return 'None' if we couldn't talk.
+#
+def talktcl(sent):
+ received = 0
+ # print "Opening connection to dtnd tcl interpreter."
+ sock = socket(AF_INET, SOCK_STREAM)
+ try:
+ sock.connect(("localhost", dtnTclConsolePort))
+ except:
+ print "Connection failed"
+ sock.close()
+ return None
+
+ try:
+ messlen, received = sock.send(sent), 0
+ if messlen != len(sent):
+ print "Failed to send complete message to tcl interpreter"
+ else:
+ # print "Message '",sent,"' sent to tcl interpreter."
+ messlen = messlen
+
+ data = ''
+ while 1:
+ promptsSeen = 0
+ data += sock.recv(32)
+ #sys.stdout.write(data)
+ received += len(data)
+ # print "Now received:", data
+ # print "checking for '%' in received data stream [",received,"], ", len(data)
+ for i in range(len(data)):
+ if data[i]=='%':
+ promptsSeen = promptsSeen + 1
+ if promptsSeen>1:
+ break;
+ if promptsSeen>1:
+ break;
+
+ # print "talktcl received: ",data," back from tcl.\n"
+
+ except:
+ sock.close()
+ return None
+
+ # Remove up to and including the first prompt
+ firstPrompt=string.find(data, "dtn% ")
+ if firstPrompt==-1:
+ return ''
+ data = data[firstPrompt+5:]
+
+ sock.close()
+ return(data);
+
+#
+# Return the port on which the TCP convergence layer is listening
+#
+def findListeningPort():
+ response = talktcl("interface list\n")
+ if response==None:
+ return None
+
+ lines = string.split(response, "\n")
+ for i in range(len(lines)):
+ if string.find(lines[i], "Convergence Layer: tcp")>=0:
+ words = string.split(lines[i+1])
+ return(words[3])
+ return None
+
+#
+# Munge the list 'lines' to contain only entries
+# that contain (in the re.seach sense) at least
+# one of the keys
+#
+def onlyLinesContaining(lines, keys):
+ answer = []
+ for theLine in lines:
+ for theKey in keys:
+ if re.search(theKey, theLine):
+ answer += [theLine]
+ break;
+ return answer
+
+#
+# Generate a random string containing letters and digits
+# of specified length
+#
+def generateRandom(length):
+ chars = string.ascii_letters + string.digits
+ return(''.join([random.choice(chars) for i in range(length)]))
+
+#
+# Generate a new unique link identifier of the form dnd_XXXX
+# where XXXX is a string of random letters.
+#
+def genNewLink(linkList):
+ done = False
+ print "genNewLink: ", linkList
+ while done==False:
+ test = generateRandom(4)
+ # See if the identifier is already in use
+ if len(linkList)>0:
+ for i in range(len(linkList)):
+ words = string.split(linkList[i], " ");
+ if words[4]!=test:
+ done = True
+ break;
+ else:
+ done = True
+ return "dnd_" + test
+
+
+#
+# Return a pair of lists: the current links and the current
+# routes from the DTN daemon
+#
+def getLinksRoutes():
+ myRoutes = talktcl("route dump\n")
+ if myRoutes==None:
+ print "getLinksRoutes: can't talk to dtn daemon"
+ return([[],[]])
+ #myRoutes = string.strip(myRoutes, "dtn% ")
+
+ # Split the response into lines
+ lines = string.split(myRoutes, '\n');
+
+ theRoutes = []
+ theLinks = []
+
+ # After stripping off the header (first 5 lines),
+ # the routes are the first few lines up to the first blank line
+ i = 0
+ for i in range(5,len(lines)):
+ # If the line has a "->" in it, it's a route.
+ if string.find(lines[i], "->")>=0:
+ theRoutes += [lines[i]]
+ if string.find(lines[i], "Long")>=0:
+ break
+ if string.find(lines[i], "Links")>=0:
+ break
+ if len(lines[i])==1:
+ break
+
+ #
+ # Fix up any Long Endpoint IDs
+ #
+ for i in range(5,len(lines)):
+ if string.find(lines[i], "Long")>=0:
+ break
+ for j in range(i+1, len(lines)):
+ if len(lines[j])==1:
+ break;
+ tokens = string.split(lines[j], ' ');
+ tokens[0] = tokens[0].lstrip()
+ tokens[0] = tokens[0].lstrip()
+ tokens[0] = tokens[0].strip(":")
+
+ for k in range(0, len(theRoutes)):
+ if theRoutes[k].find(tokens[0])>=0:
+ tokens[1] = tokens[1].strip("\r")
+ theRoutes[k] = theRoutes[k].replace(tokens[0], tokens[1])
+
+ #
+ # Find the links
+ # Start by whipping through the lines agin looking for "Links:"
+ #
+ for i in range(5,len(lines)):
+ if string.find(lines[i], "Links:")>=0:
+ break
+
+ for j in range(i+1, len(lines)):
+ if len(lines[j])==1:
+ break;
+ theLinks += [lines[j]]
+
+ if ( verbose > 4 ):
+ print "getLinksRoutes returns: "
+ print theLinks
+ print theRoutes
+ return([theLinks, theRoutes])
+
+# Return the link name of an existing link, or None
+# format for newLink is hot:port
+# format for 'newLink is host:port'
+def alreadyHaveLink(newLink):
+ theLinks, theRoutes = getLinksRoutes()
+ for testLink in theLinks:
+ bar = string.split(newLink, ":")
+ host = bar[0]
+ port = bar[1]
+
+ # If we have a complete match (host:port), we're done
+ if string.find(testLink, newLink)>=0:
+ testLink = string.split(testLink)
+ return testLink[0]
+
+ # If we match on the host and the link is opportunistic,
+ # go ahead and call it a match
+ hostThere = string.find(testLink, host)
+ isOpportunistic = testLink.startswith("opportunistic")
+ if ( (hostThere>0) and (isOpportunistic)):
+ foo = string.split(testLink, " ")
+ return foo[0]
+ return None
+
+def myBroadcast():
+ answer = []
+ myaddrs = os.popen("/sbin/ip addr show").read()
+ myaddrs = string.split(myaddrs, "\n")
+
+ myaddrs = onlyLinesContaining(myaddrs, ["inet.*brd"])
+
+ for addr in myaddrs:
+ words = string.split(addr)
+ for i in range(len(words)):
+ if words[i]=="brd":
+ answer += [words[i+1]]
+
+ return answer
+
+#
+# Called periodically to time out routes that have not been refreshed.
+#
+def timeOutOldRoutes(RIB):
+ # print "checking RIB for timed out routes..."
+ # printRIB(myRIB)
+ # The whole 'done' thing handles the fact that the list traversal gets gorked when
+ # you yank elements out of the list
+ done = 0
+ while done == 0:
+ done = 1
+ for entry in RIB:
+ if verbose>0:
+ print "RIB entry", entry, "is ", time()-entry[RIB_TIME]," seconds old"
+ if time()-entry[RIB_TIME]>ROUTE_TIMEOUT:
+ print "RIB entry", entry, "timed out."
+ print "Using 'removeRoute "+entry[RIB_EID]
+ removeRoute(entry[RIB_EID])
+ if entry[RIB_DIST]==INFINITY:
+ RIB.remove(entry)
+ entry[RIB_TIME] = time()
+ done = 0
+
+#
+# remove a route, doing 'the right thing' by re-adding routes using the 'default'
+# link for destinations listed as being able to use the default link
+#
+def removeRoute(eid):
+ talktcl("route del "+eid+"\n")
+ for (theEID, theLink) in defaultRoutes:
+ if ( theEID==eid ):
+ talktcl("route add "+eid+" "+theLink+"\n")
+ return True
+ return False
+#
+# Return True if I have an exact routing match (no wildcards) for the given
+# EID
+#
+def exactRouteFor(eid):
+ theLinks, theRoutes = getLinksRoutes()
+ if len(theRoutes)>0:
+ for i in range(len(theRoutes)):
+ theRoutes[i] = theRoutes[i].strip()
+ nextHop = string.split(theRoutes[i])[0];
+ # print "Checking",eid," against existing route:", nextHop
+ if nextHop==eid:
+ return True
+ return False
+
+#
+# Return True if I have a current route to the destination EID, False otherwise
+# Route information is extracted from the deamon, NOT the RIB
+#
+def haveRouteFor(eid):
+ theLinks, theRoutes = getLinksRoutes()
+ if ( verbose>3 ):
+ print "haveRouteFor about to check eid "+eid+" against existing routes [",len(theRoutes),"]"
+ if len(theRoutes)>0:
+ for i in range(len(theRoutes)):
+ theRoutes[i] = theRoutes[i].strip()
+ nextHop = string.split(theRoutes[i])[0];
+ # print "Checking",eid," against existing route:", nextHop
+ foo = re.search(nextHop, eid)
+ if foo!=None:
+ return True
+ return False
+
+def haveNonDefaultRouteFor(eid):
+ theLinks, theRoutes = getLinksRoutes()
+ if ( verbose>3 ):
+ print "haveRouteFor about to check eid "+eid+" against existing routes [",len(theRoutes),"]"
+ if len(theRoutes)>0:
+ for i in range(len(theRoutes)):
+ theRoutes[i] = theRoutes[i].strip()
+ nextHop = string.split(theRoutes[i])[0];
+ theLink = string.split(theRoutes[i])[4];
+ # print "Checking",eid," against existing route:", nextHop
+ foo = re.search(nextHop, eid)
+ if ( (foo!=None) & (theLink!="default") ):
+ return True
+ return False
+
+#
+# Remove any existing route to the eid that uses a link
+# named "default"
+#
+# Return True if we did in fact remove such a route, False if we didn't
+def removeDefaultRouteFor(eid):
+ theLinks, theRoutes = getLinksRoutes()
+ if len(theRoutes)>0:
+ for i in range(len(theRoutes)):
+ theRoutes[i] = theRoutes[i].strip()
+ nextHop = string.split(theRoutes[i])[0];
+ theLink = string.split(theRoutes[i])[4];
+ print "removeDefaultRouteFor:: checking",eid," against existing route:", nextHop + "->"+theLink
+ foo = re.search(nextHop, eid)
+ if ( (foo!=None) & (theLink=="default") ) :
+ # Don't call removeRoute here, we don't ever
+ # want to add the default route back in while we're removing
+ # it.
+ print "MATCH: removing default route to EID: "+eid
+ talktcl("route del "+eid+"\n")
+ return True
+ return False
+
+#
+# Check our list of destinations that can use the 'default'
+# link and add routes for any that do not have other routes
+# already in the RIB
+#
+def addDefaultRouteFor(eid):
+ if haveRouteFor(eid):
+ return(False)
+ for (theEID, theLink) in defaultRoutes:
+ if ( eid==theEID):
+ talktcl("route add "+eid+" "+theLink+"\n")
+
+#
+# Do I have a RIB entry for this EID?
+#
+def haveRIBEntryForEID(RIB, eid):
+ for entry in RIB:
+ foo = re.search(entry[RIB_EID], eid)
+ if foo!=None:
+ return True
+ return False
+
+#
+# Return True if I have a local registration for the given EID, False otherwise
+#
+def haveRegistrationForEID(eid):
+ # myEID = myLocalEID()
+
+ if string.find(myEID+"/*", eid)>=0:
+ return True
+
+ myRegistrations = getRegistrationList()
+ if myRegistrations is None:
+ return False
+ for myReg in myRegistrations:
+ if string.find(myReg+"/*", eid)>=0:
+ return True
+ return False
+
+#
+# Try to add a route to eid via tcp CL host:port
+#
+# Adds the route and a supporting link.
+#
+# Remove any existing route that uses a link called "default"
+# and replace with a link to host:port
+#
+# Don't add if we've already got a matching route for
+# the eid
+#
+# Do refresh the update time for the route
+#
+# Return the name of the link added or None
+#
+def tryAddRoute(host, port, eid):
+ theLinks, theRoutes = getLinksRoutes()
+
+ # Remove any existing route to the eid that uses a link
+ # named "default"
+ removeDefaultRouteFor(eid)
+
+ if haveRouteFor(eid):
+ return None
+
+ # print "About to check eid "+eid+" against my registrations."
+ if haveRegistrationForEID(eid):
+ return None
+
+ # See if there's an existing link we can glom onto
+ linkName = alreadyHaveLink(host+":"+port)
+ if linkName==None:
+ linkName = genNewLink(theLinks)
+ else:
+ print "Adding route to existing link:", linkName
+
+ # link add linkName host:port ONDEMAND tcp
+ print "link add ",linkName," ",host+":"+port," ONDEMAND tcp"
+ talktcl("link add "+linkName+" "+host+":"+port+" ONDEMAND tcp\n")
+
+ # route add EID linkName
+ print "route add",eid," ",linkName
+ talktcl("route add "+eid+" "+linkName+"\n")
+ return linkName
+
+#
+# Server Thread
+#
+# This will set up a server listening on a particular interface (bindInterface)
+# for messages to a particular address (listenAddress, possibly different from
+# bindInterface to support multicast), and port
+#
+# On message receipt this calls processMessage, so that we can have multiple
+# servers if need be.
+#
+def doServer(bindInterface, listenAddress, port):
+ # Set the socket parameters
+ buf = 1024
+ # addr = (listenAddress,port)
+ # list = [] # My persistent list of routes (RIB)
+
+ if bindInterface is None:
+ #intf = gethostbyname(gethostname())
+ bindInterface = INADDR_ANY
+
+ if listenAddress is None:
+ listenAddress = _DND_ADDR
+
+ if port is None:
+ port = _DND_PORT
+
+ print "doServer started on interface:", bindInterface, "address: ", listenAddress, "port: ", port
+
+ # Create socket
+ try:
+ UDPSock = socket(AF_INET,SOCK_DGRAM)
+ except:
+ print "Can't create UDP socket."
+ sys.exit(0)
+
+ #
+ # Figure out if listenAddress is multicast or not
+ #
+ if listenAddress.startswith("239."):
+ #
+ # ListenAddress IS Multicast
+ #
+ group = ('', port)
+
+ try:
+ UDPSock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
+ UDPSock.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
+ except:
+ print "Can't setsockopt SO_REUSEADDR / SO_REUSEPORT in server."
+ pass
+ # sys.exit(0)
+
+ UDPSock.setsockopt(SOL_IP, IP_MULTICAST_TTL, THE_MULTICAST_TTL)
+ UDPSock.setsockopt(SOL_IP, IP_MULTICAST_LOOP, 1)
+
+
+ try:
+ UDPSock.bind(group)
+ except:
+ # Some versions of linux raise an exception even though
+ # SO_REUSE* options have been set, so ignore it
+ print "Bind to: ", group, " failed."
+ pass
+
+ try:
+ UDPSock.setsockopt(SOL_IP, IP_MULTICAST_IF, inet_aton(bindInterface)+inet_aton('0.0.0.0'))
+ except:
+ print "Can't set IP_MULTICAST_IF on:", bindInterface
+ sys.exit(0)
+
+ try:
+ UDPSock.setsockopt(SOL_IP, IP_ADD_MEMBERSHIP, inet_aton(listenAddress) + inet_aton(bindInterface))
+ except:
+ print "Can't set IP_ADD_MEMBERSHIP for ", listenAddress
+ sys.exit(0)
+ else:
+ #
+ # ListenAddress is NOT multicast
+ #
+ #try:
+ # UDPSock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
+ #except:
+ # print "Can't set UDP socket for broadcast."
+ # sys.exit(0)
+
+ UDPSock.bind((listenAddress, listenPort))
+
+ #
+ # General Processing
+ #
+ #myEID = myLocalEID()
+
+ # Receive messages
+ while 1:
+ try:
+ data,addr = UDPSock.recvfrom(buf)
+ except:
+ "UDP recvfrom failed."
+
+ print "Got a message from: ", addr
+
+ if not data:
+ print "Client has exited!"
+ break
+ else:
+ processMessage(data, addr)
+
+ # Close socket
+ UDPSock.close()
+
+def processMessage(data, addr):
+
+ # Returns True if we got the lock, False if we didn't
+ while messageProcessingMutex.testandset() == False:
+ print "ProcessMessage is sleeping waiting on mutex..."
+ sleep(1)
+
+ #
+ # This try is here to give us an out if something causes this thread
+ # to core, so that we don't end up holding the mutex.
+ #
+ try:
+ SenderAddress = addr[0]
+ things = string.split(data, '\n')
+ SenderEID = things[0]
+ SenderListenPort = things[1]
+
+ # myEID = myLocalEID()
+
+ # Am I the sender of this message?
+ if things[0] == myEID:
+ # print "I don't process my own messages (",SenderEID,",",gethostname(),")"
+ messageProcessingMutex.unlock()
+ return
+
+ if (verbose>0):
+ print "Received message."
+
+ if (verbose>1):
+ print data,"' from addr:", addr, "\n"
+
+ if (verbose>3):
+ print "Before message processing, RIB is:"
+ printRIB(myRIB)
+
+ # For each destination EID in the message, see if I've
+ # already got a route to it or if my route is longer than
+ # the one in the message. If either of these hold, add a route
+ # via the next hop of the message.
+ # Also update RIB with entry info
+ for i in range(2, len(things)-1):
+ if (verbose>0):
+ print "Processing message element:", things[i]
+ [destEID, distance, NHEID] = string.split(things[i], " ");
+ distance = string.atoi(distance)+1
+ if (verbose>3):
+ print "Received route entry for", destEID, "from", SenderEID," ",addr[0]," ",SenderListenPort
+
+ #
+ # Split-horizon means that I shouldn't be getting advertisements of routes
+ # for which I am the next hop. We do this filtering at the receiver so that
+ # we can broadcase / multicast updates
+ #
+ if (NHEID==myEID):
+ if (verbose > 2 ):
+ print "Not processing route entry due to split horizon."
+ continue
+
+ #
+ # Check my RIB to see what my current distance to this destination EID is
+ # Also make sure that the daemon agrees that the route exists
+ #
+ myDist = currentDistanceTo(myRIB, destEID)
+ daemonHasRoute = haveNonDefaultRouteFor(destEID)
+ if (daemonHasRoute and (myDist!=None) and (myDist<=distance)):
+ # My current route is better or equal
+ if (verbose>3):
+ print "current distance", myDist," is better or equal to received distance:", distance
+
+ # If the EIDs match exactly, update the RIB entry, otherwise don't
+ # This could happen, for example, if we have a RIB entry and route to dtn://xxxxx/* and this
+ # entry is for dtn://xxxxx/ping
+ if haveExactRIBEIDMatch(myRIB, destEID):
+ refreshInternalRouteList(myRIB, SenderAddress, SenderListenPort, destEID, distance, SenderEID)
+ else:
+ # My current route entry has a higher metric or I have no current entry
+ if (myDist<99999):
+ removeRoute(destEID)
+ #talktcl("route del "+destEID+"\n")
+ removeRIBEntry(myRIB, destEID) # In case I had an entry.
+ newLinkName = tryAddRoute(SenderAddress, SenderListenPort, destEID)
+ refreshInternalRouteList(myRIB, SenderAddress, SenderListenPort, destEID, distance, SenderEID)
+
+ # Make or update a RIB entry for this route.
+
+ if (verbose>1):
+ print "After refresh RIB is:"
+ printRIB(myRIB)
+ except:
+ print "WARNING: something went wrong in processMessage..."
+ messageProcessingMutex.unlock()
+
+ messageProcessingMutex.unlock()
+
+
+def haveExactRIBEIDMatch(RIB, eid):
+ for elem in RIB:
+ if elem[RIB_EID]==eid:
+ return True
+ return False
+
+#
+# Return the current best known distance to a destination EID
+#
+def currentDistanceTo(RIB, destEID):
+ for elem in RIB:
+ if haveRIBEntryForEID(RIB, destEID):
+ return elem[RIB_DIST]
+ print "I don't have a RIB entry for:", destEID
+ return(99999)
+
+#
+# Remove an entry from the RIB table
+#
+def removeRIBEntry(RIB, destEID):
+ for elem in RIB:
+ if (elem[RIB_EID]==destEID):
+ print "Removing elem: '", elem, "' from RIB"
+ RIB.remove(elem)
+
+#
+# RIB element format described above.
+#
+# host: the IP address from which this entry was received
+# port: the port on which the sending TCPCL is listening
+# EID: A destination EID
+# distance: Distance from the sender of the update to the destination EID
+# NHEID: the EID of the node that sent the update
+#
+#
+def refreshInternalRouteList(RIB, host, port, EID, distance, NHEID):
+ found = 0;
+ if verbose>2:
+ print "Processing entry:", host," ",port," ",EID," ",distance
+
+ # If the NHEID is us, we need to NOT process this entry
+ # (split-horizon)
+ if (NHEID == myEID):
+ return RIB
+
+ for elem in RIB:
+ if ( verbose>3):
+ print "refreshInternalRouteList: checking", elem[RIB_EID]," against new entry", EID,"\n"
+ #foo = re.search(EID, elem[RIB_EID])
+ #if foo==None:
+ # continue
+ if (elem[RIB_EID]==EID):
+ elem[RIB_DIST] = distance
+ elem[RIB_TIME] = time()
+ found = 1
+
+ # If we didn't update an existing RIB entry for this destination EID
+ # make a new entry.
+ if (found == 0):
+ linkName = alreadyHaveLink(host+":"+port)
+ print "Making new RIB entry"
+ RIB += [[host, port, EID, distance, time(), linkName, NHEID]]
+
+ return RIB
+
+#
+# printRIB
+#
+def printRIB(RIB):
+ print "HOST PORT DESTEID DIST TIME LINKNAME NHEID"
+ for elem in RIB:
+ print elem[RIB_HOST]," ",elem[RIB_PORT]," ",elem[RIB_EID]," ",elem[RIB_DIST]," ",elem[RIB_TIME]," ",elem[RIB_LINKNAME]," ",elem[RIB_NHEID]
+
+#
+# Return a list of strings that are the current
+# registrations
+#
+# Return None if we can't talk to the daemon
+#
+def getRegistrationList():
+ response = talktcl("registration list\n")
+ if response==None:
+ return(None)
+ #response = string.strip(response, "dtn% registration list")
+ response = string.strip(response, "registration list")
+
+ # Split the response into lines
+ lines = string.split(response, '\n');
+
+ # Throw away the first line
+ lines = lines[1:]
+
+ # Throw away things that are not registrations
+ lines = onlyLinesContaining(lines, ["id "])
+ answer = []
+ for i in range(len(lines)):
+ temp = string.split(lines[i], " ")
+ answer += [temp[3]]
+ return answer
+
+#
+# return my local EID
+#
+def myLocalEID():
+ foo = talktcl("registration dump\n");
+ if foo==None:
+ return None
+
+ foo = string.split(foo, "\n");
+ foo = onlyLinesContaining(foo, "id 0:")
+ bar = foo[0].find('%')
+ foo = foo[0][bar+1:]
+ foo = string.split(foo)
+ return foo[3]
+
+# Figure out if the given newItem (and EID) is covered by an existing one
+# from the 'list'. The format of list is very specific to the sending client
+# (that is, list items are assumed to be (destEID, dist nheid)
+def alreadyCovered(list, newItem):
+ for listItem in list:
+ bar = string.split(listItem, " ")
+ foo = string.replace(bar[0], "?*", "\?*")
+ if re.search(foo, newItem):
+ if verbose>4:
+ print newItem, " already covered by ", bar[0]
+ return(True)
+ return False
+
+#
+# destinations is a list of ['addr', port] pairs to send messages to
+#
+# multicastInterfaces is a list of interfaces on which multicast packets will be sent
+#
+def doClient(destinations, multicastInterfaces):
+ print "doClient started with destinations: ", destinations
+ print "doClient started with multicastInterfaces: ", multicastInterfaces
+
+ group = ('', _DND_PORT)
+
+ # Create socket
+ try:
+ UDPSock = socket(AF_INET,SOCK_DGRAM)
+ except:
+ print "Can't create UDP socket."
+ sys.exit(0)
+
+ try:
+ UDPSock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
+ UDPSock.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
+ except:
+ print "Can't set UDP socket for REUSEADDR / REUSEPORT in client."
+ pass
+
+ #
+ # Need to set the socket to SO_BROADCAST in case one of the destinations
+ # is a broadcast address.
+ #
+ try:
+ UDPSock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
+ except:
+ print "Can't set UDP socket for BROADCAST."
+ sys.exit(0)
+
+ for interface in multicastInterfaces:
+ #
+ # Do these in case one of the destination addresses is multicast.
+ # IP_MULTICAST_LOOP seems to mean that I get copies of things I
+ # send.
+ #
+ try:
+ UDPSock.setsockopt(SOL_IP, IP_MULTICAST_TTL, THE_MULTICAST_TTL)
+ UDPSock.setsockopt(SOL_IP, IP_MULTICAST_LOOP, 1)
+ except:
+ print "Can't set UDP socket for IP_MULITCAST_TTL or IP_MULTICAST_LOOP."
+
+ #
+ # If the destination is multicast, I need to do this
+ #
+ try:
+ UDPSock.setsockopt(SOL_IP, IP_MULTICAST_IF, inet_aton(interface) + inet_aton('0.0.0.0'))
+ except:
+ print "Can't setsockopt SOL_MULTICAST_IF on interface: ", interface
+ pass
+
+ print "Interface: ", interface, " set for MULTICAST."
+
+ #
+ # Send messages
+ #
+ while (1):
+ # It's slightly simpler if we sleep at the top of the loop;
+ # continue's work out easier
+ sleep(broadcastInterval)
+
+ # Returns True if I got the mutex
+ while messageProcessingMutex.testandset()==False:
+ print "Client is sleeping waiting on mutex..."
+ sleep(1)
+
+ thingsSent = []
+ theList = getRegistrationList();
+ if verbose > 2:
+ print "getRegistrationList() returned:", theList
+ if theList is None:
+ # Probably couldn't talk to DTN daemon
+ messageProcessingMutex.unlock()
+ continue
+
+ if addLocalEIDWildcard==1:
+ # Build a message that contains my IP address and port,
+ # plus the list of registrations
+ thingsSent += [myEID+"/* 0 "+myEID]
+
+ #
+ # Remove any duplication in building thingsSent list
+ #
+ isAlreadyThere = 0
+ # For each registration
+ for listEntry in theList:
+ # Check against each entry already in the 'to be sent' list
+ tempEntry = string.replace(listEntry, "?*", "\?*")
+
+ if alreadyCovered(thingsSent, tempEntry):
+ continue
+ # OK, need to send this
+ # Local registrations are at distance 0
+ thingsSent += [listEntry+" 0 "+myEID]
+
+ #
+ #
+ #
+ if rebroadcastRoutes:
+ for entry in myRIB:
+ thingsSent += [entry[RIB_EID]+" "+str(entry[RIB_DIST])+" "+entry[RIB_NHEID]]
+
+ #
+ # Now build the text string to send
+ #
+ msg = myEID+'\n'
+ msg += myListeningDTNTCPPort
+ msg += '\n'
+ for entry in thingsSent:
+ msg += entry
+ msg += "\n"
+ if ( verbose>0 ):
+ print "msg to send is:"
+ print msg
+
+ # Send to desired addresses
+ for addr,port in destinations:
+ print "Sending msg to: ", addr,":", port
+ try:
+ if(UDPSock.sendto(msg,(addr, port))):
+ msg = msg
+ except:
+ print "Error sending message to:", addr
+ print os.strerror("Error sending message to")
+
+ timeOutOldRoutes(myRIB)
+
+ #
+ # Unlock the mutex
+ #
+ messageProcessingMutex.unlock()
+
+ # Close socket
+ UDPSock.close()
+
+def installDefaultRoutes():
+ for (eid,linkName) in defaultRoutes:
+ if ( haveRouteFor(eid)==False ):
+ talktcl("route add "+eid+" "+linkName+"\n")
+
+def removeExistingRoutes():
+ theLinks, theRoutes = getLinksRoutes()
+ for route in theRoutes:
+ tokens = string.split(route)
+ talktcl("route del "+tokens[0]+"\n")
+ return
+
+
+def usage():
+ print "dnd.py [-h] [-s] [-c] [-b PORT] [-t PORT] [-L seeBelow] [-d] [-r] [addr,[port]] [addr...]"
+ print " -h: Print usage information (this)"
+ print " -s: Only perform server (receiving) actions"
+ print " -c: Only perform client (transmitting) actions"
+ print " -t #: Set the DTN Tcl Console Port ("+str(dtnTclConsolePort)+")"
+ print " -L intf,addr:port Add a listening socket on the given address and"
+ print " port. If the address is multicast, then the interface "
+ print " needs to be given as well. Possible syntaxes for the"
+ print " argument of '-L' are:"
+ print " '-L port' -- Use INADDR_ANY as the listen address"
+ print " '-L intf,addr:port' -- Bind to the given interface,"
+ print " listening on a particular address/port --"
+ print " This is useful for multicast."
+ print " If you insist on binding to a particular interface"
+ print " without using multicast, use the interface address"
+ print " for both the intf and addr parts."
+ print " -d MDIST Set the MULTICAST_TTL to MDIST (default ",THE_MULTICAST_TTL,")"
+ print " -m intf: Add interface to the list of multicast SENDING"
+ print " interfaces."
+ print " -r: Include route information in addition to (local) registration"
+ print " information. This makes neighbor discovery into a"
+ print " really stupid routing algorithm, but possibly suitable"
+ print " for small lab setups (like several dtn routes in a"
+ print " linear topology)."
+ print " addrs are addresses to which UDP packets should be sent"
+ print " default:", myBroadcast()
+ print " ports are the destination ports for the addresses."
+ print " default:", _DND_PORT
+ print " -R Remove existing routes and exit."
+ print " "
+ print " "
+ print "Examples:"
+ print "These examples assume that 10.9.1.1/24 is a local interface."
+ print ""
+ print "=================="
+ print "Start dnd.py listening on port 5005 and sending to a broadcast address"
+ print ""
+ print "dnd.py -L 5005 10.9.1.255"
+ print ""
+ print "=================="
+ print "Start dnd.py listening on port 5005 and sending to a remote subnet broadcast address"
+ print "(10.10.4.255) and a remote unicast address (10.10.5.17)"
+ print ""
+ print "dnd.py -L 5005 10.10.4.255 10.10.5.17"
+ print ""
+ print "=================="
+ print "Start dnd.py listening for multicast packets and transmitting to a multicast address"
+ print ""
+ print "./dnd.py -L 10.9.1.1:239.0.1.99:5005 -m 10.9.1.1 239.0.1.99:5005"
+ print ""
+ print "=================="
+ print "Start dnd.py listening for multicast packets to group 239.0.1.99 on interface 10.9.1.1 and for"
+ print "regular packets on port 5001. Interface 10.9.3.2 is a multicast sendint interface, and we're "
+ print "going to send to the multicast group 239.0.1.99 as well as to 10.9.2.255 port 5001"
+ print ""
+ print "dnd.py -L 10.9.1.1,239.0.1.99:5005 -L 5001 -m 10.9.3.2 239.0.1.99 10.9.2.255:5001"
+ print ""
+
+
+if __name__ == '__main__':
+ print "argv is:", sys.argv, "[", len(sys.argv), "]"
+ serverOn = True
+ clientOn = True
+ listenAddress = _DND_ADDR
+ bindInterface = INADDR_ANY
+ destinations = [] # list of [addr, port] pairs
+ multicastSendInterfaces = [] # interfaces on which I may want to SEND MC packets
+ listenThings = [] # where I listen for messages
+
+ print "This is dnd.py version 1.0"
+
+ #
+ # Read DTND configuration information
+ #
+ myEID = myLocalEID()
+ print "myEID is: ",myEID
+ if myEID==None:
+ print "Can't get local EID. exiting"
+ sys.exit(-1)
+
+ myListeningDTNTCPPort = findListeningPort()
+ if myListeningDTNTCPPort == None:
+ print "Can't find listening port for TCP CL, client exiting."
+ sys.exit(-1)
+
+ sendToPort = _DND_PORT
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "L:m:l:b:rd:t:hI:scvR", ["help", "server", "client"])
+ except getopt.GetoptError:
+ usage()
+ sys.exit(2)
+
+ for o, a in opts:
+ if o == "-h":
+ usage();
+ sys.exit(2)
+ if o == "-v":
+ verbose += 1
+ if o == "-s":
+ clientOn = False
+ if o == "-c":
+ serverOn = False
+ if o == '-d':
+ THE_MULTICAST_TTL = string.atoi(a)
+ if o == "-L":
+ # Possible syntaxes:
+ # bindInterface,address:port -- Multicast
+ # bindInterface,address -- Multicast
+ # port
+ print "-L is working on :", a
+ foo = string.split(a, ':')
+ if len(foo)==2:
+ listenPort = foo[1]
+
+ # foo[0] is empty,, intf, addr,, or just a port
+
+ bar = string.split(foo[0], ',')
+ if len(bar)==1:
+ # just a port
+ listenPort = bar[0]
+ bindInterface = '0.0.0.0'
+ listenAddress = '0.0.0.0'
+ else:
+ # Assuming bindInterface,address syntax at this point
+ bindInterface = bar[0]
+ listenAddress = bar[1]
+
+ listenThings += [[bindInterface, listenAddress, string.atoi(listenPort)]]
+ if o == "-b":
+ sendToPort = a
+ if o == "-m":
+ multicastSendInterfaces += [a]
+ if o == "-r":
+ rebroadcastRoutes = 1
+ if o == "-t":
+ dtnTclConsolePort = a
+ if o == "-R":
+ removeExistingRoutes()
+ sys.exit(2)
+
+ print "rest of args is now:", args
+
+ #
+ # Process destination addresses (where I send to)
+ #
+ for item in args:
+ foo = string.split(item, ':')
+ if len(foo)==1:
+ # No port information given, use default
+ theAddr = foo[0]
+ thePort = str(sendToPort)
+ else:
+ theAddr = foo[0]
+ thePort = foo[1]
+
+ destinations += [[theAddr, string.atoi(thePort)]]
+ print "Destinations now: ", destinations
+
+ if len(destinations)==0:
+ sendToAddress = myBroadcast()
+ destinations = [[sendToAddress[0], sendToPort]]
+
+ if len(listenThings)==0:
+ listenThings = [['0.0.0.0', '0.0.0.0', sendToPort]]
+
+ print " "
+ print "================================"
+ print " "
+ print "Destinations now: ", destinations
+ print " "
+ print "ListenThings now: ", listenThings
+
+ removeExistingRoutes()
+
+ installDefaultRoutes()
+
+ if clientOn:
+ thread.start_new(doClient, (destinations, multicastSendInterfaces))
+ if serverOn:
+ for bindInterface, listenAddress, listenPort in listenThings:
+ thread.start_new(doServer, (bindInterface, listenAddress, listenPort))
+
+ # Now I just sort of hang out...
+ while 1:
+ sleep(10)
+