Advertisement
steffann

wait_for_dad

Mar 22nd, 2014
520
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.42 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. # Some code borrowed from http://partiallystapled.com/~gxti/tools/netlink.py
  4.  
  5. from collections import namedtuple
  6. from contextlib import contextmanager
  7.  
  8. import socket
  9. import struct
  10.  
  11. # The value of the tentative flag
  12. IFA_F_TENTATIVE = 0x40
  13.  
  14. # Netlink message types
  15. NLMSG_NOOP = 1
  16. NLMSG_ERROR = 2
  17. NLMSG_DONE = 3
  18. NLMSG_OVERRUN = 4
  19.  
  20. # Netlink routing message types
  21. RTM_NEWADDR = 20
  22. RTM_GETADDR = 22
  23.  
  24. # Flags values
  25. NLM_F_REQUEST = 1
  26. NLM_F_MULTI = 2
  27. NLM_F_ACK = 4
  28. NLM_F_ECHO = 8
  29. NLM_F_DUMP_INTR = 16
  30.  
  31. # Modifiers to GET request
  32. NLM_F_ROOT = 0x100
  33. NLM_F_MATCH = 0x200
  34. NLM_F_ATOMIC = 0x400
  35. NLM_F_DUMP = (NLM_F_ROOT | NLM_F_MATCH)
  36.  
  37. # Attributes
  38. IFA_ADDRESS = 1
  39.  
  40. # Data structures
  41. STRUCT_NLMSGHDR = 'IHHII'
  42. STRUCT_IFADDRMSG = '4BI'
  43. STRUCT_RTATTR = 'HH'
  44.  
  45.  
  46. @contextmanager
  47. def socketcontext(*args, **kw):
  48.   sock = socket.socket(*args, **kw)
  49.   yield sock
  50.   sock.close()
  51.  
  52.  
  53. def netlink_pack(msgtype, flags, seq, pid, data):
  54.   '''
  55.  Pack a single netlink message
  56.  '''
  57.   return struct.pack(STRUCT_NLMSGHDR, 16 + len(data),
  58.                      msgtype, flags, seq, pid) + data
  59.  
  60.  
  61. def netlink_unpack(data):
  62.   '''
  63.  Unpack a sequence of netlink packets.
  64.  '''
  65.   out = []
  66.   while data:
  67.     length, msgtype, flags, seq, pid = struct.unpack(STRUCT_NLMSGHDR, data[:16])
  68.     assert len(data) >= length
  69.  
  70.     out.append((msgtype, flags, seq, pid, data[16:length]))
  71.     data = data[length:]
  72.    
  73.   return out
  74.  
  75.  
  76. def rtattr_unpack(data):
  77.   """Unpack a sequence of netlink attributes."""
  78.   size = struct.calcsize(STRUCT_RTATTR)
  79.   attrs = {}
  80.   while data:
  81.     rta_len, rta_type = struct.unpack(STRUCT_RTATTR, data[:size])
  82.     assert len(data) >= rta_len
  83.  
  84.     rta_data = data[size:rta_len]
  85.     padded = ((rta_len + 3) / 4) * 4
  86.     attrs[rta_type] = rta_data
  87.  
  88.     data = data[padded:]
  89.  
  90.   return attrs
  91.  
  92.  
  93. def get_ipv6_address_flags():
  94.   '''
  95.  The main function to get the flags for all IPv6 addresses
  96.  '''
  97.   with socketcontext(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE) as sock:
  98.     seq = 1
  99.    
  100.     req_data = struct.pack('Bxxx', socket.AF_INET6)
  101.     msg = netlink_pack(RTM_GETADDR, NLM_F_REQUEST | NLM_F_DUMP, seq, 0, req_data)
  102.     sock.send(msg)
  103.    
  104.     addr_flags = {}
  105.     while True:
  106.       msgs = sock.recv(262144)
  107.       for msgtype, flags, mseq, pid, data in netlink_unpack(msgs):
  108.         # Check sequence number
  109.         if mseq != seq:
  110.           raise RuntimeError("Netlink message sequence mismatch")
  111.          
  112.         # Handle different message types
  113.         if msgtype == NLMSG_DONE:
  114.           # The end
  115.           return addr_flags
  116.          
  117.         elif msgtype in (NLMSG_ERROR, NLMSG_OVERRUN):
  118.           # Bad stuff happened
  119.           raise RuntimeError("Netlink error")
  120.          
  121.         elif msgtype == RTM_NEWADDR:
  122.           # Decode the address info
  123.           size = struct.calcsize(STRUCT_IFADDRMSG)
  124.           family, prefixlen, flags, scope, index = struct.unpack(STRUCT_IFADDRMSG, data[:size])
  125.           attrs = rtattr_unpack(data[size:])
  126.          
  127.           # Get the address from the attributes
  128.           addr = attrs.get(IFA_ADDRESS)
  129.          
  130.           # Store the flags
  131.           addr_flags[addr] = flags
  132.          
  133.         else:
  134.           # Ignore messages we don't understand
  135.           pass
  136.  
  137. def ipv6_address(addr_str):
  138.   '''
  139.  Simple wrapper around socket.inet_pton() so that we can use it in argparse
  140.  '''
  141.   try:
  142.     return socket.inet_pton(socket.AF_INET6, addr_str)
  143.   except socket.error:
  144.     raise ValueError("Invalid IPv6 address: {}".format(addr_str))
  145.  
  146.  
  147. def main(argv):
  148.   '''
  149.  Command line interface to wait for DAD to complete on IPv6 addresses
  150.  '''
  151.   import argparse
  152.   import time
  153.  
  154.   # Parse arguments
  155.   parser = argparse.ArgumentParser(description="Wait for DAD to complete on IPv6 addresses")
  156.   parser.add_argument('-d', '--debug', action='store_true', help="Show debugging output")
  157.   parser.add_argument('-t', '--timeout', metavar='SEC', type=int, default=10, help="Timeout in case addresses don't leave tentative state")
  158.   parser.add_argument('addresses', metavar='addr', nargs='+', type=ipv6_address, help="IPv6 address to wait for")
  159.   options = parser.parse_args(argv)
  160.  
  161.   # Show what we are going to do
  162.   if options.debug:
  163.     print("Waiting for the following IPv6 addresses:")
  164.     for addr in options.addresses:
  165.       addr_str = socket.inet_ntop(socket.AF_INET6, addr)
  166.       print("- {}".format(addr_str))
  167.  
  168.     print("Using a timeout of {} seconds".format(options.timeout))
  169.  
  170.   # Main loop
  171.   still_waiting = True
  172.   deadline = time.time() + options.timeout
  173.   while still_waiting and time.time() < deadline:
  174.     # Be optimistic: assume we are done waiting
  175.     # We'll change this when we encounter an address in tentative state
  176.     still_waiting = False
  177.    
  178.     # Get the flags of all IPv6 addresses
  179.     try:
  180.       addr_flags = get_ipv6_address_flags()
  181.     except:
  182.       print("Unable to get address information, aborting!")
  183.       return False
  184.    
  185.     # Show what we found
  186.     if options.debug:
  187.       print("We found the following addresses:")
  188.       for addr in addr_flags:
  189.         addr_str = socket.inet_ntop(socket.AF_INET6, addr)
  190.         print("- {}: Flags {:#x}".format(addr_str, addr_flags[addr]))
  191.  
  192.       if not addr_flags:
  193.         print("- None found")
  194.    
  195.     # Check the flags
  196.     for addr in options.addresses:
  197.       # Check if the given addresses even exist
  198.       if addr not in addr_flags:
  199.         addr_str = socket.inet_ntop(socket.AF_INET6, addr)
  200.         print("Address {} is not present on this system".format(addr_str))
  201.         return False
  202.      
  203.       # Check the tentative flag
  204.       if addr_flags[addr] & IFA_F_TENTATIVE:
  205.         # Still tentative, we keep waiting
  206.         if options.debug:
  207.           addr_str = socket.inet_ntop(socket.AF_INET6, addr)
  208.           print("Address {} is still tentative, keep waiting".format(addr_str))
  209.          
  210.         still_waiting = True
  211.       elif options.debug:
  212.         addr_str = socket.inet_ntop(socket.AF_INET6, addr)
  213.         print("Address {} is not tentative, ok!".format(addr_str))
  214.        
  215.       # Sleep a bit
  216.       time.sleep(0.1)
  217.  
  218.   # Check state
  219.   if still_waiting:
  220.     print("One or more addresses are still tentative after {} seconds, aborting".format(options.timeout))
  221.     return False
  222.  
  223.   # Done
  224.   return True
  225.  
  226.        
  227. if __name__ == '__main__':
  228.   import sys
  229.   ok = main(sys.argv[1:])
  230.   if not ok:
  231.     sys.exit(1)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement