1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import with_statement
from txtorcon.interface import IAddrListener
from txtorcon.util import maybe_ip_addr
from twisted.internet.interfaces import IReactorTime
from twisted.internet import reactor
import datetime
import shlex
class Addr(object):
"""
One address mapping (e.g. example.com -> 127.0.0.1)
"""
def __init__(self, map):
"""
map is an AddrMap instance, used for scheduling expiries and
updating the map.
"""
self.map = map
self.ip = None
self.name = None
self.expiry = None
self.expires = None
self.created = None
def update(self, *args):
"""
deals with an update from Tor; see parsing logic in torcontroller
"""
gmtexpires = None
(name, ip, expires) = args[:3]
for arg in args:
if arg.lower().startswith('expires='):
gmtexpires = arg[8:]
if gmtexpires is None:
if len(args) == 3:
gmtexpires = expires
else:
if args[2] == 'NEVER':
gmtexpires = args[2]
else:
gmtexpires = args[3]
self.name = name # "www.example.com"
self.ip = maybe_ip_addr(ip) # IPV4Address instance, or string
if self.ip == '<error>':
self._expire()
return
fmt = "%Y-%m-%d %H:%M:%S"
# if we already have expiry times, etc then we want to
# properly delay our timeout
oldexpires = self.expires
if gmtexpires.upper() == 'NEVER':
# FIXME can I just select a date 100 years in the future instead?
self.expires = None
else:
self.expires = datetime.datetime.strptime(gmtexpires, fmt)
self.created = datetime.datetime.utcnow()
if self.expires is not None:
if oldexpires is None:
if self.expires <= self.created:
diff = datetime.timedelta(seconds=0)
else:
diff = self.expires - self.created
self.expiry = self.map.scheduler.callLater(diff.seconds,
self._expire)
else:
diff = self.expires - oldexpires
self.expiry.delay(diff.seconds)
def _expire(self):
"""
callback done via callLater
"""
del self.map.addr[self.name]
self.map.notify("addrmap_expired", *[self.name], **{})
class AddrMap(object):
"""
A collection of Addr objects mapping domains to addresses, with
automatic expiry.
FIXME: need listener interface, so far:
addrmap_added(Addr)
addrmap_expired(name)
"""
def __init__(self):
self.addr = {}
self.scheduler = IReactorTime(reactor)
self.listeners = []
def update(self, update):
"""
Deal with an update from Tor; either creates a new Addr object
or find existing one and calls update() on it.
"""
params = shlex.split(update)
if params[0] in self.addr:
self.addr[params[0]].update(*params)
else:
a = Addr(self)
# add both name and IP address
self.addr[params[0]] = a
self.addr[params[1]] = a
a.update(*params)
self.notify("addrmap_added", *[a], **{})
def find(self, name_or_ip):
"FIXME should make this class a dict-like (or subclass?)"
return self.addr[name_or_ip]
def notify(self, method, *args, **kwargs):
for listener in self.listeners:
getattr(listener, method)(*args, **kwargs)
def add_listener(self, listener):
if listener not in self.listeners:
self.listeners.append(IAddrListener(listener))
|