def check_for_overlaps(self): """Checks if there already are subnets in the database that overlap with this subnet. Overlap is defined as having an ip_min-ip_max range that overlaps with an existing (= in DB) subnet's ip_min-ip_max range. Raises SubnetError if there are overlaps. """ binds = {"min": self.ip_min, "max": self.ip_max} overlap_entries = self.query( """SELECT subnet_ip, ip_min, ip_max FROM [:table schema=cerebrum name=dns_ipv6_subnet] WHERE NOT (:max < ip_min OR :min > ip_max)""", binds) if overlap_entries: overlaps = [ "%s/%s" % (o['subnet_ip'], IPv6Subnet.calculate_subnet_mask(o['ip_min'], o['ip_max'])) for o in overlap_entries ] raise SubnetError( "Subnet '%s/%s' overlaps with the following " "subnet(s): '%s'" % (self.subnet_ip, self.subnet_mask, "', '".join(overlaps)))
def delete(self, perform_checks=True): if perform_checks: if self.has_adresses_in_use(): raise SubnetError( "Subnet '%s/%s' cannot be deleted; it has addresses in use" % (self.subnet_ip, self.subnet_mask)) # Revoke BofhdAuthRoles associated with subnet baot = BofhdAuthOpTarget(self._db) bar = BofhdAuthRole(self._db) targets = [x['op_target_id'] for x in baot.list(entity_id=self.entity_id)] if targets: for target in targets: for x in bar.list(op_target_id=target): bar.revoke_auth(*x) bar.commit() # Remove BofhdAuthOpTarget associated with subnet for x in targets: baot.clear() try: baot.find(x) baot.delete() baot.commit() except NotFoundError: pass self._db.log_change(self.entity_id, self.const.subnet_delete, None) if self.__in_db: self.execute(""" DELETE FROM [:table schema=cerebrum name=dns_subnet] WHERE entity_id=:e_id""", {'e_id': self.entity_id}) self.__super.delete()
def check_reserved_addresses_in_use(self): """TODO: DOC """ # Need to import Utils here, since Utils imports this module, # and we cannot have circular module dependencies. from Cerebrum.modules.dns.Utils import Find default_zone = self.const.DnsZone(getattr(cereconf, 'DNS_DEFAULT_ZONE', 'uio')) find = Find(self._db, default_zone) ip_number = IPNumber.IPNumber(self._db) ip_number.clear() res_adr_in_use = [] for row in ip_number.find_in_range(self.ip_min, self.ip_max): current_address = long(row['ipnr']) if current_address in self.reserved_adr: res_adr_in_use.append(IPCalc.long_to_ip(current_address)) if res_adr_in_use: res_adr_in_use.sort() raise SubnetError( "The following reserved ip's are already in use " + "on (new?) subnet %s/%s: " % (self.subnet_ip, self.subnet_mask) + "'%s'." % (', '.join(res_adr_in_use)))
def validate_subnet(subnet): """Validates that a subnet specification is correctly formatted and with legal values. Raises SubnetError if invalid. """ try: ip, mask = subnet.split('/') mask = int(mask) except ValueError: raise SubnetError("Not a valid subnet '%s'" % subnet) if not IPv6Utils.is_valid_ipv6(ip): raise SubnetError("Invalid adress: %s" % ip) # TODO 25-08-2015: # Since Subnet-masks for IPv6 are not properly implemented yet, this # will validate an unspecified subnet mask (''), enabling bofh-users # to specify a subnet like this: '2007:700:111:1/'. This should be # fixed when/if proper subnet-handling is implemented. if mask < 0 or mask > 128: raise SubnetError("Invalid subnet mask '%s'; " "outside range 0-128" % mask) return True
def delete(self, perform_checks=True): if perform_checks: if self.has_adresses_in_use(): raise SubnetError( "Subnet '%s/%s' cannot be deleted; it has addresses in use" % ( self.subnet_ip, self.subnet_mask)) self._db.log_change(self.entity_id, self.const.subnet6_delete, None) if self.__in_db: self.execute(""" DELETE FROM [:table schema=cerebrum name=dns_ipv6_subnet] WHERE entity_id=:e_id""", {'e_id': self.entity_id}) self.__super.delete()
def validate_subnet(subnet): """Validates that a subnet specification is correctly formatted and with legal values. Raises SubnetError if invalid. """ try: ip, mask = subnet.split('/') mask = int(mask) except ValueError: raise SubnetError("Not a valid subnet '%s'" % subnet) if len(ip.split('.')) == 3: ip = ip + ".0" elif len(ip.split('.')) != 4: raise SubnetError("Invalid number of segments in '%s'. " "Should be 3 or 4" % ip) for element in ip.split('.'): if int(element) < 0 or int(element) > 255: raise SubnetError("Element out of range in '%s': '%s'" % (ip, element)) if mask < 0 or mask > 32: raise SubnetError("Invalid subnet mask '%s'; outside range 0-32" % mask) return True
def calculate_reserved_addresses(self): """TODO: DOC """ # If no_of_reserved_adr is 0, then only the net and broadcast # address should be reserved; typically for small, special # purpose subnets. if self.no_of_reserved_adr == 0: self.reserved_adr = set() self.reserved_adr.add(self.ip_min) # Add the broadcast unless /32 net if self.ip_min < self.ip_max: self.reserved_adr.add(self.ip_max) return # First, check that we aren't trying to reserve more addresses # than there are in the subnet. Don't count the subnet's # address itself, since the number is for counting addresses # after it. if (self.ip_min + self.no_of_reserved_adr) > self.ip_max: raise SubnetError( "Trying to reserve %i addresses in a subnet" " that has %i addresses available." " You can't do that! Because it's *wrong*" % (self.no_of_reserved_adr, self.ip_max - self.ip_min)) # Designate the first X adresses in subnet (not counting the # subnet's address itself) as reserved, where X equals the # number specifically set as reserved addresses. self.reserved_adr = set(x + self.ip_min + 1 for x in range(self.no_of_reserved_adr)) # Make sure the first (subnet address) and last (broadcast) # addresses in the subnet is set as reserved. self.reserved_adr.add(self.ip_min) self.reserved_adr.add(self.ip_max) # /22 and /23 nets have some intermediate adresses reserved in # order to ease future netsplits. if self.subnet_mask == 22: self.reserved_adr.update(x + self.ip_min for x in [255, 256] + [255 * 2 + 1, 255 * 2 + 2]) if self.subnet_mask == 23: self.reserved_adr.update(x + self.ip_min for x in [255, 256])
def check_reserved_addresses_in_use(self): """Raise a SubnetError if this subnet has addresses in use, that are really reserved. """ ip_number = IPv6Number.IPv6Number(self._db) ip_number.clear() res_adr_in_use = [] for row in ip_number.find_in_range(self.ip_min, self.ip_max): current_address = IPv6Calc.ip_to_long(row['aaaa_ip']) if current_address in self.reserved_adr: res_adr_in_use.append(row['aaaa_ip']) if res_adr_in_use: res_adr_in_use.sort() raise SubnetError("The following reserved ip's are already in " + "use on (new?) subnet %s/%s: " % (self.subnet_ip, self.subnet_mask) + "'%s'." % (', '.join(res_adr_in_use)))
def check_reserved_addresses_in_use(self): """TODO: DOC """ ip_number = IPNumber.IPNumber(self._db) ip_number.clear() res_adr_in_use = [] for row in ip_number.find_in_range(self.ip_min, self.ip_max): current_address = long(row['ipnr']) if current_address in self.reserved_adr: res_adr_in_use.append(IPCalc.long_to_ip(current_address)) if res_adr_in_use: res_adr_in_use.sort() raise SubnetError( "The following reserved ip's are already in use" " on (new?) subnet %s/%s: '%s'." % (self.subnet_ip, self.subnet_mask, ', '.join(res_adr_in_use)))
def delete(self, perform_checks=True): if perform_checks and self.has_adresses_in_use(): raise SubnetError( "Subnet '%s/%s' cannot be deleted; it has addresses in use" % (self.subnet_ip, self.subnet_mask)) if self.__in_db: binds = {'e_id': self.entity_id} exists_stmt = """ SELECT EXISTS ( SELECT 1 FROM [:table schema=cerebrum name=dns_ipv6_subnet] WHERE entity_id=:e_id ) """ if self.query_1(exists_stmt, binds): delete_stmt = """ DELETE FROM [:table schema=cerebrum name=dns_ipv6_subnet] WHERE entity_id=:e_id""" self.execute(delete_stmt, binds) self._db.log_change(self.entity_id, self.clconst.subnet6_delete, None) self.__super.delete()
def calculate_reserved_addresses(self): """Populates the list over reserved addresses. """ # If no_of_reserved_adr is 0 if self.no_of_reserved_adr == 0: self.reserved_adr = set() return # First, check that we aren't trying to reserve more addresses # than there are in the subnet. Don't count the subnet's # address itself, since the number is for counting addresses # after it. if (self.ip_min + self.no_of_reserved_adr) > self.ip_max: raise SubnetError( "Trying to reserve %i addresses in a subnet " % self.no_of_reserved_adr + "that has %i addresses available. " % (self.ip_max - self.ip_min) + "You can't do that! Because it's *wrong*") # Designate the first X adresses in subnet as reserved, # where X equals the, number specifically set as reserved addresses. self.reserved_adr = set([x + self.ip_min for x in range(self.no_of_reserved_adr)])
def find(self, identifier): """Find and instantiate the subnet entity with data from the db. @type identifier: mixed @param identifier: The identifier of the Subnet. Note that the DNS module behaves a bit differently than other Cerebrum modules, in that this find method accepts other input than entity_id. Possibilities are: - A string containing the entity_id, prefixed with 'entity_id:' or 'id:'. - A string with a subnet address, e.g. '2002:123:111::/64'. - A string with an IPv6 address, e.g. '2001:700:111::0:12'. """ binds = {} if identifier is None: raise SubnetError("Unable to find IPv6 subnet identified" " by '%s'" % identifier) if isinstance(identifier, (int, long)): # The proper way of running find() where_param = "entity_id = :e_id" binds['e_id'] = identifier elif identifier.startswith('id:') or identifier.startswith( 'entity_id:'): # E.g. 'id:X' or 'entity_id:X'; where_param = "entity_id = :e_id" try: binds['e_id'] = int(identifier.split(':')[1]) except ValueError: raise SubnetError("Entity ID must be an integer") elif identifier.find('/') > 0: # A '/' indicates a subnet spec: just need the ip where_param = "subnet_ip = :subnet_ip" subnet_ip = identifier.split('/')[0] binds['subnet_ip'] = IPv6Utils.explode(subnet_ip) else: # Last valid type is simply an IP; need to find correct range where_param = "ip_min <= :ip AND ip_max >= :ip" binds['ip'] = IPv6Calc.ip_to_long(identifier) try: (eid, self.subnet_ip, self.ip_min, self.ip_max, self.description, self.dns_delegated, self.name_prefix, self.vlan_number, self.no_of_reserved_adr) = self.query_1( """ SELECT entity_id, subnet_ip, ip_min, ip_max, description, dns_delegated, name_prefix, vlan_number, no_of_reserved_adr FROM [:table schema=cerebrum name=dns_ipv6_subnet] WHERE %s""" % where_param, binds) self.__super.find(eid) self.subnet_mask = IPv6Subnet.calculate_subnet_mask( self.ip_min, self.ip_max) self.calculate_reserved_addresses() except NotFoundError: raise SubnetError("Unable to find IPv6 subnet identified" " by '%s'" % identifier) self.__in_db = True self.__updated = []