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?
35 Answers
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) 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 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).
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' >>> 7I adapted @Mark Ransom's excellent answer and created 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