1# This file is part of Hypothesis, which may be found at
2# https://github.com/HypothesisWorks/hypothesis/
3#
4# Copyright the Hypothesis Authors.
5# Individual contributors are listed in AUTHORS.rst and the git log.
6#
7# This Source Code Form is subject to the terms of the Mozilla Public License,
8# v. 2.0. If a copy of the MPL was not distributed with this file, You can
9# obtain one at https://mozilla.org/MPL/2.0/.
10
11from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network, ip_network
12from typing import Literal, Optional, Union
13
14from hypothesis.errors import InvalidArgument
15from hypothesis.internal.validation import check_type
16from hypothesis.strategies._internal.core import binary, sampled_from
17from hypothesis.strategies._internal.numbers import integers
18from hypothesis.strategies._internal.strategies import SearchStrategy
19from hypothesis.strategies._internal.utils import defines_strategy
20
21# See https://www.iana.org/assignments/iana-ipv4-special-registry/
22SPECIAL_IPv4_RANGES = (
23 "0.0.0.0/8",
24 "10.0.0.0/8",
25 "100.64.0.0/10",
26 "127.0.0.0/8",
27 "169.254.0.0/16",
28 "172.16.0.0/12",
29 "192.0.0.0/24",
30 "192.0.0.0/29",
31 "192.0.0.8/32",
32 "192.0.0.9/32",
33 "192.0.0.10/32",
34 "192.0.0.170/32",
35 "192.0.0.171/32",
36 "192.0.2.0/24",
37 "192.31.196.0/24",
38 "192.52.193.0/24",
39 "192.88.99.0/24",
40 "192.168.0.0/16",
41 "192.175.48.0/24",
42 "198.18.0.0/15",
43 "198.51.100.0/24",
44 "203.0.113.0/24",
45 "240.0.0.0/4",
46 "255.255.255.255/32",
47)
48# and https://www.iana.org/assignments/iana-ipv6-special-registry/
49SPECIAL_IPv6_RANGES = (
50 "::1/128",
51 "::/128",
52 "::ffff:0:0/96",
53 "64:ff9b::/96",
54 "64:ff9b:1::/48",
55 "100::/64",
56 "2001::/23",
57 "2001::/32",
58 "2001:1::1/128",
59 "2001:1::2/128",
60 "2001:2::/48",
61 "2001:3::/32",
62 "2001:4:112::/48",
63 "2001:10::/28",
64 "2001:20::/28",
65 "2001:db8::/32",
66 "2002::/16",
67 "2620:4f:8000::/48",
68 "fc00::/7",
69 "fe80::/10",
70)
71
72
73@defines_strategy(force_reusable_values=True)
74def ip_addresses(
75 *,
76 v: Optional[Literal[4, 6]] = None,
77 network: Optional[Union[str, IPv4Network, IPv6Network]] = None,
78) -> SearchStrategy[Union[IPv4Address, IPv6Address]]:
79 r"""Generate IP addresses - ``v=4`` for :class:`~python:ipaddress.IPv4Address`\ es,
80 ``v=6`` for :class:`~python:ipaddress.IPv6Address`\ es, or leave unspecified
81 to allow both versions.
82
83 ``network`` may be an :class:`~python:ipaddress.IPv4Network` or
84 :class:`~python:ipaddress.IPv6Network`, or a string representing a network such as
85 ``"127.0.0.0/24"`` or ``"2001:db8::/32"``. As well as generating addresses within
86 a particular routable network, this can be used to generate addresses from a
87 reserved range listed in the
88 `IANA <https://www.iana.org/assignments/iana-ipv4-special-registry/>`__
89 `registries <https://www.iana.org/assignments/iana-ipv6-special-registry/>`__.
90
91 If you pass both ``v`` and ``network``, they must be for the same version.
92 """
93 if v is not None:
94 check_type(int, v, "v")
95 if v not in (4, 6):
96 raise InvalidArgument(f"{v=}, but only v=4 or v=6 are valid")
97 if network is None:
98 # We use the reserved-address registries to boost the chance
99 # of generating one of the various special types of address.
100 four = binary(min_size=4, max_size=4).map(IPv4Address) | sampled_from(
101 SPECIAL_IPv4_RANGES
102 ).flatmap(lambda network: ip_addresses(network=network))
103 six = binary(min_size=16, max_size=16).map(IPv6Address) | sampled_from(
104 SPECIAL_IPv6_RANGES
105 ).flatmap(lambda network: ip_addresses(network=network))
106 if v == 4:
107 return four
108 if v == 6:
109 return six
110 return four | six
111 if isinstance(network, str):
112 network = ip_network(network)
113 check_type((IPv4Network, IPv6Network), network, "network")
114 assert isinstance(network, (IPv4Network, IPv6Network)) # for Mypy
115 if v not in (None, network.version):
116 raise InvalidArgument(f"{v=} is incompatible with {network=}")
117 addr_type = IPv4Address if network.version == 4 else IPv6Address
118 return integers(int(network[0]), int(network[-1])).map(addr_type) # type: ignore