aboutsummaryrefslogtreecommitdiff
path: root/rpkid.with_tls/rpki/resource_set.py
diff options
context:
space:
mode:
Diffstat (limited to 'rpkid.with_tls/rpki/resource_set.py')
-rw-r--r--rpkid.with_tls/rpki/resource_set.py1107
1 files changed, 1107 insertions, 0 deletions
diff --git a/rpkid.with_tls/rpki/resource_set.py b/rpkid.with_tls/rpki/resource_set.py
new file mode 100644
index 00000000..9addab49
--- /dev/null
+++ b/rpkid.with_tls/rpki/resource_set.py
@@ -0,0 +1,1107 @@
+"""
+Classes dealing with sets of resources.
+
+The basic mechanics of a resource set are the same for any of the
+resources we handle (ASNs, IPv4 addresses, or IPv6 addresses), so we
+can provide the same operations on any of them, even though the
+underlying details vary.
+
+We also provide some basic set operations (union, intersection, etc).
+
+$Id$
+
+Copyright (C) 2009-2010 Internet Systems Consortium ("ISC")
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+
+Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+"""
+
+import re, math
+import rpki.ipaddrs, rpki.oids, rpki.exceptions
+
+## @var inherit_token
+# Token used to indicate inheritance in read and print syntax.
+
+inherit_token = "<inherit>"
+
+re_asn_range = re.compile("^([0-9]+)-([0-9]+)$")
+re_address_range = re.compile("^([0-9:.a-fA-F]+)-([0-9:.a-fA-F]+)$")
+re_prefix_with_maxlen = re.compile("^([0-9:.a-fA-F]+)/([0-9]+)-([0-9]+)$")
+re_prefix = re.compile("^([0-9:.a-fA-F]+)/([0-9]+)$")
+
+class resource_range(object):
+ """
+ Generic resource range type. Assumes underlying type is some kind
+ of integer.
+
+ This is a virtual class. You probably don't want to use this type
+ directly.
+ """
+
+ def __init__(self, min, max):
+ """
+ Initialize and sanity check a resource_range.
+ """
+ assert min.__class__ is max.__class__, "Type mismatch, %r doesn't match %r" % (min.__class__, max.__class__)
+ assert min <= max, "Mis-ordered range: %s before %s" % (min, max)
+ self.min = min
+ self.max = max
+
+ def __cmp__(self, other):
+ """
+ Compare two resource_range objects.
+ """
+ assert self.__class__ is other.__class__, "Type mismatch, comparing %r with %r" % (self.__class__, other.__class__)
+ return cmp(self.min, other.min) or cmp(self.max, other.max)
+
+class resource_range_as(resource_range):
+ """
+ Range of Autonomous System Numbers.
+
+ Denotes a single ASN by a range whose min and max values are
+ identical.
+ """
+
+ ## @var datum_type
+ # Type of underlying data (min and max).
+
+ datum_type = long
+
+ def __str__(self):
+ """
+ Convert a resource_range_as to string format.
+ """
+ if self.min == self.max:
+ return str(self.min)
+ else:
+ return str(self.min) + "-" + str(self.max)
+
+ def to_rfc3779_tuple(self):
+ """
+ Convert a resource_range_as to tuple format for RFC 3779 ASN.1 encoding.
+ """
+ if self.min == self.max:
+ return ("id", self.min)
+ else:
+ return ("range", (self.min, self.max))
+
+ @classmethod
+ def parse_str(cls, x):
+ """
+ Parse ASN resource range from text (eg, XML attributes).
+ """
+ r = re_asn_range.match(x)
+ if r:
+ return cls(long(r.group(1)), long(r.group(2)))
+ else:
+ return cls(long(x), long(x))
+
+ @classmethod
+ def from_strings(cls, a, b = None):
+ """
+ Construct ASN range from strings.
+ """
+ if b is None:
+ b = a
+ return cls(long(a), long(b))
+
+class resource_range_ip(resource_range):
+ """
+ Range of (generic) IP addresses.
+
+ Prefixes are converted to ranges on input, and ranges that can be
+ represented as prefixes are written as prefixes on output.
+
+ This is a virtual class. You probably don't want to use it
+ directly.
+ """
+
+ def prefixlen(self):
+ """
+ Determine whether a resource_range_ip can be expressed as a
+ prefix. Returns prefix length if it can, otherwise raises
+ MustBePrefix exception.
+ """
+ mask = self.min ^ self.max
+ if self.min & mask != 0:
+ raise rpki.exceptions.MustBePrefix
+ prefixlen = self.datum_type.bits
+ while mask & 1:
+ prefixlen -= 1
+ mask >>= 1
+ if mask:
+ raise rpki.exceptions.MustBePrefix
+ return prefixlen
+
+ # Backwards compatability, will go away at some point
+ _prefixlen = prefixlen
+
+ def __str__(self):
+ """
+ Convert a resource_range_ip to string format.
+ """
+ try:
+ return str(self.min) + "/" + str(self.prefixlen())
+ except rpki.exceptions.MustBePrefix:
+ return str(self.min) + "-" + str(self.max)
+
+ def to_rfc3779_tuple(self):
+ """
+ Convert a resource_range_ip to tuple format for RFC 3779 ASN.1
+ encoding.
+ """
+ try:
+ return ("addressPrefix", _long2bs(self.min, self.datum_type.bits,
+ prefixlen = self.prefixlen()))
+ except rpki.exceptions.MustBePrefix:
+ return ("addressRange", (_long2bs(self.min, self.datum_type.bits, strip = 0),
+ _long2bs(self.max, self.datum_type.bits, strip = 1)))
+
+ @classmethod
+ def parse_str(cls, x):
+ """
+ Parse IP address range or prefix from text (eg, XML attributes).
+ """
+ r = re_address_range.match(x)
+ if r:
+ return cls(cls.datum_type(r.group(1)), cls.datum_type(r.group(2)))
+ r = re_prefix.match(x)
+ if r:
+ return cls.make_prefix(cls.datum_type(r.group(1)), int(r.group(2)))
+ raise rpki.exceptions.BadIPResource, 'Bad IP resource "%s"' % (x)
+
+ @classmethod
+ def make_prefix(cls, prefix, prefixlen):
+ """
+ Construct a resource range corresponding to a prefix.
+ """
+ assert isinstance(prefix, cls.datum_type) and isinstance(prefixlen, (int, long))
+ assert prefixlen >= 0 and prefixlen <= cls.datum_type.bits, "Nonsensical prefix length: %s" % prefixlen
+ mask = (1 << (cls.datum_type.bits - prefixlen)) - 1
+ assert (prefix & mask) == 0, "Resource not in canonical form: %s/%s" % (prefix, prefixlen)
+ return cls(cls.datum_type(prefix), cls.datum_type(prefix | mask))
+
+ def chop_into_prefixes(self, result):
+ """
+ Chop up a resource_range_ip into ranges that can be represented as
+ prefixes.
+ """
+ try:
+ self.prefixlen()
+ result.append(self)
+ except rpki.exceptions.MustBePrefix:
+ min = self.min
+ max = self.max
+ while max >= min:
+ bits = int(math.log(max - min + 1, 2))
+ while True:
+ mask = ~(~0 << bits)
+ assert min + mask <= max
+ if min & mask == 0:
+ break
+ assert bits > 0
+ bits -= 1
+ result.append(self.make_prefix(min, self.datum_type.bits - bits))
+ min = self.datum_type(min + mask + 1)
+
+ @classmethod
+ def from_strings(cls, a, b = None):
+ """
+ Construct IP address range from strings.
+ """
+ if b is None:
+ b = a
+ a = rpki.ipaddrs.parse(a)
+ b = rpki.ipaddrs.parse(b)
+ if a.__class__ is not b.__class__:
+ raise TypeError
+ if cls is resource_range_ip:
+ if isinstance(a, rpki.ipaddrs.v4addr):
+ return resource_range_ipv4(a, b)
+ if isinstance(a, rpki.ipaddrs.v6addr):
+ return resource_range_ipv6(a, b)
+ elif isinstance(a, cls.datum_type):
+ return cls(a, b)
+ raise TypeError
+
+class resource_range_ipv4(resource_range_ip):
+ """
+ Range of IPv4 addresses.
+ """
+
+ ## @var datum_type
+ # Type of underlying data (min and max).
+
+ datum_type = rpki.ipaddrs.v4addr
+
+class resource_range_ipv6(resource_range_ip):
+ """
+ Range of IPv6 addresses.
+ """
+
+ ## @var datum_type
+ # Type of underlying data (min and max).
+
+ datum_type = rpki.ipaddrs.v6addr
+
+def _rsplit(rset, that):
+ """
+ Utility function to split a resource range into two resource ranges.
+ """
+ this = rset.pop(0)
+ cell_type = type(this.min)
+ assert type(this) is type(that) and type(this.max) is cell_type and \
+ type(that.min) is cell_type and type(that.max) is cell_type
+ if this.min < that.min:
+ rset.insert(0, type(this)(this.min, cell_type(that.min - 1)))
+ rset.insert(1, type(this)(that.min, this.max))
+ else:
+ assert this.max > that.max
+ rset.insert(0, type(this)(this.min, that.max))
+ rset.insert(1, type(this)(cell_type(that.max + 1), this.max))
+
+class resource_set(list):
+ """
+ Generic resource set, a list subclass containing resource ranges.
+
+ This is a virtual class. You probably don't want to use it
+ directly.
+ """
+
+ ## @var inherit
+ # Boolean indicating whether this resource_set uses RFC 3779 inheritance.
+
+ inherit = False
+
+ ## @var canonical
+ # Whether this resource_set is currently in canonical form.
+
+ canonical = False
+
+ def __init__(self, ini = None):
+ """
+ Initialize a resource_set.
+ """
+ list.__init__(self)
+ if isinstance(ini, (int, long)):
+ ini = str(ini)
+ if ini is inherit_token:
+ self.inherit = True
+ elif isinstance(ini, str) and len(ini):
+ self.extend(self.parse_str(s) for s in ini.split(","))
+ elif isinstance(ini, tuple):
+ self.parse_rfc3779_tuple(ini)
+ elif isinstance(ini, list):
+ self.extend(ini)
+ else:
+ assert ini is None or (isinstance(ini, str) and ini == ""), "Unexpected initializer: %s" % str(ini)
+ self.canonize()
+
+ def canonize(self):
+ """
+ Whack this resource_set into canonical form.
+ """
+ assert not self.inherit or not self
+ if not self.canonical:
+ self.sort()
+ for i in xrange(len(self) - 2, -1, -1):
+ if self[i].max + 1 == self[i+1].min:
+ self[i] = type(self[i])(self[i].min, self[i+1].max)
+ self.pop(i + 1)
+ if __debug__:
+ for i in xrange(0, len(self) - 1):
+ assert self[i].max < self[i+1].min, "Resource overlap: %s %s" % (self[i], self[i+1])
+ self.canonical = True
+
+ def append(self, item):
+ """
+ Wrapper around list.append() (q.v.) to reset canonical flag.
+ """
+ list.append(self, item)
+ self.canonical = False
+
+ def extend(self, item):
+ """
+ Wrapper around list.extend() (q.v.) to reset canonical flag.
+ """
+ list.extend(self, item)
+ self.canonical = False
+
+ def __str__(self):
+ """
+ Convert a resource_set to string format.
+ """
+ if self.inherit:
+ return inherit_token
+ else:
+ return ",".join(str(x) for x in self)
+
+ def _comm(self, other):
+ """
+ Like comm(1), sort of.
+
+ Returns a tuple of three resource sets: resources only in self,
+ resources only in other, and resources in both. Used (not very
+ efficiently) as the basis for most set operations on resource
+ sets.
+ """
+
+ assert not self.inherit
+ assert type(self) is type(other), "Type mismatch %r %r" % (type(self), type(other))
+ set1 = type(self)(self) # clone and whack into canonical form
+ set2 = type(other)(other) # ditto
+ only1, only2, both = [], [], []
+ while set1 or set2:
+ if set1 and (not set2 or set1[0].max < set2[0].min):
+ only1.append(set1.pop(0))
+ elif set2 and (not set1 or set2[0].max < set1[0].min):
+ only2.append(set2.pop(0))
+ elif set1[0].min < set2[0].min:
+ _rsplit(set1, set2[0])
+ elif set2[0].min < set1[0].min:
+ _rsplit(set2, set1[0])
+ elif set1[0].max < set2[0].max:
+ _rsplit(set2, set1[0])
+ elif set2[0].max < set1[0].max:
+ _rsplit(set1, set2[0])
+ else:
+ assert set1[0].min == set2[0].min and set1[0].max == set2[0].max
+ both.append(set1.pop(0))
+ set2.pop(0)
+ return type(self)(only1), type(self)(only2), type(self)(both)
+
+ def union(self, other):
+ """
+ Set union for resource sets.
+ """
+
+ assert not self.inherit
+ assert type(self) is type(other), "Type mismatch: %r %r" % (type(self), type(other))
+ set1 = type(self)(self) # clone and whack into canonical form
+ set2 = type(other)(other) # ditto
+ result = []
+ while set1 or set2:
+ if set1 and (not set2 or set1[0].max < set2[0].min):
+ result.append(set1.pop(0))
+ elif set2 and (not set1 or set2[0].max < set1[0].min):
+ result.append(set2.pop(0))
+ else:
+ this = set1.pop(0)
+ that = set2.pop(0)
+ assert type(this) is type(that)
+ if this.min < that.min: min = this.min
+ else: min = that.min
+ if this.max > that.max: max = this.max
+ else: max = that.max
+ result.append(type(this)(min, max))
+ while set1 and set1[0].max <= max:
+ assert set1[0].min >= min
+ del set1[0]
+ while set2 and set2[0].max <= max:
+ assert set2[0].min >= min
+ del set2[0]
+ return type(self)(result)
+
+ def intersection(self, other):
+ """
+ Set intersection for resource sets.
+ """
+ return self._comm(other)[2]
+
+ def difference(self, other):
+ """
+ Set difference for resource sets.
+ """
+ return self._comm(other)[0]
+
+ def symmetric_difference(self, other):
+ """
+ Set symmetric difference (XOR) for resource sets.
+ """
+ com = self._comm(other)
+ return com[0].union(com[1])
+
+ def contains(self, item):
+ """
+ Set membership test for resource sets.
+ """
+ assert not self.inherit
+ self.canonize()
+ if not self:
+ return False
+ if type(item) is type(self[0]):
+ min = item.min
+ max = item.max
+ else:
+ min = item
+ max = item
+ lo = 0
+ hi = len(self)
+ while lo < hi:
+ mid = (lo + hi) / 2
+ if self[mid].max < max:
+ lo = mid + 1
+ else:
+ hi = mid
+ return lo < len(self) and self[lo].min <= min and self[lo].max >= max
+
+ def issubset(self, other):
+ """
+ Test whether self is a subset (possibly improper) of other.
+ """
+ for i in self:
+ if not other.contains(i):
+ return False
+ return True
+
+ def issuperset(self, other):
+ """
+ Test whether self is a superset (possibly improper) of other.
+ """
+ return other.issubset(self)
+
+ @classmethod
+ def from_sql(cls, sql, query, args = None):
+ """
+ Create resource set from an SQL query.
+
+ sql is an object that supports execute() and fetchall() methods
+ like a DB API 2.0 cursor object.
+
+ query is an SQL query that returns a sequence of (min, max) pairs.
+ """
+
+ sql.execute(query, args)
+ return cls(ini = [cls.range_type(cls.range_type.datum_type(b),
+ cls.range_type.datum_type(e))
+ for (b, e) in sql.fetchall()])
+
+ @classmethod
+ def parse_str(cls, s):
+ """
+ Parse resource set from text string (eg, XML attributes). This is
+ a backwards compatability wrapper, real functionality is now part
+ of the range classes.
+ """
+ return cls.range_type.parse_str(s)
+
+class resource_set_as(resource_set):
+ """
+ Autonomous System Number resource set.
+ """
+
+ ## @var range_type
+ # Type of range underlying this type of resource_set.
+
+ range_type = resource_range_as
+
+ def parse_rfc3779_tuple(self, x):
+ """
+ Parse ASN resource from tuple format generated by RFC 3779 ASN.1
+ decoder.
+ """
+ if x[0] == "asIdsOrRanges":
+ for aor in x[1]:
+ if aor[0] == "range":
+ min = aor[1][0]
+ max = aor[1][1]
+ else:
+ min = aor[1]
+ max = min
+ self.append(resource_range_as(min, max))
+ else:
+ assert x[0] == "inherit"
+ self.inherit = True
+
+ def to_rfc3779_tuple(self):
+ """
+ Convert ASN resource set into tuple format used for RFC 3779 ASN.1
+ encoding.
+ """
+ self.canonize()
+ if self:
+ return ("asIdsOrRanges", tuple(a.to_rfc3779_tuple() for a in self))
+ elif self.inherit:
+ return ("inherit", "")
+ else:
+ return None
+
+class resource_set_ip(resource_set):
+ """
+ (Generic) IP address resource set.
+
+ This is a virtual class. You probably don't want to use it
+ directly.
+ """
+
+ def parse_rfc3779_tuple(self, x):
+ """
+ Parse IP address resource sets from tuple format generated by RFC
+ 3779 ASN.1 decoder.
+ """
+ if x[0] == "addressesOrRanges":
+ for aor in x[1]:
+ if aor[0] == "addressRange":
+ min = _bs2long(aor[1][0], self.range_type.datum_type.bits, 0)
+ max = _bs2long(aor[1][1], self.range_type.datum_type.bits, 1)
+ else:
+ min = _bs2long(aor[1], self.range_type.datum_type.bits, 0)
+ max = _bs2long(aor[1], self.range_type.datum_type.bits, 1)
+ self.append(self.range_type(self.range_type.datum_type(min), self.range_type.datum_type(max)))
+ else:
+ assert x[0] == "inherit"
+ self.inherit = True
+
+ def to_roa_prefix_set(self):
+ """
+ Convert from a resource set to a ROA prefix set.
+ """
+ prefix_ranges = []
+ for r in self:
+ r.chop_into_prefixes(prefix_ranges)
+ return self.roa_prefix_set_type([
+ self.roa_prefix_set_type.prefix_type(r.min, r.prefixlen())
+ for r in prefix_ranges])
+
+ def to_rfc3779_tuple(self):
+ """
+ Convert IP resource set into tuple format used by RFC 3779 ASN.1
+ encoder.
+ """
+ self.canonize()
+ if self:
+ return (self.afi, ("addressesOrRanges", tuple(a.to_rfc3779_tuple() for a in self)))
+ elif self.inherit:
+ return (self.afi, ("inherit", ""))
+ else:
+ return None
+
+class resource_set_ipv4(resource_set_ip):
+ """
+ IPv4 address resource set.
+ """
+
+ ## @var range_type
+ # Type of range underlying this type of resource_set.
+
+ range_type = resource_range_ipv4
+
+ ## @var afi
+ # Address Family Identifier value for IPv4.
+
+ afi = "\x00\x01"
+
+class resource_set_ipv6(resource_set_ip):
+ """
+ IPv6 address resource set.
+ """
+
+ ## @var range_type
+ # Type of range underlying this type of resource_set.
+
+ range_type = resource_range_ipv6
+
+ ## @var afi
+ # Address Family Identifier value for IPv6.
+
+ afi = "\x00\x02"
+
+def _bs2long(bs, addrlen, fill):
+ """
+ Utility function to convert a bitstring (POW.pkix tuple
+ representation) into a Python long.
+ """
+ x = 0L
+ for y in bs:
+ x = (x << 1) | y
+ for y in xrange(addrlen - len(bs)):
+ x = (x << 1) | fill
+ return x
+
+def _long2bs(number, addrlen, prefixlen = None, strip = None):
+ """
+ Utility function to convert a Python long into a POW.pkix tuple
+ bitstring. This is a bit complicated because it supports the
+ fiendishly compact encoding used in RFC 3779.
+ """
+ assert prefixlen is None or strip is None
+ bs = []
+ while number:
+ bs.append(int(number & 1))
+ number >>= 1
+ if addrlen > len(bs):
+ bs.extend((0 for i in xrange(addrlen - len(bs))))
+ bs.reverse()
+ if prefixlen is not None:
+ return tuple(bs[0:prefixlen])
+ if strip is not None:
+ while bs and bs[-1] == strip:
+ bs.pop()
+ return tuple(bs)
+
+class resource_bag(object):
+ """
+ Container to simplify passing around the usual triple of ASN, IPv4,
+ and IPv6 resource sets.
+ """
+
+ ## @var asn
+ # Set of Autonomous System Number resources.
+
+ ## @var v4
+ # Set of IPv4 resources.
+
+ ## @var v6
+ # Set of IPv6 resources.
+
+ ## @var valid_until
+ # Expiration date of resources, for setting certificate notAfter field.
+
+ def __init__(self, asn = None, v4 = None, v6 = None, valid_until = None):
+ self.asn = asn or resource_set_as()
+ self.v4 = v4 or resource_set_ipv4()
+ self.v6 = v6 or resource_set_ipv6()
+ self.valid_until = valid_until
+
+ def oversized(self, other):
+ """
+ True iff self is oversized with respect to other.
+ """
+ return not self.asn.issubset(other.asn) or \
+ not self.v4.issubset(other.v4) or \
+ not self.v6.issubset(other.v6)
+
+ def undersized(self, other):
+ """
+ True iff self is undersized with respect to other.
+ """
+ return not other.asn.issubset(self.asn) or \
+ not other.v4.issubset(self.v4) or \
+ not other.v6.issubset(self.v6)
+
+ @classmethod
+ def from_rfc3779_tuples(cls, exts):
+ """
+ Build a resource_bag from intermediate form generated by RFC 3779
+ ASN.1 decoder.
+ """
+ asn = None
+ v4 = None
+ v6 = None
+ for x in exts:
+ if x[0] == rpki.oids.name2oid["sbgp-autonomousSysNum"]:
+ assert len(x[2]) == 1 or x[2][1] is None, "RDI not implemented: %s" % (str(x))
+ assert asn is None
+ asn = resource_set_as(x[2][0])
+ if x[0] == rpki.oids.name2oid["sbgp-ipAddrBlock"]:
+ for fam in x[2]:
+ if fam[0] == resource_set_ipv4.afi:
+ assert v4 is None
+ v4 = resource_set_ipv4(fam[1])
+ if fam[0] == resource_set_ipv6.afi:
+ assert v6 is None
+ v6 = resource_set_ipv6(fam[1])
+ return cls(asn, v4, v6)
+
+ def empty(self):
+ """
+ True iff all resource sets in this bag are empty.
+ """
+ return not self.asn and not self.v4 and not self.v6
+
+ def __eq__(self, other):
+ return self.asn == other.asn and \
+ self.v4 == other.v4 and \
+ self.v6 == other.v6 and \
+ self.valid_until == other.valid_until
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def intersection(self, other):
+ """
+ Compute intersection with another resource_bag. valid_until
+ attribute (if any) inherits from self.
+ """
+ return self.__class__(self.asn.intersection(other.asn),
+ self.v4.intersection(other.v4),
+ self.v6.intersection(other.v6),
+ self.valid_until)
+
+ def union(self, other):
+ """
+ Compute union with another resource_bag. valid_until attribute
+ (if any) inherits from self.
+ """
+ return self.__class__(self.asn.union(other.asn),
+ self.v4.union(other.v4),
+ self.v6.union(other.v6),
+ self.valid_until)
+
+ def __str__(self):
+ s = ""
+ if self.asn:
+ s += "ASN: %s" % self.asn
+ if self.v4:
+ if s:
+ s += ", "
+ s += "V4: %s" % self.v4
+ if self.v6:
+ if s:
+ s += ", "
+ s += "V6: %s" % self.v6
+ return s
+
+# Sadly, there are enough differences between RFC 3779 and the data
+# structures in the latest proposed ROA format that we can't just use
+# the RFC 3779 code for ROAs. So we need a separate set of classes
+# that are similar in concept but different in detail, with conversion
+# functions. Such is life. I suppose it might be possible to do this
+# with multiple inheritance, but that's probably more bother than it's
+# worth.
+
+class roa_prefix(object):
+ """
+ ROA prefix. This is similar to the resource_range_ip class, but
+ differs in that it only represents prefixes, never ranges, and
+ includes the maximum prefix length as an additional value.
+
+ This is a virtual class, you probably don't want to use it directly.
+ """
+
+ ## @var prefix
+ # The prefix itself, an IP address with bits beyond the prefix
+ # length zeroed.
+
+ ## @var prefixlen
+ # (Minimum) prefix length.
+
+ ## @var max_prefixlen
+ # Maxmimum prefix length.
+
+ def __init__(self, prefix, prefixlen, max_prefixlen = None):
+ """
+ Initialize a ROA prefix. max_prefixlen is optional and defaults
+ to prefixlen. max_prefixlen must not be smaller than prefixlen.
+ """
+ if max_prefixlen is None:
+ max_prefixlen = prefixlen
+ assert max_prefixlen >= prefixlen, "Bad max_prefixlen: %d must not be shorter than %d" % (max_prefixlen, prefixlen)
+ self.prefix = prefix
+ self.prefixlen = prefixlen
+ self.max_prefixlen = max_prefixlen
+
+ def __cmp__(self, other):
+ """
+ Compare two ROA prefix objects. Comparision is based on prefix,
+ prefixlen, and max_prefixlen, in that order.
+ """
+ assert self.__class__ is other.__class__
+ return (cmp(self.prefix, other.prefix) or
+ cmp(self.prefixlen, other.prefixlen) or
+ cmp(self.max_prefixlen, other.max_prefixlen))
+
+ def __str__(self):
+ """
+ Convert a ROA prefix to string format.
+ """
+ if self.prefixlen == self.max_prefixlen:
+ return str(self.prefix) + "/" + str(self.prefixlen)
+ else:
+ return str(self.prefix) + "/" + str(self.prefixlen) + "-" + str(self.max_prefixlen)
+
+ def to_resource_range(self):
+ """
+ Convert this ROA prefix to the equivilent resource_range_ip
+ object. This is an irreversable transformation because it loses
+ the max_prefixlen attribute, nothing we can do about that.
+ """
+ return self.range_type.make_prefix(self.prefix, self.prefixlen)
+
+ def min(self):
+ """
+ Return lowest address covered by prefix.
+ """
+ return self.prefix
+
+ def max(self):
+ """
+ Return highest address covered by prefix.
+ """
+ t = self.range_type.datum_type
+ return t(self.prefix | ((1 << (t.bits - self.prefixlen)) - 1))
+
+ def to_roa_tuple(self):
+ """
+ Convert a resource_range_ip to tuple format for ROA ASN.1
+ encoding.
+ """
+ return (_long2bs(self.prefix, self.range_type.datum_type.bits, prefixlen = self.prefixlen),
+ None if self.prefixlen == self.max_prefixlen else self.max_prefixlen)
+
+ @classmethod
+ def parse_str(cls, x):
+ """
+ Parse ROA prefix from text (eg, an XML attribute).
+ """
+ r = re_prefix_with_maxlen.match(x)
+ if r:
+ return cls(cls.range_type.datum_type(r.group(1)), int(r.group(2)), int(r.group(3)))
+ r = re_prefix.match(x)
+ if r:
+ return cls(cls.range_type.datum_type(r.group(1)), int(r.group(2)))
+ raise rpki.exceptions.BadROAPrefix, 'Bad ROA prefix "%s"' % (x)
+
+class roa_prefix_ipv4(roa_prefix):
+ """
+ IPv4 ROA prefix.
+ """
+
+ ## @var range_type
+ # Type of corresponding resource_range_ip.
+
+ range_type = resource_range_ipv4
+
+class roa_prefix_ipv6(roa_prefix):
+ """
+ IPv6 ROA prefix.
+ """
+
+ ## @var range_type
+ # Type of corresponding resource_range_ip.
+
+ range_type = resource_range_ipv6
+
+class roa_prefix_set(list):
+ """
+ Set of ROA prefixes, analogous to the resource_set_ip class.
+ """
+
+ def __init__(self, ini = None):
+ """
+ Initialize a ROA prefix set.
+ """
+ list.__init__(self)
+ if isinstance(ini, str) and len(ini):
+ self.extend(self.parse_str(s) for s in ini.split(","))
+ elif isinstance(ini, (list, tuple)):
+ self.extend(ini)
+ else:
+ assert ini is None or ini == "", "Unexpected initializer: %s" % str(ini)
+ self.sort()
+
+ def __str__(self):
+ """
+ Convert a ROA prefix set to string format.
+ """
+ return ",".join(str(x) for x in self)
+
+ @classmethod
+ def parse_str(cls, s):
+ """
+ Parse ROA prefix from text (eg, an XML attribute).
+ This method is a backwards compatability shim.
+ """
+ return cls.prefix_type.parse_str(s)
+
+ def to_resource_set(self):
+ """
+ Convert a ROA prefix set to a resource set. This is an
+ irreversable transformation. We have to compute a union here
+ because ROA prefix sets can include overlaps, while RFC 3779
+ resource sets cannot. This is ugly, and there is almost certainly
+ a more efficient way to do this, but start by getting the output
+ right before worrying about making it fast or pretty.
+ """
+ r = self.resource_set_type()
+ s = self.resource_set_type()
+ s.append(None)
+ for p in self:
+ s[0] = p.to_resource_range()
+ r = r.union(s)
+ return r
+
+ @classmethod
+ def from_sql(cls, sql, query, args = None):
+ """
+ Create ROA prefix set from an SQL query.
+
+ sql is an object that supports execute() and fetchall() methods
+ like a DB API 2.0 cursor object.
+
+ query is an SQL query that returns a sequence of (prefix,
+ prefixlen, max_prefixlen) triples.
+ """
+
+ sql.execute(query, args)
+ return cls([cls.prefix_type(cls.prefix_type.range_type.datum_type(x), int(y), int(z))
+ for (x, y, z) in sql.fetchall()])
+
+ def to_roa_tuple(self):
+ """
+ Convert ROA prefix set into tuple format used by ROA ASN.1
+ encoder. This is a variation on the format used in RFC 3779.
+ """
+ if self:
+ return (self.resource_set_type.afi, tuple(a.to_roa_tuple() for a in self))
+ else:
+ return None
+
+class roa_prefix_set_ipv4(roa_prefix_set):
+ """
+ Set of IPv4 ROA prefixes.
+ """
+
+ ## @var prefix_type
+ # Type of underlying roa_prefix.
+
+ prefix_type = roa_prefix_ipv4
+
+ ## @var resource_set_type
+ # Type of corresponding resource_set_ip class.
+
+ resource_set_type = resource_set_ipv4
+
+# Fix back link from resource_set to roa_prefix
+resource_set_ipv4.roa_prefix_set_type = roa_prefix_set_ipv4
+
+class roa_prefix_set_ipv6(roa_prefix_set):
+ """
+ Set of IPv6 ROA prefixes.
+ """
+
+ ## @var prefix_type
+ # Type of underlying roa_prefix.
+
+ prefix_type = roa_prefix_ipv6
+
+ ## @var resource_set_type
+ # Type of corresponding resource_set_ip class.
+
+ resource_set_type = resource_set_ipv6
+
+# Fix back link from resource_set to roa_prefix
+resource_set_ipv6.roa_prefix_set_type = roa_prefix_set_ipv6
+
+# Test suite for set operations.
+
+if __name__ == "__main__":
+
+ def testprefix(v):
+ return " (%s)" % v.to_roa_prefix_set() if isinstance(v, resource_set_ip) else ""
+
+ def test1(t, s1, s2):
+ if isinstance(s1, str) and isinstance(s2, str):
+ print "x: ", s1
+ print "y: ", s2
+ r1 = t(s1)
+ r2 = t(s2)
+ print "x: ", r1, testprefix(r1)
+ print "y: ", r2, testprefix(r2)
+ v1 = r1._comm(r2)
+ v2 = r2._comm(r1)
+ assert v1[0] == v2[1] and v1[1] == v2[0] and v1[2] == v2[2]
+ for i in r1: assert r1.contains(i) and r1.contains(i.min) and r1.contains(i.max)
+ for i in r2: assert r2.contains(i) and r2.contains(i.min) and r2.contains(i.max)
+ for i in v1[0]: assert r1.contains(i) and not r2.contains(i)
+ for i in v1[1]: assert not r1.contains(i) and r2.contains(i)
+ for i in v1[2]: assert r1.contains(i) and r2.contains(i)
+ v1 = r1.union(r2)
+ v2 = r2.union(r1)
+ assert v1 == v2
+ print "x|y:", v1, testprefix(v1)
+ v1 = r1.difference(r2)
+ v2 = r2.difference(r1)
+ print "x-y:", v1, testprefix(v1)
+ print "y-x:", v2, testprefix(v2)
+ v1 = r1.symmetric_difference(r2)
+ v2 = r2.symmetric_difference(r1)
+ assert v1 == v2
+ print "x^y:", v1, testprefix(v1)
+ v1 = r1.intersection(r2)
+ v2 = r2.intersection(r1)
+ assert v1 == v2
+ print "x&y:", v1, testprefix(v1)
+
+ def test2(t, s1, s2):
+ print "x: ", s1
+ print "y: ", s2
+ r1 = t(s1)
+ r2 = t(s2)
+ print "x: ", r1
+ print "y: ", r2
+ print "x>y:", (r1 > r2)
+ print "x<y:", (r1 < r2)
+ test1(t.resource_set_type,
+ r1.to_resource_set(),
+ r2.to_resource_set())
+
+ def test3(t, s1, s2):
+ test1(t, s1, s2)
+ r1 = t(s1).to_roa_prefix_set()
+ r2 = t(s2).to_roa_prefix_set()
+ print "x: ", r1
+ print "y: ", r2
+ print "x>y:", (r1 > r2)
+ print "x<y:", (r1 < r2)
+ test1(t.roa_prefix_set_type.resource_set_type,
+ r1.to_resource_set(),
+ r2.to_resource_set())
+
+ print
+ print "Testing set operations on resource sets"
+ print
+ test1(resource_set_as, "1,2,3,4,5,6,11,12,13,14,15", "1,2,3,4,5,6,111,121,131,141,151")
+ print
+ test1(resource_set_ipv4, "10.0.0.44/32,10.6.0.2/32", "10.3.0.0/24,10.0.0.77/32")
+ print
+ test1(resource_set_ipv4, "10.0.0.44/32,10.6.0.2/32", "10.0.0.0/24")
+ print
+ test1(resource_set_ipv4, "10.0.0.0/24", "10.3.0.0/24,10.0.0.77/32")
+ print
+ test1(resource_set_ipv4, "10.0.0.0/24", "10.0.0.0/32,10.0.0.2/32,10.0.0.4/32")
+ print
+ print "Testing set operations on ROA prefixes"
+ print
+ test2(roa_prefix_set_ipv4, "10.0.0.44/32,10.6.0.2/32", "10.3.0.0/24,10.0.0.77/32")
+ print
+ test2(roa_prefix_set_ipv4, "10.0.0.0/24-32,10.6.0.0/24-32", "10.3.0.0/24,10.0.0.0/16-32")
+ print
+ test2(roa_prefix_set_ipv4, "10.3.0.0/24-24,10.0.0.0/16-32", "10.3.0.0/24,10.0.0.0/16-32")
+ print
+ test2(roa_prefix_set_ipv6, "2002:0a00:002c::1/128", "2002:0a00:002c::2/128")
+ print
+ test2(roa_prefix_set_ipv6, "2002:0a00:002c::1/128", "2002:0a00:002c::7/128")
+ print
+ test2(roa_prefix_set_ipv6, "2002:0a00:002c::1/128", "2002:0a00:002c::/120")
+ print
+ test2(roa_prefix_set_ipv6, "2002:0a00:002c::1/128", "2002:0a00:002c::/120-128")
+ print
+ test3(resource_set_ipv4, "10.0.0.44/32,10.6.0.2/32", "10.3.0.0/24,10.0.0.77/32")
+ print
+ test3(resource_set_ipv6, "2002:0a00:002c::1/128", "2002:0a00:002c::2/128")
+ print
+ test3(resource_set_ipv6, "2002:0a00:002c::1/128", "2002:0a00:002c::/120")