|
1 """ |
|
2 Track active DHCP hosts on network by dhcp messages. |
|
3 """ |
|
4 |
|
5 import logging; log = logging.getLogger('pvl.dhcp.hosts') |
|
6 |
|
7 # XXX: from db.dhcp_leases instead? |
|
8 import pvl.verkko.db as db |
|
9 |
|
10 class DHCPHostsDatabase (object) : |
|
11 """ |
|
12 pvl.verkko.Database dhcp_hosts model for updates. |
|
13 """ |
|
14 |
|
15 def __init__ (self, db) : |
|
16 self.db = db |
|
17 |
|
18 def create (self) : |
|
19 """ |
|
20 CREATE TABLEs |
|
21 """ |
|
22 |
|
23 log.info("Creating database tables: dhcp_hosts") |
|
24 db.dhcp_hosts.create(self.db.engine, checkfirst=True) |
|
25 |
|
26 def insert (self, attrs) : |
|
27 """ |
|
28 INSERT new host |
|
29 """ |
|
30 |
|
31 query = db.dhcp_hosts.insert().values( |
|
32 ip = attrs['ip'], |
|
33 mac = attrs['mac'], |
|
34 gw = attrs['gw'], |
|
35 |
|
36 first_seen = attrs['timestamp'], |
|
37 count = 1, |
|
38 |
|
39 last_seen = attrs['timestamp'], |
|
40 state = attrs['state'], |
|
41 |
|
42 name = attrs.get('name'), |
|
43 error = attrs.get('error'), |
|
44 ) |
|
45 |
|
46 # -> id |
|
47 return self.db.insert(query) |
|
48 |
|
49 def update (self, attrs) : |
|
50 """ |
|
51 UPDATE existing host, or return False if not found. |
|
52 """ |
|
53 |
|
54 table = db.dhcp_hosts |
|
55 |
|
56 query = table.update() |
|
57 query = query.where((table.c.ip == attrs['ip']) & (table.c.mac == attrs['mac']) & (table.c.gw == attrs['gw'])) |
|
58 query = query.values( |
|
59 count = db.func.coalesce(table.c.count, 0) + 1, |
|
60 |
|
61 # set |
|
62 last_seen = attrs['timestamp'], |
|
63 state = attrs['state'], |
|
64 ) |
|
65 |
|
66 if 'name' in attrs : |
|
67 query = query.values(name = attrs['name']) |
|
68 |
|
69 if 'error' in attrs : |
|
70 query = query.values(error = attrs['error']) |
|
71 |
|
72 # any matched rows? |
|
73 return self.db.update(query) |
|
74 |
|
75 def __call__ (self, item) : |
|
76 """ |
|
77 Process given DHCP syslog message to update the hosts table. |
|
78 """ |
|
79 |
|
80 attrs = {} |
|
81 |
|
82 # ignore unless we have enough info to fully identify the client |
|
83 # this means that we omit DHCPDISCOVER messages, but we get the OFFER/REQUEST/ACK |
|
84 if any(name not in item for name in ('lease', 'hwaddr', 'gateway')) : |
|
85 # ignore; we require these |
|
86 return |
|
87 |
|
88 # do not override error from request on NAK; clear otherwise |
|
89 if item.get('type') == 'DHCPNAK' : |
|
90 pass |
|
91 else : |
|
92 attrs['error'] = item.get('error') |
|
93 |
|
94 # do not override name unless known |
|
95 if item.get('name') : |
|
96 attrs['name'] = item.get('name') |
|
97 |
|
98 # db: syslog |
|
99 ATTR_MAP = ( |
|
100 ('ip', 'lease'), |
|
101 ('mac', 'hwaddr'), |
|
102 ('gw', 'gateway'), |
|
103 |
|
104 ('timestamp', 'timestamp'), |
|
105 ('state', 'type'), |
|
106 ) |
|
107 |
|
108 # generic attrs |
|
109 for key, name in ATTR_MAP : |
|
110 attrs[key] = item.get(name) |
|
111 |
|
112 # update existing? |
|
113 if self.update(attrs) : |
|
114 log.info("Update: %s", attrs) |
|
115 |
|
116 else : |
|
117 # new |
|
118 log.info("Insert: %s", attrs) |
|
119 self.insert(attrs) |
|
120 |
|
121 |