Lease File Parsing Madness
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

leases.py 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. #!/usr/bin/python
  2. import datetime, bisect
  3. from pprint import pprint
  4. def parse_timestamp(raw_str):
  5. tokens = raw_str.split()
  6. if len(tokens) == 1:
  7. if tokens[0].lower() == 'never':
  8. return 'never';
  9. else:
  10. raise Exception('Parse error in timestamp')
  11. elif len(tokens) == 3:
  12. return datetime.datetime.strptime(' '.join(tokens[1:]),
  13. '%Y/%m/%d %H:%M:%S')
  14. else:
  15. raise Exception('Parse error in timestamp')
  16. def timestamp_is_ge(t1, t2):
  17. if type(t2) == type(""):
  18. return False
  19. if t1 == 'never':
  20. return True
  21. elif t2 == 'never':
  22. return False
  23. else:
  24. return t1 >= t2
  25. def timestamp_is_lt(t1, t2):
  26. if type(t2) == type(""):
  27. return t1 != 'never'
  28. if t1 == 'never':
  29. return False
  30. elif t2 == 'never':
  31. return t1 != 'never'
  32. else:
  33. return t1 < t2
  34. def timestamp_is_between(t, tstart, tend):
  35. return timestamp_is_ge(t, tstart) and timestamp_is_lt(t, tend)
  36. def parse_hardware(raw_str):
  37. tokens = raw_str.split()
  38. if len(tokens) == 2:
  39. return tokens[1]
  40. else:
  41. raise Exception('Parse error in hardware')
  42. def strip_endquotes(raw_str):
  43. return raw_str.strip('"')
  44. def identity(raw_str):
  45. return raw_str
  46. def parse_binding_state(raw_str):
  47. tokens = raw_str.split()
  48. if len(tokens) == 2:
  49. return tokens[1]
  50. else:
  51. raise Exception('Parse error in binding state')
  52. def parse_next_binding_state(raw_str):
  53. tokens = raw_str.split()
  54. if len(tokens) == 3:
  55. return tokens[2]
  56. else:
  57. raise Exception('Parse error in next binding state')
  58. def parse_rewind_binding_state(raw_str):
  59. tokens = raw_str.split()
  60. if len(tokens) == 3:
  61. return tokens[2]
  62. else:
  63. raise Exception('Parse error in next binding state')
  64. def parse_leases_file(leases_file):
  65. valid_keys = {
  66. 'starts': parse_timestamp,
  67. 'ends': parse_timestamp,
  68. 'tstp': parse_timestamp,
  69. 'tsfp': parse_timestamp,
  70. 'atsfp': parse_timestamp,
  71. 'cltt': parse_timestamp,
  72. 'hardware': parse_hardware,
  73. 'binding': parse_binding_state,
  74. 'next': parse_next_binding_state,
  75. 'rewind': parse_rewind_binding_state,
  76. 'uid': strip_endquotes,
  77. 'client-hostname': strip_endquotes,
  78. 'option': identity,
  79. 'set': identity,
  80. 'on': identity,
  81. 'abandoned': None,
  82. 'bootp': None,
  83. 'reserved': None,
  84. }
  85. leases_db = {}
  86. lease_rec = {}
  87. in_lease = False
  88. in_failover = False
  89. for line in leases_file:
  90. if line.lstrip().startswith('#'):
  91. continue
  92. tokens = line.split()
  93. if len(tokens) == 0:
  94. continue
  95. key = tokens[0].lower()
  96. if key == 'lease':
  97. if not in_lease:
  98. ip_address = tokens[1]
  99. lease_rec = {'ip_address' : ip_address}
  100. in_lease = True
  101. else:
  102. raise Exception('Parse error in leases file')
  103. elif key == 'failover':
  104. in_failover = True
  105. elif key == '}':
  106. if in_lease:
  107. for k in valid_keys:
  108. if callable(valid_keys[k]):
  109. lease_rec[k] = lease_rec.get(k, '')
  110. else:
  111. lease_rec[k] = False
  112. ip_address = lease_rec['ip_address']
  113. if ip_address in leases_db:
  114. leases_db[ip_address].insert(0, lease_rec)
  115. else:
  116. leases_db[ip_address] = [lease_rec]
  117. lease_rec = {}
  118. in_lease = False
  119. elif in_failover:
  120. in_failover = False
  121. continue
  122. else:
  123. raise Exception('Parse error in leases file')
  124. elif key in valid_keys:
  125. if in_lease:
  126. value = line[(line.index(key) + len(key)):]
  127. value = value.strip().rstrip(';').rstrip()
  128. if callable(valid_keys[key]):
  129. lease_rec[key] = valid_keys[key](value)
  130. else:
  131. lease_rec[key] = True
  132. else:
  133. raise Exception('Parse error in leases file')
  134. else:
  135. if in_lease:
  136. raise Exception('Parse error in leases file')
  137. if in_lease:
  138. raise Exception('Parse error in leases file')
  139. return leases_db
  140. def round_timedelta(tdelta):
  141. return datetime.timedelta(tdelta.days,
  142. tdelta.seconds + (0 if tdelta.microseconds < 500000 else 1))
  143. def timestamp_now():
  144. n = datetime.datetime.utcnow()
  145. return datetime.datetime(n.year, n.month, n.day, n.hour, n.minute,
  146. n.second + (0 if n.microsecond < 500000 else 1))
  147. def lease_is_active(lease_rec, as_of_ts):
  148. return timestamp_is_between(as_of_ts, lease_rec['starts'],
  149. lease_rec['ends'])
  150. def ipv4_to_int(ipv4_addr):
  151. parts = ipv4_addr.split('.')
  152. return (int(parts[0]) << 24) + (int(parts[1]) << 16) + \
  153. (int(parts[2]) << 8) + int(parts[3])
  154. def select_active_leases(leases_db, as_of_ts):
  155. retarray = []
  156. sortedarray = []
  157. for ip_address in leases_db:
  158. lease_rec = leases_db[ip_address][0]
  159. if lease_is_active(lease_rec, as_of_ts):
  160. ip_as_int = ipv4_to_int(ip_address)
  161. insertpos = bisect.bisect(sortedarray, ip_as_int)
  162. sortedarray.insert(insertpos, ip_as_int)
  163. retarray.insert(insertpos, lease_rec)
  164. return retarray
  165. def select_old_leases(leases_db, as_of_ts):
  166. retarray = []
  167. sortedarray = []
  168. for ip_address in leases_db:
  169. lease_rec = leases_db[ip_address][0]
  170. if not lease_is_active(lease_rec, as_of_ts):
  171. ip_as_int = ipv4_to_int(ip_address)
  172. insertpos = bisect.bisect(sortedarray, ip_as_int)
  173. sortedarray.insert(insertpos, ip_as_int)
  174. retarray.insert(insertpos, lease_rec)
  175. return retarray
  176. ##############################################################################
  177. #myfile = open("dhcpd.leases", "r")
  178. myfile = open('/var/lib/dhcp/dhcpd.leases', 'r')
  179. leases = parse_leases_file(myfile)
  180. myfile.close()
  181. #pprint(leases)
  182. now = timestamp_now()
  183. report_dataset = select_active_leases(leases, now)
  184. #old_dataset = select_old_leases(leases, now)
  185. print('+------------------------------------------------------------------------------')
  186. print('| DHCPD ACTIVE LEASES REPORT')
  187. print('+-----------------+-------------------+----------------------+-----------------')
  188. print('| IP Address | MAC Address | Expires (days,H:M:S) | Client Hostname ')
  189. print('+-----------------+-------------------+----------------------+-----------------')
  190. for lease in report_dataset:
  191. try:
  192. print('| ' + format(lease['ip_address'], '<15') + ' | ' + \
  193. format(lease['hardware'], '<17') + ' | ' + \
  194. format(str((lease['ends'] - now) if lease['ends'] != 'never' else 'never'), '>20') + ' | ' + \
  195. lease['client-hostname'])
  196. except:
  197. pass
  198. #print('+-----------------+-------------------+----------------------+-----------------')
  199. #for lease in old_dataset:
  200. # try:
  201. # print('| ' + format(lease['ip_address'], '<15') + ' | ' + \
  202. # format(lease['hardware'], '<17') + ' | ' + \
  203. # format(str((lease['ends'] - now) if lease['ends'] != 'never' else 'never'), '>20') + ' | ' + \
  204. # lease['client-hostname'])
  205. # except:
  206. # pass
  207. print('+-----------------+-------------------+----------------------+-----------------')
  208. print("| Total Active Leases: {}".format(len(report_dataset)))
  209. #print("| Total Known Devices: {} ({} old leases)".format(len(report_dataset) + len(old_dataset), len(old_dataset)))
  210. print("| Report generated (UTC): {}".format(now))
  211. print('+------------------------------------------------------------------------------')