dnd.py

Go to the documentation of this file.
00001 #!/usr/bin/python
00002 
00003 # DTN Neighbor Discovery (over UDP Broadcast) -- A small python script
00004 # that will propagate DTN registration information via UDP broadcasts.
00005 #
00006 # Written by Keith Scott, The MITRE Corporation
00007 
00008 # I got tired of having to manually configure dtn daemons, expecially
00009 # since the combination of the windows operating system and MITRE's
00010 # dhcp/dynamic DNS caused machine names/addresses to change
00011 # when least convenient.
00012 
00013 # This script will populate the static routing tables of DTN daemons
00014 # with registrations (and optionally routes) it hears from its peers.
00015 # When advertising my local
00016 # registrations, I append "/*" to the local EID and prune anything
00017 # that would match on this route.  This way if I'm running something
00018 # like bundlePing that generates 'transient' registrations,
00019 # I don't end up cluttering up everybody else's tables with 'em.
00020 
00021 # This script assumes that all machines use TCP convergence layers to
00022 # communicate
00023 
00024 # This script transmits UDP broadcast messages to a particular port
00025 # (5005 by default).  You'll need to open up firewalls to let this
00026 # traffic through.
00027 
00028 # The UDP Messages sent are of the form:
00029 #
00030 #       my DTN local EID
00031 #       my TCP CL Listen Port
00032 #       registration/route 1
00033 #       registration/route 2
00034 #       ...
00035 
00036 # The myHostname used to be used by the receiver to instantiate (TCP)
00037 # convergence-layer routes back to the sender.  It is now mainly just
00038 # a tag to keep a sender from processing its own messages (receivers
00039 # use the sender's IP address from received packets to set the CL
00040 # destination for injected routes).
00041 
00042 from socket import *
00043 from time import *
00044 import os
00045 import random
00046 import string
00047 import thread
00048 import re
00049 import getopt
00050 import sys
00051 
00052 theBroadcastAddress = []
00053 theBroadcastPort = 5005                         # Port to which reg info is sent
00054 dtnTclConsolePort = 5050                        # Port on which DTN tcl interpreter is listening
00055 
00056 broadcastInterval = 10 # How often to broadcast, in seconds
00057 
00058 #
00059 # Send a message to the dtn tcl interpreter and return results
00060 #
00061 #
00062 def talktcl(sent):
00063         received = 0
00064         # print "Opening connection to dtnd tcl interpreter."
00065         sock = socket(AF_INET, SOCK_STREAM)
00066         try:
00067                         sock.connect(("localhost", dtnTclConsolePort))
00068         except:
00069                 print "Connection failed"
00070                 sock.close()
00071                 return None
00072 
00073         messlen, received = sock.send(sent), 0
00074         if messlen != len(sent):
00075                 print "Failed to send complete message to tcl interpreter"
00076         else:
00077                 # print "Message '",sent,"' sent to tcl interpreter."
00078                 messlen = messlen
00079 
00080         data = ''
00081         while 1:
00082                 promptsSeen = 0
00083                 data += sock.recv(32)
00084                 #sys.stdout.write(data)
00085                 received += len(data)
00086                 # print "Now received:", data
00087                 # print "checking for '%' in received data stream [",received,"], ", len(data)
00088                 for i in range(len(data)):
00089                         if data[i]=='%':
00090                                 promptsSeen = promptsSeen + 1
00091                         if promptsSeen>1:
00092                                 break;
00093                 if promptsSeen>1:
00094                         break;
00095 
00096         # print "talktcl received: ",data," back from tcl.\n"
00097 
00098         sock.close()
00099         return(data);
00100         
00101 #
00102 # Return the port on which the TCP convergence layer is listening
00103 #
00104 def findListeningPort():
00105         response = talktcl("interface list\n")
00106         if response==None:
00107                 return None
00108 
00109         lines = string.split(response, "\n")
00110         for i in range(len(lines)):
00111                 if string.find(lines[i], "Convergence Layer: tcp")>=0:
00112                         words = string.split(lines[i+1])
00113                         return(words[3])
00114         return None
00115 
00116 #
00117 # Munge the list 'lines' to contain only entries
00118 # that contain (in the re.seach sense) at least
00119 # one of the keys
00120 #
00121 def onlyLinesContaining(lines, keys):
00122         answer = []
00123         for theLine in lines:
00124                 for theKey in keys:
00125                         if re.search(theKey, theLine):
00126                                         answer += [theLine]
00127                                         break;
00128         return answer
00129 
00130 #
00131 # Generate a random string containing letters and digits
00132 # of specified length
00133 #
00134 def generateRandom(length):
00135         chars = string.ascii_letters + string.digits
00136         return(''.join([random.choice(chars) for i in range(length)]))
00137 
00138 #
00139 # Generate a new unique link identifier
00140 #
00141 def genNewLink(linkList):
00142         done = False
00143         print "genNewLink: ", linkList
00144         while done==False:
00145                 test = generateRandom(4)
00146                 if len(linkList)>0:
00147                         for i in range(len(linkList)):
00148                                 words = string.split(linkList[i], " ");
00149                                 if words[4]!=test:
00150                                         done = True
00151                                         break;
00152                 else:
00153                         done = True
00154         return test
00155 
00156 
00157 #
00158 # Return a pair of lists: the current links and the current
00159 # routes from the DTN daemon
00160 #
00161 def getLinksRoutes():
00162         myRoutes = talktcl("route dump\n")
00163         if myRoutes==None:
00164                 print "tryAddRoute: can't talk to dtn daemon"
00165                 return([[],[]])
00166         myRoutes = string.strip(myRoutes, "dtn% ")
00167 
00168         # Split the response into lines
00169         lines = string.split(myRoutes, '\n');
00170         
00171         theRoutes = []
00172         theLinks = []
00173 
00174         # Find the routes
00175         for i in range(1,len(lines)):
00176                 if string.find(lines[i], "Links")>=0:
00177                         break
00178                 else:
00179                         if lines[i]=="\r":
00180                                 i = i
00181                         else:
00182                                 theRoutes += [lines[i]]
00183 
00184         # Find the links
00185         if len(lines)>i+2:
00186                 for j in range(i+1, len(lines)-2):
00187                         theLinks += [lines[j]]
00188 
00189         return([theLinks, theRoutes])
00190 
00191 # Return the link name of an existing link, or None
00192 def alreadyHaveLink(theLinks, newLink):
00193         for testLink in theLinks:
00194                 # This isn't really right, but it's close
00195                 if string.find(testLink, newLink)>=0:
00196                         testLink = string.split(testLink)
00197                         return testLink[1]
00198         return None
00199 
00200 def myBroadcast():
00201         answer = []
00202         myaddrs = os.popen("/sbin/ip addr show").read()
00203         myaddrs = string.split(myaddrs, "\n")
00204 
00205         myaddrs = onlyLinesContaining(myaddrs, ["inet.*brd"])
00206 
00207         for addr in myaddrs:
00208                 words = string.split(addr)
00209                 for i in range(len(words)):
00210                         if words[i]=="brd":
00211                                 answer += [words[i+1]]
00212 
00213         return answer
00214 
00215 #
00216 # Try to add a route to eid via tcp CL host:port
00217 # Don't add if we've already got a matching route for
00218 # the eid
00219 #
00220 def tryAddRoute(host, port, eid):
00221         # print "tryAddRoute(",host,",",port,",",eid,")"
00222 
00223         myRegistrations = getRegistrationList()
00224         theLinks, theRoutes = getLinksRoutes()
00225 
00226         # print "About to check eid "+eid+" against existing routes [",len(theRoutes),"]"
00227         if len(theRoutes)>0:
00228                 for i in range(len(theRoutes)):
00229                         theRoutes[i] = theRoutes[i].strip()
00230                         nextHop = string.split(theRoutes[i])[0];
00231                         # print "Checking",eid," against existing route:", nextHop
00232                         foo = re.search(nextHop, eid)
00233                         if foo==None:
00234                                 foo = foo
00235                         else:
00236                         #if theRoutes[i].find(eid)>=0:
00237                                 #print "Existing route found, not adding route for: "+eid
00238                                 return
00239 
00240         # print "About to check eid "+eid+" against my registrations."
00241         for myReg in myRegistrations:
00242                 if string.find(myReg+"/*", eid)>=0:
00243                         return
00244 
00245         # OK, we need to add a new route entry.  Start by getting
00246         # a new unique link name
00247         print "Adding new link/route"
00248         print theLinks
00249         print theRoutes
00250 
00251         # See if there's an existing link we can glom onto
00252         linkName = alreadyHaveLink(theLinks, host+":"+port)
00253         if linkName==None:
00254                 linkName = genNewLink(theLinks)
00255         else:
00256                 print "Adding route to existing link:", linkName
00257 
00258         # link add linkName host:port ONDEMAND tcp
00259         print "link add ",linkName," ",host+":"+port," ONDEMAND tcp"
00260         talktcl("link add "+linkName+" "+host+":"+port+" ONDEMAND tcp\n")
00261         
00262         # route add EID linkName
00263         print "route add",eid," ",linkName
00264         talktcl("route add "+eid+" "+linkName+"\n")
00265         return
00266 
00267 #
00268 # Server Thread
00269 #
00270 def doServer(host, port):
00271 # Set the socket parameters
00272         buf = 1024
00273         addr = (host,port)
00274 
00275         print "doServer started on host:", host, "port: ", port
00276 
00277         # Create socket and bind to address
00278         try:
00279                 UDPSock = socket(AF_INET,SOCK_DGRAM)
00280         except:
00281                 print "Can't create UDP socket."
00282                 sys.exit(0)
00283         try:
00284                 UDPSock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
00285         except:
00286                 print "Can't set UDP socket for broadcast."
00287                 sys.exit(0)
00288 
00289         UDPSock.bind(addr)
00290 
00291         myEID = myLocalEID()
00292 
00293         # Receive messages
00294         while 1:
00295                 try:
00296                         data,addr = UDPSock.recvfrom(buf)
00297                 except:
00298                         "UDP recvfrom failed."
00299 
00300                 if not data:
00301                         print "Client has exited!"
00302                         break
00303                 else:
00304                         print "\nReceived message '", data,"' from addr:", addr
00305                         things = string.split(data, '\n')
00306                         # Am I the sender of this message?
00307                         if things[0] == myEID:
00308                                 print "I don't process my own messages (",things[0],",",gethostname(),")"
00309                                 continue
00310                         # For each registration in the message, see if I've
00311                         # already got one and if not, add a route
00312                         for i in range(2, len(things)-1):
00313                                 tryAddRoute(addr[0], things[1], things[i])  # host, port, EID
00314 
00315         # Close socket
00316         UDPSock.close()
00317 
00318 #
00319 # Return a list of strings that are the current
00320 # registrations
00321 #
00322 def getRegistrationList():
00323         response = talktcl("registration list\n")
00324         if response==None:
00325                 return(())
00326         response = string.strip(response, "dtn% ")
00327 
00328         # Split the response into lines
00329         lines = string.split(response, '\n');
00330 
00331         # Throw away things that are not registrations
00332         lines = onlyLinesContaining(lines, ["id "])
00333         answer = []
00334         for i in range(len(lines)):
00335                         temp = string.split(lines[i], " ")
00336                         answer += [temp[3]]
00337         return answer
00338 
00339 #
00340 # return my local EID
00341 #
00342 def myLocalEID():
00343         foo = talktcl("registration dump\n");
00344         if foo==None:
00345                 return None
00346 
00347         foo = string.strip(foo, "dtn% ")
00348         foo = string.split(foo, "\n");
00349         foo = onlyLinesContaining(foo, "id 0:")
00350         foo = string.split(foo[0])
00351         return foo[3]
00352         
00353 def alreadyCovered(list, newItem):
00354         for listItem in list:
00355                         if re.search(newItem, listItem):
00356                                         return(True)
00357         return False
00358 
00359 def doClient(sendToAddresses, port):
00360         print "doClient thread started with sendToAddresses:", sendToAddresses, ", port:", port
00361 
00362         # Create socket
00363         try:
00364                 UDPSock = socket(AF_INET,SOCK_DGRAM)
00365         except:
00366                 print "Can't create UDP socket."
00367                 sys.exit(0)
00368         try:
00369                 UDPSock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
00370         except:
00371                 print "Can't set UDP socket for broadcast."
00372                 sys.exit(0)
00373 
00374         myListenPort = findListeningPort()
00375         if myListenPort == None:
00376                 print "Can't find listening port for TCP CL, client exiting."
00377                 return
00378 
00379         myEID = myLocalEID()
00380         if myEID==None:
00381                 print "Can't get local EID.  exiting"
00382                 sys.exit(-1)
00383                 
00384         print "client local EID is:", myEID
00385 
00386         # Send messages
00387         while (1):
00388                 thingsSent = []
00389                 theList = getRegistrationList();
00390                 print "getRegistrationList() returned:", theList
00391 
00392                 if len(theList)==0:
00393                         print "Can't find registrations"
00394                 else:
00395                         # Build a message that contains my IP address and port,
00396                         # plus the list of registrations
00397                         thingsSent += [myEID+"/*"]
00398                         for listEntry in theList:
00399                                 if alreadyCovered(thingsSent, listEntry):
00400                                         continue
00401                                 else:
00402                                         thingsSent += [listEntry]
00403 
00404                         if True:
00405                                 theLinks, theRoutes = getLinksRoutes()
00406                                 for route in theRoutes:
00407                                         route = string.split(route)
00408                                         route = route[0]
00409                                         if alreadyCovered(thingsSent, route):
00410                                                 continue
00411                                         else:
00412                                                 thingsSent += [route]
00413 
00414                         # Now build the text string to send
00415                         msg = myEID+'\n'
00416                         msg += myListenPort
00417                         msg += '\n'
00418                         for entry in thingsSent:
00419                                         msg += entry
00420                                         msg += "\n"
00421                         print "msg to send is:",msg
00422 
00423                         # Send to desired addresses
00424                         for addr in sendToAddresses:
00425                                         try:
00426                                                 if(UDPSock.sendto(msg,(addr, port))):
00427                                                         msg = msg
00428                                         except:
00429                                                 print "Error sending message to:", addr
00430                                                 print os.strerror()
00431                 sleep(broadcastInterval)
00432 
00433         # Close socket
00434         UDPSock.close()
00435 
00436 
00437 def usage():
00438         print "dnd.py [-h] [-s] [-c] [-b PORT] [-t PORT] [-r] [addr] [addr...]"
00439         print "  -h:   Print usage information (this)"
00440         print "  -s:   Only perform server (receiving) actions"
00441         print "  -c:   Only perform client (transmitting) actions"
00442         print "  -b #: Set the port for dnd UDP messaging ("+str(theBroadcastPort)+")"
00443         print "  -t #: Set the DTN Tcl Console Port ("+str(dtnTclConsolePort)+")"
00444 #       print "  -d addr: Add a destination addr to transmit info to"
00445 #       print "           (subnet broadcasts work well here)"
00446 #       print "           Current default is:", myBroadcast()
00447         print "  -r:   Include route information in addition to (local) registration"
00448         print "           information.  This makes neighbor discovery into a"
00449         print "           really stupid routing algorithm, but possibly suitable"
00450         print "           for small lab setups (like several dtn routes in a"
00451         print "           linear topology).  Note that this is NOT EVEN as"
00452         print "           sophisticated as RIP (there's no 'distance')."
00453         print "  addrs are addresses to which UDP packets should be sent"
00454         print "        default:", myBroadcast()
00455 
00456 if __name__ == '__main__':
00457         print "argv is:", sys.argv, "[", len(sys.argv), "]"
00458         serverOn = True
00459         clientOn = True
00460         
00461         try:
00462                         opts, args = getopt.getopt(sys.argv[1:], "d:b:t:hsc", ["help", "server", "client"])
00463         except getopt.GetoptError:
00464                 usage()
00465                 sys.exit(2)
00466 
00467         for o, a in opts:
00468                 if o == "-h":
00469                         usage();
00470                         sys.exit(2)
00471                 if o == "-s":
00472                         clientOn = False
00473                 if o == "-d":
00474                         theBroadcastAddress += [a]
00475                 if o == "-c":
00476                         serverOn = False
00477                 if o == "-b":
00478                         theBroadcastPort = a
00479                 if o == "-t":
00480                         dtnTclConsolePort = a
00481 
00482         print "rest of args is now:", args
00483 
00484         if len(theBroadcastAddress)==0:
00485                 #theBroadcastAddress = ['<broadcast>']
00486                 theBroadcastAddress = myBroadcast()
00487                 if len(theBroadcastAddress)==0:
00488                         print "I don't have anybody to broadcast to."
00489                         sys.exit(0)
00490                 print "I figure to transmit to:", theBroadcastAddress
00491 
00492         if clientOn:
00493                 thread.start_new(doClient, (theBroadcastAddress, theBroadcastPort))
00494         if serverOn:
00495                 thread.start_new(doServer, ("", theBroadcastPort))
00496 
00497         # Now I just sort of hang out...
00498         while 1:
00499                 sleep(10)
00500 

Generated on Fri Dec 22 14:47:58 2006 for DTN Reference Implementation by  doxygen 1.5.1