diff options
-rw-r--r-- | rpkid/portal-gui/scripts/rpkigui-import-routes.py | 74 | ||||
-rw-r--r-- | rpkid/rpki/gui/app/views.py | 5 | ||||
-rw-r--r-- | rpkid/rpki/gui/models.py | 91 | ||||
-rw-r--r-- | rpkid/rpki/gui/routeview/models.py | 68 |
4 files changed, 155 insertions, 83 deletions
diff --git a/rpkid/portal-gui/scripts/rpkigui-import-routes.py b/rpkid/portal-gui/scripts/rpkigui-import-routes.py index b8d2413d..4ca8ef96 100644 --- a/rpkid/portal-gui/scripts/rpkigui-import-routes.py +++ b/rpkid/portal-gui/scripts/rpkigui-import-routes.py @@ -1,7 +1,11 @@ import sys, itertools, re +import struct +import _mysql_exceptions from django.db import transaction, connection +import rpki +import rpki.gui.models from rpki.gui.routeview import models from rpki.resource_set import resource_range_ipv4, resource_range_ipv6 @@ -14,31 +18,50 @@ ip_re = re.compile(r'^[0-9a-fA-F:.]+/\d{1,3}$') class InvalidPrefix(Exception): pass +last_prefix = None +last_asn = None + for row in itertools.islice(f, 5, None): try: cols = row.split() prefix = cols[1] - # validate the prefix since the "sh ip bgp" output is sometimes corrupt - # by no space between the prefix and the next hop IP address. - if not ip_re.match(prefix): - raise InvalidPrefix(prefix) - # index -1 is i/e/? for igp/egp origin_as = cols[-2] - # skip AS_SETs + # FIXME: skip AS_SETs if origin_as[0] == '{': continue + # the output may contain multiple paths to the same origin. + # if this is the same prefix as the last entry, we don't need + # to validate it again. + if prefix != last_prefix: + # validate the prefix since the "sh ip bgp" output is sometimes corrupt + # by no space between the prefix and the next hop IP address. + + if not ip_re.match(prefix): + net, bits = prefix.split('/') + if len(bits) > 2 and int(bits[0]) <= 3: + print 'mask for %s looks fishy...' % prefix, + prefix = '%s/%s' % (net, bits[0:2]) + print 'assuming it should be %s' % prefix + if not ip_re.match(prefix): + raise InvalidPrefix(prefix) + last_prefix = prefix + elif origin_as == last_asn: + # we are only interested in origins, so skip alternate paths + # to same origin as last entry. + continue + last_asn = origin_as + asns = prefixes.get(prefix) if not asns: asns = set() prefixes[prefix] = asns asns.add(int(origin_as)) - #print 'prefix=%s asns=%s' % (prefix, asns) except InvalidPrefix, e: print >>sys.stderr, 'skipping bad entry: ' + row, print >>sys.stderr, e @@ -48,17 +71,17 @@ f.close() def commit(): cursor = connection.cursor() - # an OperationalError exception is thrown when the index doesn't exist try: - print 'Removing existing index...' - cursor.execute('DROP INDEX routeview_routeorigin_idx ON routeview_routeorigin') - except Exception, e: - print type(e) - print e - cursor.execute('BEGIN') + print 'Dropping existing staging table...' + cursor.execute('DROP TABLE IF EXISTS routeview_routeorigin_new') + except _mysql_exceptions.Warning: + pass - print 'Deleting rows from table...' - cursor.execute('DELETE FROM routeview_routeorigin') + print 'Creating staging table...' + cursor.execute('CREATE TABLE routeview_routeorigin_new LIKE routeview_routeorigin') + + print 'Disabling autocommit...' + cursor.execute('SET autocommit=0') print 'Adding rows to table...' for prefix, asns in prefixes.iteritems(): @@ -66,14 +89,25 @@ def commit(): cls = resource_range_ipv6 if family == 6 else resource_range_ipv4 rng = cls.parse_str(prefix) - cursor.executemany("INSERT INTO routeview_routeorigin SET family=%s, asn=%s, prefix_min=X%s, prefix_max=X%s", - [(family, asn, '%032x' % rng.min, '%032x' % rng.max) for asn in asns]) + if family == 4: + xform = long + else: + xform = lambda v: struct.pack('!QQ', (long(v) >> 64) &0xffffffffffffffffL, long(v) & 0xFFFFFFFFFFFFFFFFL) + + cursor.executemany("INSERT INTO routeview_routeorigin_new SET asn=%s, prefix_min=%s, prefix_max=%s", + [(asn, xform(rng.min), xform(rng.max)) for asn in asns]) print 'Committing...' cursor.execute('COMMIT') - print 'Creating index on table...' - cursor.execute('CREATE INDEX routeview_routeorigin_idx ON routeview_routeorigin (family, prefix_min, prefix_max)') + try: + print 'Dropping old table...' + cursor.execute('DROP TABLE IF EXISTS routeview_routeorigin_old') + except _mysql_exceptions.Warning: + pass + + print 'Swapping staging table with live table...' + cursor.execute('RENAME TABLE routeview_routeorigin TO routeview_routeorigin_old, routeview_routeorigin_new TO routeview_routeorigin') transaction.commit_unless_managed() diff --git a/rpkid/rpki/gui/app/views.py b/rpkid/rpki/gui/app/views.py index a29ecba2..4e89dcda 100644 --- a/rpkid/rpki/gui/app/views.py +++ b/rpkid/rpki/gui/app/views.py @@ -918,7 +918,10 @@ def route_view(request): for p in models.AddressRange.objects.filter(from_cert__parent__in=handle.parents.all()): r = p.as_resource_range() print >>log, 'querying for routes matching %s' % r - qs = rpki.gui.routeview.models.RouteOrigin.objects.filter(family=4, prefix_min__gte=r.min, prefix_max__lte=r.max) + if isinstance(r, rpki.resource_set.resource_range_ipv6): + print >>log, 'skipping ipv6 address: %s' % r + continue + qs = rpki.gui.routeview.models.RouteOrigin.objects.filter(prefix_min__gte=r.min, prefix_max__lte=r.max) for obj in qs: # determine the validation status of each route obj.status_label = 'warning' diff --git a/rpkid/rpki/gui/models.py b/rpkid/rpki/gui/models.py new file mode 100644 index 00000000..323d43e6 --- /dev/null +++ b/rpkid/rpki/gui/models.py @@ -0,0 +1,91 @@ +# Common classes for resuse in apps + +import struct + +from django.db import models + +import rpki.resource_set +import rpki.ipaddrs + +class IPv6AddressField(models.Field): + "Field large enough to hold a 128-bit unsigned integer." + + __metaclass__ = models.SubfieldBase + + def db_type(self, connection): + return 'binary(16)' + + def to_python(self, value): + if isinstance(value, rpki.ipaddrs.v6addr): + return value + x = struct.unpack('!QQ', value) + return rpki.ipaddrs.v6addr((x[0] << 64) | x[1]) + + def get_db_prep_value(self, value, connection, prepared): + return struct.pack('!QQ', (long(value) >> 64) & 0xFFFFFFFFFFFFFFFFL, long(value) & 0xFFFFFFFFFFFFFFFFL) + +class IPv4AddressField(models.Field): + "Wrapper around rpki.ipaddrs.v4addr." + + __metaclass__ = models.SubfieldBase + + def db_type(self, connection): + return 'int UNSIGNED' + + def to_python(self, value): + if isinstance(value, rpki.ipaddrs.v4addr): + return value + return rpki.ipaddrs.v4addr(value) + + def get_db_prep_value(self, value, connection, prepared): + return long(value) + +class Prefix(models.Model): + """Common implementation for models with an IP address range. + + Expects that `range_cls` is set to the appropriate subclass of + rpki.resource_set.resource_range_ip.""" + + def as_resource_range(self): + """ + Returns the prefix as a rpki.resource_set.resource_range_ip object. + """ + return self.range_cls(self.prefix_min, self.prefix_max) + + def prefixlen(self): + "Returns the prefix length for the prefix in this object." + return self.as_range().prefixlen() + + def get_prefix_display(self): + "Returns a string version of the prefix in this object." + return str(self.as_resource_range()) + + class Meta: + abstract = True + + # default sort order reflects what "sh ip bgp" outputs + ordering = ('prefix_min',) + +class PrefixV4(Prefix): + "IPv4 Prefix." + + range_cls = rpki.resource_set.resource_range_ipv4 + + prefix_min = IPv4AddressField(db_index=True, null=False) + prefix_max = IPv4AddressField(db_index=True, null=False) + + class Meta: + abstract = True + +class PrefixV6(Prefix): + "IPv6 Prefix." + + range_cls = rpki.resource_set.resource_range_ipv6 + + prefix_min = IPv6AddressField(db_index=True, null=False) + prefix_max = IPv6AddressField(db_index=True, null=False) + + class Meta: + abstract = True + +# vim:sw=4 ts=8 expandtab diff --git a/rpkid/rpki/gui/routeview/models.py b/rpkid/rpki/gui/routeview/models.py index 4bb6eef5..99dd0297 100644 --- a/rpkid/rpki/gui/routeview/models.py +++ b/rpkid/rpki/gui/routeview/models.py @@ -1,68 +1,12 @@ -import binascii +import django.db.models +import rpki.gui.models -from django.db import models - -import rpki -import rpki.resource_set -import rpki.ipaddrs - -class PositiveHugeIntegerField(models.Field): - - description = "Represents a 128-bit unsigned integer." - - __metaclass__ = models.SubfieldBase - - def db_type(self, connection): - if connection.settings_dict['ENGINE'] == 'django.db.backends.mysql': - return 'binary(16)' - return 'blob' - - def to_python(self, value): - if isinstance(value, int): - return long(value) - if isinstance(value, long): - return value - return long(binascii.hexlify(value), 16) - - def get_db_prep_value(self, value, connection, prepared=False): - return binascii.unhexlify('%032x' % value) - -class RouteOrigin(models.Model): - - asn = models.PositiveIntegerField(help_text='origin AS') - family = models.PositiveSmallIntegerField(help_text='IP version') - - # address stored as unsigned integer to faciliate lookups - prefix_min = PositiveHugeIntegerField() - prefix_max = PositiveHugeIntegerField() - - def as_range(self): - """ - Returns the prefix as a rpki.resource_set.resource_range_ip object. - """ - cls = rpki.resource_set.resource_range_ipv4 if self.family == 4 else rpki.resource_set.resource_range_ipv6 - ipcls = rpki.ipaddrs.v4addr if self.family == 4 else rpki.ipaddrs.v6addr - return cls(ipcls(self.prefix_min), ipcls(self.prefix_max)) - - def get_prefix_display(self): - """ - Returns a string version of the prefix in the routing entry. - """ - return str(self.as_range()) - - def prefixlen(self): - """ - Returns the prefix length for this route object. - """ - return self.as_range().prefixlen() +class RouteOrigin(rpki.gui.models.PrefixV4): + "Represents a BGP routing table entry." + asn = django.db.models.PositiveIntegerField(help_text='origin AS', null=False) + def __unicode__(self): return u"AS%d's route origin for %s" % (self.asn, self.get_prefix_display()) - class Meta: - # sort order reflects what "sh ip bgp" outputs - ordering = ( 'family', 'prefix_min', 'prefix_max', 'asn' ) - - unique_together = ('family', 'asn', 'prefix_min', 'prefix_max') - # vim:sw=4 ts=8 expandtab |