'How can I generate a random IP address from a list of IP ranges in Python?

How can I generate a random IP address from a list of IP ranges in Python?

I tried to create a list of ip address using ipaddress module then make it random.

ipRanges = [
    "103.112.2.0/24"
]
network = ipaddress.IPv4Network(random.choice(ipRanges))
ip= random.choice(list(network))

this is my current code. it worked, But it uses a many RAM because it needs to be stored in a list Any better ways?



Solution 1:[1]

You can use the netmask and the address from the IPNetwork object to construct an IP address within the range:

import ipaddress
import random

# Pick a CIDR block to use
ipRanges = [
    "103.112.2.0/24",
    "1.0.0.0/8",
]
network = ipaddress.IPv4Network(random.choice(ipRanges))

# Get the netmask and address as a list of bytes
netmask = network.netmask.packed
address = network.network_address.packed

# Operate on each byte of the address
picked = b''
for netmask_byte, address_byte in zip(netmask, address):
    # Pick a random value
    val = random.randint(0, 255)
    # Or in the random bits not in the netmask with the bits in the address in the netmask
    picked += bytes([((255 ^ netmask_byte) & random.randint(0, 255)) | address_byte])
# Turn the result into an IP address
picked = ipaddress.IPv4Address(picked)

print(picked)

Solution 2:[2]

Instead of generating all the possible IPs and picking one, you should just threat the IP as 32-bit integer and use the network address as the fixed most significant bits and generate a random host part.

import ipaddress, random, struct

def random_ip(network):
    network = ipaddress.IPv4Network(network)
    network_int, = struct.unpack("!I", network.network_address.packed) # make network address into an integer
    rand_bits = network.max_prefixlen - network.prefixlen # calculate the needed bits for the host part
    rand_host_int = random.randint(0, 2**rand_bits - 1) # generate random host part
    ip_address = ipaddress.IPv4Address(network_int + rand_host_int) # combine the parts 
    return ip_address.exploded

This gives you:

>>> for _ in range(4):
    print(random_ip("103.112.2.0/24"))

103.112.2.143
103.112.2.120
103.112.2.75
103.112.2.59

>>> for _ in range(4):
    print(random_ip("103.112.164.0/22"))

103.112.165.120
103.112.164.219
103.112.165.159
103.112.166.232

>>> for _ in range(4):
    print(random_ip("103.112.164.0/255.255.252.0"))

103.112.165.17
103.112.165.210
103.112.167.184
103.112.166.171

>>> for _ in range(4):
    print(random_ip("0.0.0.0/0"))

115.231.216.55
252.123.19.92
218.198.69.183
179.73.234.254

Solution 3:[3]

I am assuming you're only interested in IPv4 addresses, since that's what you show in your example. The answer gets more complicated if you need to do IPv6 addresses too.

Making random selections will be much easier if you can convert the range of IP addresses into a range of integers. Fortunately ipaddress makes this simple even if it's not straightforward.

import ipaddress
import struct

def ip_range(addr_spec):
    ip = ipaddress.ip_network(addr_spec)
    base = struct.unpack('>I', ip.network_address.packed)[0]
    mask = struct.unpack('>I', ip.netmask.packed)[0]
    extent = 0x100000000 - mask
    return range(base, base+extent)

So now you can convert your list into something easier to work with:

ranges = [ip_range(spec) for spec in ipRanges]

You probably want each IP address to be equally likely to be chosen, so you need to make a table of weights corresponding to the size of each range.

weights = [len(r) for r in ranges]

From here the process is simple. First you choose a range at random, then randomly select a specific address within the range.

import random

rng = random.choices(ranges, weights=weights)[0]
ip_int = random.choice(rng)
ip_addr = ipaddress.ip_address(ip_int)

The reason this is more efficient is that starting with Python 3, range returns a range object rather than a list. This object is hyper-optimized; all it stores is the parameters that were passed in, and a little simple math is all that's required to implement the interface - there's no need to precompute all the values and hold them in memory. For example, len(range) is roughly (stop - start) // step. range[i] is roughly start + i * step. Obviously the full implementation is a little more complex than that, but the memory usage and time complexity are still O(1).

Solution 4:[4]

how about this

>>> import random
>>> ".".join(str(random.randint(0,255)) for _ in range(4))
'102.131.173.132'
>>> ".".join(str(random.randint(0,255)) for _ in range(4))
'13.58.135.8'
>>> ".".join(str(random.randint(0,255)) for _ in range(4))
'60.224.20.244'
>>> 

The above can be specialize into a function that take any combination of numbers or a tuple of 2 number (a,b) and in the latter case pick a random number in the interval [a,b], and if less that 4 elements are given fill the missing part with a random number in the range 0-255

>>> def random_ip(*parts):
        ip_part=[]
        for p in parts:
            if isinstance(p,int):
                ip_part.append(p)
            else:
                ip_part.append(random.randint(*p))
        while len(ip_part)<4:
            ip_part.append(random.randint(0,255))
        return ".".join(map(str,ip_part))

>>> random_ip(103,112,2,(0,24))
'103.112.2.17'
>>> random_ip(103,112,2,(0,24))
'103.112.2.21'
>>> random_ip(103,112,2,(0,24))
'103.112.2.7'
>>> random_ip(103,112,2,(0,24))
'103.112.2.18'
>>>     
>>> random_ip(103,(100,200),2,(0,24))
'103.184.2.5'
>>> random_ip(103,(100,200),2,(0,24))
'103.149.2.11'
>>> random_ip(103,(100,200),2,(0,24))
'103.161.2.6'

>>> # 103.112.2.0/24
>>> random_ip(103,112,2)
'103.112.2.229'
>>> random_ip(103,112,2)
'103.112.2.91'
>>> random_ip(103,112,2)
'103.112.2.203'
>>> random_ip(103,112,2)
'103.112.2.98'

>>> # 103.112.164.0/22
>>> random_ip(103,112,(164,167))
'103.112.165.78'
>>> random_ip(103,112,(164,167))
'103.112.165.241'
>>> random_ip(103,112,(164,167))
'103.112.167.192'
>>> random_ip(103,112,(164,167))
'103.112.167.25'
>>>     

Solution 5:[5]

I adapted @Mark Ransom's excellent answer and created https://github.com/luckman212/random-subnet.py which outputs random subnets in CIDR notation, in case anyone else finds that useful.

Example

$ random-subnet.py
172.19.187.0/24

$ random-subnet.py 29
10.118.232.192/29

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Anon Coward
Solution 2 gre_gor
Solution 3
Solution 4
Solution 5 luckman212