dnd.py

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

Generated on Thu Jun 7 12:54:26 2007 for DTN Reference Implementation by  doxygen 1.5.1