from pvl.verkko import db, web
from pvl.html import tags as html
import socket # dns
import logging; log = logging.getLogger('pvl.verkko.hosts')
# XXX: this should actually be DHCPHost
class Host (object) :
DATE_FMT = '%Y%m%d'
def __init__ (self, ip, mac, name=None) :
self.ip = ip
self.mac = mac
self.name = name
def render_mac (self) :
if not self.mac :
return None
elif len(self.mac) > (6 * 2 + 5) :
return u'???'
else :
return unicode(self.mac)
def render_name (self) :
if self.name :
return self.name.decode('ascii', 'replace')
else :
return None
def when (self) :
return '{frm} - {to}'.format(
frm = self.first_seen.strftime(self.DATE_FMT),
to = self.last_seen.strftime(self.DATE_FMT),
)
def dns (self) :
"""
Reverse-DNS lookup.
"""
if not self.ip :
return None
sockaddrs = set(sockaddr for family, socktype, proto, canonname, sockaddr in socket.getaddrinfo(self.ip, 0, 0, 0, 0, socket.AI_NUMERICHOST))
for sockaddr in sockaddrs :
try :
host, port = socket.getnameinfo(sockaddr, socket.NI_NAMEREQD)
except socket.gaierror :
continue
return host
def __unicode__ (self) :
return u"{host.ip} ({host.mac})".format(host=self)
db.mapper(Host, db.dhcp_hosts, properties=dict(
id = db.dhcp_hosts.c.rowid,
#_mac = db.dhcp_hosts.c.mac,
#_name = db.dhcp_hosts.c.name,
))
HOST_ATTRS = {
'id': Host.id,
'ip': Host.ip,
'mac': Host.mac,
'name': Host.name,
'seen': Host.last_seen,
}
HOST_SORT = Host.last_seen.desc()
def render_hosts (hosts, title=None) :
COLS = (
#title sort
('#', None),
('IP', 'ip'),
('MAC', 'mac'),
('Hostname', 'name'),
('Seen', 'seen'),
)
return html.table(
html.caption(title) if title else None,
html.thead(
html.tr(
html.th(
html.a(href='?sort={name}'.format(name=sort))(title) if sort else (title)
) for title, sort in COLS
)
),
html.tbody(
html.tr(class_=('alternate' if i % 2 else None), id=host.id)(
html.td(class_='id')(
html.a(href='/hosts/{host.id}'.format(host=host))(
'#' #host['rowid'])
)
),
html.td(class_='ip')(
html.a(href='/hosts/ip/{host.ip}'.format(host=host))(host.ip)
),
html.td(class_='mac')(
html.a(href='/hosts/mac/{host.mac}'.format(host=host))(
host.render_mac()
)
),
html.td(host.render_name()),
html.td(host.when()),
) for i, host in enumerate(hosts)
)
)
def render_host (host, hosts) :
title = 'DHCP Host: {host}'.format(host=host)
attrs = (
('IP', host.ip),
('MAC', host.mac),
('Hostname', host.name),
('DNS', host.dns()),
)
return (
html.h2('Host'),
html.dl(
(html.dt(title), html.dd(value)) for title, value in attrs
),
html.h2('Related'),
render_hosts(hosts),
html.a(href='/hosts')(html('«'), 'Back'),
)
class Handler (web.Handler) :
def title (self) :
pass
def index (self) :
return render_hosts(self.hosts)
def detail (self) :
return render_host(self.host, self.hosts)
def list (self) :
return render_host(self.host, self.hosts)
def process (self, id=None, attr=None, value=None) :
hosts = self.db.query(Host)
# sort ?
sort = self.request.args.get('sort')
if sort :
sort = HOST_ATTRS[sort]
else :
sort = HOST_SORT
log.debug("sort: %s", sort)
hosts = hosts.order_by(sort)
# lookup host
if id :
self.host = hosts.get(id)
if not self.host :
raise web.NotFound("No such host: {id}".format(id=id))
self.hosts = hosts.filter((Host.ip == self.host.ip) | (Host.mac == self.host.mac))
self.render = self.detail
self.title = "DHCP Host: {host}".format(host=unicode(self.host))
# lookup hosts
elif attr and value :
# fake host
host = { 'ip': None, 'mac': None, 'name': None }
if attr not in HOST_ATTRS :
raise web.BadRequest("Invalid attribute: {attr}".format(attr=attr))
host[attr] = value
self.host = Host(**host)
# query
attr = HOST_ATTRS[attr]
log.debug("%s == %s", attr, value)
self.hosts = hosts.filter(attr == value)
self.render = self.list
self.title = "DHCP Hosts: {value}".format(value=value)
# list
else :
self.hosts = hosts
self.render = self.index
self.title = "DHCP Hosts"