00001 """Classes dealing with sets of resources.
00002
00003 The basic mechanics of a resource set are the same for any of the
00004 resources we handle (ASNs, IPv4 addresses, or IPv6 addresses), so we
00005 can provide the same operations on any of them, even though the
00006 underlying details vary.
00007
00008 We also provide some basic set operations (union, intersection, etc).
00009
00010 $Id: resource_set.py 1873 2008-06-12 02:49:41Z sra $
00011
00012 Copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
00013
00014 Permission to use, copy, modify, and distribute this software for any
00015 purpose with or without fee is hereby granted, provided that the above
00016 copyright notice and this permission notice appear in all copies.
00017
00018 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH
00019 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
00020 AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT,
00021 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
00022 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
00023 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
00024 PERFORMANCE OF THIS SOFTWARE.
00025 """
00026
00027 import re
00028 import rpki.ipaddrs, rpki.oids, rpki.exceptions
00029
00030
00031
00032
00033 inherit_token = "<inherit>"
00034
00035 class resource_range(object):
00036 """Generic resource range type. Assumes underlying type is some
00037 kind of integer.
00038
00039 This is a virtual class. You probably don't want to use this type
00040 directly.
00041 """
00042
00043 def __init__(self, min, max):
00044 """Initialize and sanity check a resource_range."""
00045 assert min <= max, "Mis-ordered range: %s before %s" % (str(min), str(max))
00046 self.min = min
00047 self.max = max
00048
00049 def __cmp__(self, other):
00050 """Compare two resource_range objects."""
00051 assert self.__class__ is other.__class__
00052 c = self.min - other.min
00053 if c == 0: c = self.max - other.max
00054 if c < 0: c = -1
00055 if c > 0: c = 1
00056 return c
00057
00058 class resource_range_as(resource_range):
00059 """Range of Autonomous System Numbers.
00060
00061 Denotes a single ASN by a range whose min and max values are identical.
00062 """
00063
00064
00065
00066
00067 datum_type = long
00068
00069 def __str__(self):
00070 """Convert a resource_range_as to string format."""
00071 if self.min == self.max:
00072 return str(self.min)
00073 else:
00074 return str(self.min) + "-" + str(self.max)
00075
00076 def to_rfc3779_tuple(self):
00077 """Convert a resource_range_as to tuple format for RFC 3779 ASN.1 encoding."""
00078 if self.min == self.max:
00079 return ("id", self.min)
00080 else:
00081 return ("range", (self.min, self.max))
00082
00083 class resource_range_ip(resource_range):
00084 """Range of (generic) IP addresses.
00085
00086 Prefixes are converted to ranges on input, and ranges that can be
00087 represented as prefixes are written as prefixes on output.
00088
00089 This is a virtual class. You probably don't want to use it directly.
00090 """
00091
00092 def _prefixlen(self):
00093 """Determine whether a resource_range_ip can be expressed as a prefix."""
00094 mask = self.min ^ self.max
00095 if self.min & mask != 0:
00096 return -1
00097 prefixlen = self.datum_type.bits
00098 while mask & 1:
00099 prefixlen -= 1
00100 mask >>= 1
00101 if mask:
00102 return -1
00103 else:
00104 return prefixlen
00105
00106 def __str__(self):
00107 """Convert a resource_range_ip to string format."""
00108 prefixlen = self._prefixlen()
00109 if prefixlen < 0:
00110 return str(self.min) + "-" + str(self.max)
00111 else:
00112 return str(self.min) + "/" + str(prefixlen)
00113
00114 def to_rfc3779_tuple(self):
00115 """Convert a resource_range_ip to tuple format for RFC 3779 ASN.1 encoding."""
00116 prefixlen = self._prefixlen()
00117 if prefixlen < 0:
00118 return ("addressRange", (_long2bs(self.min, self.datum_type.bits, strip = 0),
00119 _long2bs(self.max, self.datum_type.bits, strip = 1)))
00120 else:
00121 return ("addressPrefix", _long2bs(self.min, self.datum_type.bits, prefixlen = prefixlen))
00122
00123 @classmethod
00124 def make_prefix(cls, address, prefixlen):
00125 """Construct a resource range corresponding to a prefix."""
00126 assert isinstance(address, cls.datum_type) and isinstance(prefixlen, (int, long))
00127 assert prefixlen >= 0 and prefixlen <= cls.datum_type.bits, "Nonsensical prefix length: %s" % prefixlen
00128 mask = (1 << (cls.datum_type.bits - prefixlen)) - 1
00129 assert (address & mask) == 0, "Resource not in canonical form: %s/s" % (address, prefixlen)
00130 return cls(cls.datum_type(address), cls.datum_type(address | mask))
00131
00132 class resource_range_ipv4(resource_range_ip):
00133 """Range of IPv4 addresses."""
00134
00135
00136
00137
00138 datum_type = rpki.ipaddrs.v4addr
00139
00140 class resource_range_ipv6(resource_range_ip):
00141 """Range of IPv6 addresses."""
00142
00143
00144
00145
00146 datum_type = rpki.ipaddrs.v6addr
00147
00148 def _rsplit(rset, that):
00149 """Utility function to split a resource range into two resource ranges."""
00150 this = rset.pop(0)
00151 cell_type = type(this.min)
00152 assert type(this) is type(that) and type(this.max) is cell_type and \
00153 type(that.min) is cell_type and type(that.max) is cell_type
00154 if this.min < that.min:
00155 rset.insert(0, type(this)(this.min, cell_type(that.min - 1)))
00156 rset.insert(1, type(this)(that.min, this.max))
00157 else:
00158 assert this.max > that.max
00159 rset.insert(0, type(this)(this.min, that.max))
00160 rset.insert(1, type(this)(cell_type(that.max + 1), this.max))
00161
00162 class resource_set(list):
00163 """Generic resource set.
00164 This is a list subclass containing resource ranges.
00165
00166 This is a virtual class. You probably don't want to use it
00167 directly.
00168 """
00169
00170
00171
00172
00173 inherit = False
00174
00175 def __init__(self, ini = None):
00176 """Initialize a resource_set."""
00177 if isinstance(ini, (int, long)):
00178 ini = str(ini)
00179 if ini == inherit_token:
00180 self.inherit = True
00181 elif isinstance(ini, str) and len(ini):
00182 self.extend(self.parse_str(s) for s in ini.split(","))
00183 elif isinstance(ini, tuple):
00184 self.parse_rfc3779_tuple(ini)
00185 elif isinstance(ini, list):
00186 self.extend(ini)
00187 else:
00188 assert ini is None or ini == "", "Unexpected initializer: %s" % str(ini)
00189 assert not self.inherit or not self
00190 self.sort()
00191 for i in xrange(len(self) - 2, -1, -1):
00192 if self[i].max + 1 == self[i+1].min:
00193 self[i] = type(self[i])(self[i].min, self[i+1].max)
00194 self.pop(i + 1)
00195 if __debug__:
00196 for i in xrange(0, len(self) - 1):
00197 assert self[i].max < self[i+1].min, "Resource overlap: %s %s" % (self[i], self[i+1])
00198
00199 def __str__(self):
00200 """Convert a resource_set to string format."""
00201 if self.inherit:
00202 return inherit_token
00203 else:
00204 return ",".join(str(x) for x in self)
00205
00206 def _comm(self, other):
00207 """Like comm(1), sort of.
00208
00209 Returns a tuple of three resource sets: resources only in self,
00210 resources only in other, and resources in both. Used (not very
00211 efficiently) as the basis for most set operations on resource
00212 sets.
00213 """
00214 assert not self.inherit
00215 assert type(self) is type(other), "Type mismatch %s %s" % (repr(type(self)), repr(type(other)))
00216 set1 = self[:]
00217 set2 = other[:]
00218 only1, only2, both = [], [], []
00219 while set1 or set2:
00220 if set1 and (not set2 or set1[0].max < set2[0].min):
00221 only1.append(set1.pop(0))
00222 elif set2 and (not set1 or set2[0].max < set1[0].min):
00223 only2.append(set2.pop(0))
00224 elif set1[0].min < set2[0].min:
00225 _rsplit(set1, set2[0])
00226 elif set2[0].min < set1[0].min:
00227 _rsplit(set2, set1[0])
00228 elif set1[0].max < set2[0].max:
00229 _rsplit(set2, set1[0])
00230 elif set2[0].max < set1[0].max:
00231 _rsplit(set1, set2[0])
00232 else:
00233 assert set1[0].min == set2[0].min and set1[0].max == set2[0].max
00234 both.append(set1.pop(0))
00235 set2.pop(0)
00236 return type(self)(only1), type(self)(only2), type(self)(both)
00237
00238 def union(self, other):
00239 """Set union for resource sets."""
00240 assert not self.inherit
00241 assert type(self) is type(other), "Type mismatch: %s %s" % (repr(type(self)), repr(type(other)))
00242 set1 = self[:]
00243 set2 = other[:]
00244 result = []
00245 while set1 or set2:
00246 if set1 and (not set2 or set1[0].max < set2[0].min):
00247 result.append(set1.pop(0))
00248 elif set2 and (not set1 or set2[0].max < set1[0].min):
00249 result.append(set2.pop(0))
00250 else:
00251 this = set1.pop(0)
00252 that = set2.pop(0)
00253 assert type(this) is type(that)
00254 if this.min < that.min: min = this.min
00255 else: min = that.min
00256 if this.max > that.max: max = this.max
00257 else: max = that.max
00258 result.append(type(this)(min, max))
00259 return type(self)(result)
00260
00261 def intersection(self, other):
00262 """Set intersection for resource sets."""
00263 return self._comm(other)[2]
00264
00265 def difference(self, other):
00266 """Set difference for resource sets."""
00267 return self._comm(other)[0]
00268
00269 def symmetric_difference(self, other):
00270 """Set symmetric difference (XOR) for resource sets."""
00271 com = self._comm(other)
00272 return com[0].union(com[1])
00273
00274 def contains(self, item):
00275 """Set membership test for resource sets."""
00276 assert not self.inherit
00277 for i in self:
00278 if isinstance(item, type(i)) and i.min <= item.min and i.max >= item.max:
00279 return True
00280 elif isinstance(item, type(i.min)) and i.min <= item and i.max >= item:
00281 return True
00282 else:
00283 assert isinstance(item, (type(i), type(i.min)))
00284 return False
00285
00286 def issubset(self, other):
00287 """Test whether self is a subset (possibly improper) of other."""
00288 for i in self:
00289 if not other.contains(i):
00290 return False
00291 return True
00292
00293 def issuperset(self, other):
00294 """Test whether self is a superset (possibly improper) of other."""
00295 return other.issubset(self)
00296
00297 @classmethod
00298 def from_sql(cls, sql, query, args = None):
00299 """Create resource set from an SQL query.
00300
00301 sql is an object that supports execute() and fetchall() methods
00302 like a DB API 2.0 cursor object.
00303
00304 query is an SQL query that returns a sequence of (min, max) pairs.
00305 """
00306
00307 sql.execute(query, args)
00308 return cls(ini = [cls.range_type(cls.range_type.datum_type(b),
00309 cls.range_type.datum_type(e))
00310 for (b,e) in sql.fetchall()])
00311
00312 class resource_set_as(resource_set):
00313 """ASN resource set."""
00314
00315
00316
00317
00318 range_type = resource_range_as
00319
00320 def parse_str(self, x):
00321 """Parse ASN resource sets from text (eg, XML attributes)."""
00322 r = re.match("^([0-9]+)-([0-9]+)$", x)
00323 if r:
00324 return resource_range_as(long(r.group(1)), long(r.group(2)))
00325 else:
00326 return resource_range_as(long(x), long(x))
00327
00328 def parse_rfc3779_tuple(self, x):
00329 """Parse ASN resource from tuple format generated by RFC 3779 ASN.1 decoder."""
00330 if x[0] == "asIdsOrRanges":
00331 for aor in x[1]:
00332 if aor[0] == "range":
00333 min = aor[1][0]
00334 max = aor[1][1]
00335 else:
00336 min = aor[1]
00337 max = min
00338 self.append(resource_range_as(min, max))
00339 else:
00340 assert x[0] == "inherit"
00341 self.inherit = True
00342
00343 def to_rfc3779_tuple(self):
00344 """Convert ASN resource set into tuple format used for RFC 3779 ASN.1 encoding."""
00345 if self:
00346 return ("asIdsOrRanges", tuple(a.to_rfc3779_tuple() for a in self))
00347 elif self.inherit:
00348 return ("inherit", "")
00349 else:
00350 return None
00351
00352 class resource_set_ip(resource_set):
00353 """(Generic) IP address resource set.
00354
00355 This is a virtual class. You probably don't want to use it
00356 directly.
00357 """
00358
00359 def parse_str(self, x):
00360 """Parse IP address resource sets from text (eg, XML attributes)."""
00361 r = re.match("^([0-9:.a-fA-F]+)-([0-9:.a-fA-F]+)$", x)
00362 if r:
00363 return self.range_type(self.range_type.datum_type(r.group(1)), self.range_type.datum_type(r.group(2)))
00364 r = re.match("^([0-9:.a-fA-F]+)/([0-9]+)$", x)
00365 if r:
00366 return self.range_type.make_prefix(self.range_type.datum_type(r.group(1)), int(r.group(2)))
00367 raise RuntimeError, 'Bad IP resource "%s"' % (x)
00368
00369 def parse_rfc3779_tuple(self, x):
00370 """Parse IP address resource sets from tuple format generated by RFC 3779 ASN.1 decoder."""
00371 if x[0] == "addressesOrRanges":
00372 for aor in x[1]:
00373 if aor[0] == "addressRange":
00374 min = _bs2long(aor[1][0]) << (self.range_type.datum_type.bits - len(aor[1][0]))
00375 max = _bs2long(aor[1][1]) << (self.range_type.datum_type.bits - len(aor[1][1]))
00376 mask = (1L << (self.range_type.datum_type.bits - len(aor[1][1]))) - 1
00377 else:
00378 min = _bs2long(aor[1]) << (self.range_type.datum_type.bits - len(aor[1]))
00379 mask = (1L << (self.range_type.datum_type.bits - len(aor[1]))) - 1
00380 assert (min & mask) == 0, "Resource not in canonical form: %s" % (str(x))
00381 max = min | mask
00382 self.append(self.range_type(self.range_type.datum_type(min), self.range_type.datum_type(max)))
00383 else:
00384 assert x[0] == "inherit"
00385 self.inherit = True
00386
00387 def to_rfc3779_tuple(self):
00388 """Convert IP resource set into tuple format used by RFC 3779 ASN.1 encoder."""
00389 if self:
00390 return (self.afi, ("addressesOrRanges", tuple(a.to_rfc3779_tuple() for a in self)))
00391 elif self.inherit:
00392 return (self.afi, ("inherit", ""))
00393 else:
00394 return None
00395
00396 class resource_set_ipv4(resource_set_ip):
00397 """IPv4 address resource set."""
00398
00399
00400
00401
00402 range_type = resource_range_ipv4
00403
00404
00405
00406
00407 afi = "\x00\x01"
00408
00409 class resource_set_ipv6(resource_set_ip):
00410 """IPv6 address resource set."""
00411
00412
00413
00414
00415 range_type = resource_range_ipv6
00416
00417
00418
00419
00420 afi = "\x00\x02"
00421
00422 def _bs2long(bs):
00423 """Utility function to convert a bitstring (POW.pkix tuple
00424 representation) into a Python long.
00425 """
00426 return reduce(lambda x, y: (x << 1) | y, bs, 0L)
00427
00428 def _long2bs(number, addrlen, prefixlen = None, strip = None):
00429 """Utility function to convert a Python long into a POW.pkix tuple
00430 bitstring. This is a bit complicated because it supports the
00431 fiendishly compact encoding used in RFC 3779.
00432 """
00433 assert prefixlen is None or strip is None
00434 bs = []
00435 while number:
00436 bs.append(int(number & 1))
00437 number >>= 1
00438 if addrlen > len(bs):
00439 bs.extend((0 for i in xrange(addrlen - len(bs))))
00440 bs.reverse()
00441 if prefixlen is not None:
00442 return tuple(bs[0:prefixlen])
00443 if strip is not None:
00444 while bs and bs[-1] == strip:
00445 bs.pop()
00446 return tuple(bs)
00447
00448 class resource_bag(object):
00449 """Container to simplify passing around the usual triple of ASN,
00450 IPv4, and IPv6 resource sets.
00451 """
00452
00453
00454
00455
00456
00457
00458
00459
00460
00461
00462
00463
00464
00465 def __init__(self, asn = None, v4 = None, v6 = None, valid_until = None):
00466 self.asn = asn or resource_set_as()
00467 self.v4 = v4 or resource_set_ipv4()
00468 self.v6 = v6 or resource_set_ipv6()
00469 self.valid_until = valid_until
00470
00471 def oversized(self, other):
00472 """True iff self is oversized with respect to other."""
00473 return not self.asn.issubset(other.asn) or \
00474 not self.v4.issubset(other.v4) or \
00475 not self.v6.issubset(other.v6)
00476
00477 def undersized(self, other):
00478 """True iff self is undersized with respect to other."""
00479 return not other.asn.issubset(self.asn) or \
00480 not other.v4.issubset(self.v4) or \
00481 not other.v6.issubset(self.v6)
00482
00483 @classmethod
00484 def from_rfc3779_tuples(cls, exts):
00485 """Build a resource_bag from intermediate form generated by RFC 3779 ASN.1 decoder."""
00486 asn = None
00487 v4 = None
00488 v6 = None
00489 for x in exts:
00490 if x[0] == rpki.oids.name2oid["sbgp-autonomousSysNum"]:
00491 assert len(x[2]) == 1 or x[2][1] is None, "RDI not implemented: %s" % (str(x))
00492 assert asn is None
00493 asn = resource_set_as(x[2][0])
00494 if x[0] == rpki.oids.name2oid["sbgp-ipAddrBlock"]:
00495 for fam in x[2]:
00496 if fam[0] == resource_set_ipv4.afi:
00497 assert v4 is None
00498 v4 = resource_set_ipv4(fam[1])
00499 if fam[0] == resource_set_ipv6.afi:
00500 assert v6 is None
00501 v6 = resource_set_ipv6(fam[1])
00502 return cls(asn, v4, v6)
00503
00504 def empty(self):
00505 """Return True iff all resource sets in this bag are empty."""
00506 return not self.asn and not self.v4 and not self.v6
00507
00508 def __eq__(self, other):
00509 return self.asn == other.asn and \
00510 self.v4 == other.v4 and \
00511 self.v6 == other.v6 and \
00512 self.valid_until == other.valid_until
00513
00514 def __ne__(self, other):
00515 return not (self == other)
00516
00517 def intersection(self, other):
00518 """Compute intersection with another resource_bag.
00519 valid_until attribute (if any) inherits from self.
00520 """
00521 return self.__class__(self.asn.intersection(other.asn),
00522 self.v4.intersection(other.v4),
00523 self.v6.intersection(other.v6),
00524 self.valid_until)
00525
00526 def union(self, other):
00527 """Compute union with another resource_bag.
00528 valid_until attribute (if any) inherits from self.
00529 """
00530 return self.__class__(self.asn.union(other.asn),
00531 self.v4.union(other.v4),
00532 self.v6.union(other.v6),
00533 self.valid_until)
00534
00535 def __str__(self):
00536 s = ""
00537 if self.asn:
00538 s += "ASN: %s" % self.asn
00539 if self.v4:
00540 if s:
00541 s += ", "
00542 s += "V4: %s" % self.v4
00543 if self.v6:
00544 if s:
00545 s += ", "
00546 s += "V6: %s" % self.v6
00547 return s
00548
00549
00550
00551
00552
00553
00554
00555
00556
00557 class roa_prefix(object):
00558 """ROA prefix. This is similar to the resource_range_ip class, but
00559 differs in that it only represents prefixes, never ranges, and
00560 includes the maximum prefix length as an additional value.
00561
00562 This is a virtual class, you probably don't want to use it directly.
00563 """
00564
00565
00566
00567
00568
00569
00570
00571
00572
00573
00574 def __init__(self, address, prefixlen, max_prefixlen = None):
00575 """Initialize a ROA prefix. max_prefixlen is optional and
00576 defaults to prefixlen. max_prefixlen must not be smaller than
00577 prefixlen.
00578 """
00579 if max_prefixlen is None:
00580 max_prefixlen = prefixlen
00581 assert max_prefixlen >= prefixlen, "Bad max_prefixlen: %d must not be shorter than %d" % (max_prefixlen, prefixlen)
00582 self.address = address
00583 self.prefixlen = prefixlen
00584 self.max_prefixlen = max_prefixlen
00585
00586 def __cmp__(self, other):
00587 """Compare two ROA prefix objects. Comparision is based on
00588 address, prefixlen, and max_prefixlen, in that order.
00589 """
00590 assert self.__class__ is other.__class__
00591 c = self.address - other.address
00592 if c == 0: c = self.prefixlen - other.prefixlen
00593 if c == 0: c = self.max_prefixlen - other.max_prefixlen
00594 if c < 0: c = -1
00595 if c > 0: c = 1
00596 return c
00597
00598 def __str__(self):
00599 """Convert a ROA prefix to string format."""
00600 if self.prefixlen == self.max_prefixlen:
00601 return str(self.address) + "/" + str(self.prefixlen)
00602 else:
00603 return str(self.address) + "/" + str(self.prefixlen) + "-" + str(self.max_prefixlen)
00604
00605 def to_resource_range(self):
00606 """Convert this ROA prefix to the equivilent resource_range_ip
00607 object. This is an irreversable transformation because it loses
00608 the max_prefixlen attribute, nothing we can do about that.
00609 """
00610 return self.range_type.make_prefix(self.address, self.prefixlen)
00611
00612 def min(self):
00613 """Return lowest address covered by prefix."""
00614 return self.address
00615
00616 def max(self):
00617 """Return highest address covered by prefix."""
00618 t = self.range_type.datum_type
00619 return t(self.address | ((1 << (t.bits - self.prefixlen)) - 1))
00620
00621 def to_roa_tuple(self):
00622 """Convert a resource_range_ip to tuple format for ROA ASN.1 encoding."""
00623 return (_long2bs(self.address, self.range_type.datum_type.bits, prefixlen = self.prefixlen),
00624 None if self.prefixlen == self.max_prefixlen else self.max_prefixlen)
00625
00626 class roa_prefix_ipv4(roa_prefix):
00627 """IPv4 ROA prefix."""
00628
00629
00630
00631
00632 range_type = resource_range_ipv4
00633
00634 class roa_prefix_ipv6(roa_prefix):
00635 """IPv6 ROA prefix."""
00636
00637
00638
00639
00640 range_type = resource_range_ipv6
00641
00642 class roa_prefix_set(list):
00643 """Set of ROA prefixes, analogous to the resource_set_ip class."""
00644
00645 def __init__(self, ini = None):
00646 """Initialize a ROA prefix set."""
00647 if isinstance(ini, str) and len(ini):
00648 self.extend(self.parse_str(s) for s in ini.split(","))
00649 elif isinstance(ini, (list, tuple)):
00650 self.extend(ini)
00651 else:
00652 assert ini is None or ini == "", "Unexpected initializer: %s" % str(ini)
00653 self.sort()
00654 if __debug__:
00655 for i in xrange(0, len(self) - 1):
00656 assert self[i].max() < self[i+1].min(), "Prefix overlap: %s %s" % (self[i], self[i+1])
00657
00658 def __str__(self):
00659 """Convert a ROA prefix set to string format."""
00660 return ",".join(str(x) for x in self)
00661
00662 def parse_str(self, x):
00663 """Parse ROA prefix from text (eg, an XML attribute)."""
00664 r = re.match("^([0-9:.a-fA-F]+)/([0-9]+)-([0-9]+)$", x)
00665 if r:
00666 return self.prefix_type(self.prefix_type.range_type.datum_type(r.group(1)), int(r.group(2)), int(r.group(3)))
00667 r = re.match("^([0-9:.a-fA-F]+)/([0-9]+)$", x)
00668 if r:
00669 return self.prefix_type(self.prefix_type.range_type.datum_type(r.group(1)), int(r.group(2)))
00670 raise RuntimeError, 'Bad ROA prefix "%s"' % (x)
00671
00672 def to_resource_set(self):
00673 """Convert a ROA prefix set to a resource set. This is an
00674 irreversable transformation.
00675 """
00676 return self.resource_set_type([p.to_resource_range() for p in self])
00677
00678 @classmethod
00679 def from_sql(cls, sql, query, args = None):
00680 """Create ROA prefix set from an SQL query.
00681
00682 sql is an object that supports execute() and fetchall() methods
00683 like a DB API 2.0 cursor object.
00684
00685 query is an SQL query that returns a sequence of (address,
00686 prefixlen, max_prefixlen) triples.
00687 """
00688
00689 sql.execute(query, args)
00690 return cls([cls.prefix_type(cls.prefix_type.range_type.datum_type(x), int(y), int(z))
00691 for (x,y,z) in sql.fetchall()])
00692
00693 def to_roa_tuple(self):
00694 """Convert ROA prefix set into tuple format used by ROA ASN.1 encoder.
00695 This is a variation on the format used in RFC 3779."""
00696 if self:
00697 return (self.resource_set_type.afi, tuple(a.to_roa_tuple() for a in self))
00698 else:
00699 return None
00700
00701 class roa_prefix_set_ipv4(roa_prefix_set):
00702 """Set of IPv4 ROA prefixes."""
00703
00704
00705
00706
00707 prefix_type = roa_prefix_ipv4
00708
00709
00710
00711
00712 resource_set_type = resource_set_ipv4
00713
00714 class roa_prefix_set_ipv6(roa_prefix_set):
00715 """Set of IPv6 ROA prefixes."""
00716
00717
00718
00719
00720 prefix_type = roa_prefix_ipv6
00721
00722
00723
00724
00725 resource_set_type = resource_set_ipv6
00726
00727
00728
00729 if __name__ == "__main__":
00730
00731 def test1(t, s1, s2):
00732 if isinstance(s1, str) and isinstance(s2, str):
00733 print "x: ", s1
00734 print "y: ", s2
00735 r1 = t(s1)
00736 r2 = t(s2)
00737 print "x: ", r1
00738 print "y: ", r2
00739 v1 = r1._comm(r2)
00740 v2 = r2._comm(r1)
00741 assert v1[0] == v2[1] and v1[1] == v2[0] and v1[2] == v2[2]
00742 for i in r1: assert r1.contains(i) and r1.contains(i.min) and r1.contains(i.max)
00743 for i in r2: assert r2.contains(i) and r2.contains(i.min) and r2.contains(i.max)
00744 for i in v1[0]: assert r1.contains(i) and not r2.contains(i)
00745 for i in v1[1]: assert not r1.contains(i) and r2.contains(i)
00746 for i in v1[2]: assert r1.contains(i) and r2.contains(i)
00747 v1 = r1.union(r2)
00748 v2 = r2.union(r1)
00749 assert v1 == v2
00750 print "x|y:", v1
00751 v1 = r1.difference(r2)
00752 v2 = r2.difference(r1)
00753 print "x-y:", v1
00754 print "y-x:", v2
00755 v1 = r1.symmetric_difference(r2)
00756 v2 = r2.symmetric_difference(r1)
00757 assert v1 == v2
00758 print "x^y:", v1
00759 v1 = r1.intersection(r2)
00760 v2 = r2.intersection(r1)
00761 assert v1 == v2
00762 print "x&y:", v1
00763
00764 def test2(t, s1, s2):
00765 print "x: ", s1
00766 print "y: ", s2
00767 r1 = t(s1)
00768 r2 = t(s2)
00769 print "x: ", r1
00770 print "y: ", r2
00771 print "x>y:", (r1 > r2)
00772 print "x<y:", (r1 < r2)
00773 test1(t.resource_set_type, r1.to_resource_set(), r2.to_resource_set())
00774
00775 print
00776 print "Testing set operations on resource sets"
00777 print
00778 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")
00779 print
00780 test1(resource_set_ipv4, "10.0.0.44/32,10.6.0.2/32", "10.3.0.0/24,10.0.0.77/32")
00781 print
00782 test1(resource_set_ipv4, "10.0.0.44/32,10.6.0.2/32", "10.0.0.0/24")
00783 print
00784 test1(resource_set_ipv4, "10.0.0.0/24", "10.3.0.0/24,10.0.0.77/32")
00785 print
00786 print "Testing set operations on ROA prefixes"
00787 print
00788 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")
00789 print
00790 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")
00791 print
00792 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")
00793 print