Get the next available subnet of a particular size within a supernet

Is there any way in the API or through SQL queries to retrieve the next available subnet in a supernet? For example, if I want to know the next free /28 subnet in a 10.10.10.0/24, how would I do that?

  • This is my really, really nasty way of coming up with an aswer for now, in case anyone else is interested. It takes the entire supernet's worth of IP address entries into memory in order to accomplish the task, so don't try this with a big subnet (I'm using /16's and it taking a little bit.) I'm really hoping for an elegant solution from Solarwinds for this though!

    def getNextAvailableSubnet(supernet: ip.IPv4Network, size: int, numSubnets: int = 1):
        """Retrieves the next available subnet of a particular size within a given supernet.
       
        Keyword Args:
            supernet (ip.IPv4Network): The address of the supernet
            size (int): The CIDR size of the new network
            numSubnets (int): The number of subnets to find

        Raises:
            LookupError: If the supernet doesn't exist
            LookupError: If no valid subnet exists
            ValueError: If an incorrect size or numSubnets was supplied

        Returns:
            List: A list of IPv4Network objects representing the next free subnets
        """
       
        if numSubnets <= 0:
            raise ValueError('An unusable number of networks was supplied ({})'.format(numSubnets))

        if size <= supernet.prefixlen:
            raise ValueError('A new network CIDR was supplied that is larger than the parent CIDR')

        # Make sure the supernet exists and get its internal ID number
        try:
            supernetID = getSubnetAttributes(net=supernet)
        except LookupError as err:
            # The supernet doesn't exist
            print(err)
            raise

        def getNextNetwork(addrs):
            newNet = None
            # Iterate over the list of possible subnets and check if each one has been used yet.
            for net in supernet.subnets(new_prefix=size):
                if not net.network_address in supernetAddresses:
                    # We found a potential candidate, now check if it really is fully available
                    validatedAddr = True
                    for addr in net:
                        addr = addr.compressed
                        # print('{} in supernetAddresses: {}'.format(addr, addr in supernetAddresses))
                        if addr in supernetAddresses: # Check all addresses to see if they are used
                            validatedAddr = False
                            break
                    if validatedAddr: # A valid address was found
                        # print('A valid address was found')
                        newNet = net
                        break
           
            # Error out if nothing was found
            if not newNet: raise LookupError('No valid subnet was found')
           
            # print('Subnet Found: ' + newNet.exploded)
           
            return newNet
       
        # This query gets a list of all IP addresses within the supernet
        # For performance reasons, please don't try this on something bigger
        # than a /16
        supernetAddresses = swis.query(
            "SELECT IPAddress "\
            "FROM IPAM.IPNode "\
            "WHERE "\
            "   IPAddressN >= '{}' AND "\
            "   IPAddressN <= '{}' ".format(
                IPtoGUID(supernet.network_address),
                IPtoGUID(supernet.broadcast_address),
                )
            )['results']
        supernetAddresses = [i['IPAddress'] for i in supernetAddresses]

        results = []

        # Find the next available networks
        for x in range(numSubnets):
            nextNet = getNextNetwork(supernetAddresses)
            # Mark the found network as used in our result set
            supernetAddresses.append(nextNet.network_address.compressed)
            print('Got ' + nextNet.exploded)
            results.append(nextNet)
           
        return results
  • I passed your message to an IPAM expert. He's out of the office until Tuesday though.

  • Hi, wyko.ter.haar,

    Currently, there is no API to do this easily. However, it should be possible with a single SWQL query - I will investigate more deeply if this is possible this week

  • Hi,

    Do we have any option available now ?

  • did you look into this? I am also needing to integrate an automated solution for creating /25 networks from /16s in solarwinds with a python script using pyodbc to integrate with the db but am not sure which tables need rows inserted or any dependencies/requirements. 

    The powershell sdk that works with swiss allows you to create one but assumes you know the next available subnet starting value.

    Is the IPAM_Group table the only one that needs an entry for a vlan to be created? 

  • Incase this helps anyone else. I needed to break /16s down into /25s and i used pyodbc to hit the solarwinds sql server as the swis way doesnt like my sql query and refuses the int. The sql orders the ips to give you the last one in this case for a /16. Then i just strip down and rebuild the string. FYI You need to create a read only user on the DB to do it this way.

    From here i will look to call the solarwinds create subnet function.

    query = "SELECT TOP 1 Address, CIDR, AllocSize, FriendlyName FROM IPAM_Group " \
            "where Address like 'x.x.%' ORDER BY " \
            "CAST(PARSENAME([Address], 4) AS INT) DESC, " \
            "CAST(PARSENAME([Address], 3) AS INT) DESC, " \
            "CAST(PARSENAME([Address], 2) AS INT) DESC, " \
            "CAST(PARSENAME([Address], 1) AS INT) DESC"
    results = cursor.execute(query)
    result = {}
    while True:
        row = cursor.fetchone()
        if not row:
            break
        else:
            result['address'] = row.Address
            result['cidr'] = row.CIDR
            result['allocsize'] = row.AllocSize
            result['friendlyname'] = row.FriendlyName
    ip_split = result['address'].split(".")
    ip_split = list(map(int, ip_split))
    
    new_cidr25 = [ip_split[0], ip_split[1]]
    if ip_split[3] == 128:
        new_cidr25.insert(2, ip_split[2] + 1)
        new_cidr25.insert(3, 0)
    elif ip_split[3] == 0:
        new_cidr25.insert(2, ip_split[2])
        new_cidr25.insert(3, 128)
        
    new_cidr25 = str(new_cidr25[0]) + '.' + str(new_cidr25[1]) + '.' + str(new_cidr25[2]) + '.' + str(new_cidr25[3])
    print(new_cidr25)