ipaddons 0.2.3__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ipaddons-0.2.3 → ipaddons-0.3.0}/PKG-INFO +1 -1
- {ipaddons-0.2.3 → ipaddons-0.3.0}/src/ipaddons/_version.py +2 -2
- {ipaddons-0.2.3 → ipaddons-0.3.0}/src/ipaddons/tools.py +56 -12
- {ipaddons-0.2.3 → ipaddons-0.3.0}/tests/test_ip_allocation.py +34 -1
- {ipaddons-0.2.3 → ipaddons-0.3.0}/.gitignore +0 -0
- {ipaddons-0.2.3 → ipaddons-0.3.0}/LICENSE +0 -0
- {ipaddons-0.2.3 → ipaddons-0.3.0}/README.md +0 -0
- {ipaddons-0.2.3 → ipaddons-0.3.0}/pyproject.toml +0 -0
- {ipaddons-0.2.3 → ipaddons-0.3.0}/src/ipaddons/__init__.py +0 -0
- {ipaddons-0.2.3 → ipaddons-0.3.0}/src/ipaddons/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipaddons
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Additional tools for the ipaddress standard library module
|
|
5
5
|
Project-URL: docs, https://ipaddress-tools.readthedocs.io/stable/
|
|
6
6
|
Project-URL: homepage, https://ipaddress-tools.readthedocs.io/stable/
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.3.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 3, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -17,6 +17,10 @@ if TYPE_CHECKING:
|
|
|
17
17
|
NetworkRange: TypeAlias = tuple[int, int]
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
class IPAllocationError(Exception):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
20
24
|
def ip_allocation(supernet: UnSpecNet, used_networks: UnspecNetSeq | None = None) -> IPv4Allocation | IPv6Allocation:
|
|
21
25
|
"""Create an IP allocation.
|
|
22
26
|
|
|
@@ -42,7 +46,11 @@ def ip_allocation(supernet: UnSpecNet, used_networks: UnspecNetSeq | None = None
|
|
|
42
46
|
raise ValueError(msg)
|
|
43
47
|
|
|
44
48
|
|
|
45
|
-
def
|
|
49
|
+
def _ceildiv(a: int, b: int) -> int:
|
|
50
|
+
return -(a // -b)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def range_overlap(a: NetworkRange, b: NetworkRange) -> bool:
|
|
46
54
|
"""Network Range Overlap Check.
|
|
47
55
|
|
|
48
56
|
:param a: a network range
|
|
@@ -52,7 +60,7 @@ def overlap(a: NetworkRange, b: NetworkRange) -> bool:
|
|
|
52
60
|
return max(a[0], b[0]) < min(a[1], b[1])
|
|
53
61
|
|
|
54
62
|
|
|
55
|
-
def
|
|
63
|
+
def range_overlaps(a: NetworkRange, b: list[NetworkRange]) -> NetworkRange | None:
|
|
56
64
|
"""Overlap between a network range and a list of other network ranges.
|
|
57
65
|
|
|
58
66
|
:param a: Network range to check against.
|
|
@@ -60,7 +68,7 @@ def overlaps(a: NetworkRange, b: list[NetworkRange]) -> NetworkRange | None:
|
|
|
60
68
|
:return: The first range from b that overlaps with a.
|
|
61
69
|
"""
|
|
62
70
|
for x in b:
|
|
63
|
-
if
|
|
71
|
+
if range_overlap(a, x):
|
|
64
72
|
return x
|
|
65
73
|
return None
|
|
66
74
|
|
|
@@ -75,6 +83,29 @@ def netrange(network: IPNetwork) -> NetworkRange:
|
|
|
75
83
|
return int(network[0]), int(network[-1])
|
|
76
84
|
|
|
77
85
|
|
|
86
|
+
def free_ranges(superrange: NetworkRange, used_ranges: list[NetworkRange]) -> Generator[NetworkRange, None, None]:
|
|
87
|
+
"""Get free ranges from a supernet with provided used ranges.
|
|
88
|
+
|
|
89
|
+
:param superrange: Covering range from where free subranges should be returned.
|
|
90
|
+
:param used_ranges: List of used network ranges.
|
|
91
|
+
:return: An interator for free ranges.
|
|
92
|
+
"""
|
|
93
|
+
used_ranges = sorted(used_ranges)
|
|
94
|
+
for range in used_ranges:
|
|
95
|
+
if superrange[0] >= superrange[1]:
|
|
96
|
+
# Last range either ends at or shoots over the supernet range
|
|
97
|
+
return
|
|
98
|
+
if superrange[0] == range[0]:
|
|
99
|
+
# used range is at the beginning, shrink superrange
|
|
100
|
+
superrange = range[1] + 1, superrange[1]
|
|
101
|
+
continue
|
|
102
|
+
free_range = superrange[0], range[0] - 1
|
|
103
|
+
superrange = range[1] + 1, superrange[1]
|
|
104
|
+
yield free_range
|
|
105
|
+
# The rest of the superrange is free when there are no more used ranges left
|
|
106
|
+
yield superrange
|
|
107
|
+
|
|
108
|
+
|
|
78
109
|
def merge_ranges(ranges: list[NetworkRange]) -> list[NetworkRange]:
|
|
79
110
|
"""Merge network ranges that overlap.
|
|
80
111
|
|
|
@@ -89,7 +120,7 @@ def merge_ranges(ranges: list[NetworkRange]) -> list[NetworkRange]:
|
|
|
89
120
|
base = sorted_ranges.pop()
|
|
90
121
|
while len(sorted_ranges):
|
|
91
122
|
range = sorted_ranges.pop()
|
|
92
|
-
if
|
|
123
|
+
if range_overlap(base, range):
|
|
93
124
|
base = min(base[0], range[0]), max(base[1], range[1])
|
|
94
125
|
else:
|
|
95
126
|
merged_ranges.append(base)
|
|
@@ -98,16 +129,28 @@ def merge_ranges(ranges: list[NetworkRange]) -> list[NetworkRange]:
|
|
|
98
129
|
return merged_ranges
|
|
99
130
|
|
|
100
131
|
|
|
101
|
-
def net_size_iterator(
|
|
132
|
+
def net_size_iterator(
|
|
133
|
+
supernet: IPNetwork, cidr: int, used_ranges: list[NetworkRange] | None = None
|
|
134
|
+
) -> Generator[NetworkRange, None, None]:
|
|
102
135
|
"""Iterate over an ipaddr network object and return subnet network ranges of the specified cidr size.
|
|
103
136
|
|
|
104
|
-
:param
|
|
137
|
+
:param supernet: An ipaddr network object.
|
|
105
138
|
:param cidr: CIDR size of the subnets.
|
|
139
|
+
:param used_ranges: A list of used network ranges.
|
|
106
140
|
:return: An interator over all subnets in the network in the specified CIDR size.
|
|
107
141
|
"""
|
|
108
|
-
cidr_size = 2 ** (
|
|
109
|
-
|
|
110
|
-
|
|
142
|
+
cidr_size = 2 ** (supernet.max_prefixlen - cidr)
|
|
143
|
+
supernet_range = int(supernet[0]), int(supernet[-1])
|
|
144
|
+
if used_ranges is None:
|
|
145
|
+
used_ranges = []
|
|
146
|
+
for free_range in free_ranges(supernet_range, used_ranges):
|
|
147
|
+
free_range_boundary = (
|
|
148
|
+
# Large IPv6 integers are tricky...
|
|
149
|
+
_ceildiv(free_range[0], cidr_size) * cidr_size,
|
|
150
|
+
free_range[1] // cidr_size * cidr_size - 1,
|
|
151
|
+
)
|
|
152
|
+
for s in range(free_range_boundary[0], free_range_boundary[1] + cidr_size, cidr_size):
|
|
153
|
+
yield s, s + cidr_size - 1
|
|
111
154
|
|
|
112
155
|
|
|
113
156
|
class _BaseIPAllocation(ABC, Generic[_N]):
|
|
@@ -195,9 +238,10 @@ class _BaseIPAllocation(ABC, Generic[_N]):
|
|
|
195
238
|
:param prefixlen: Size of the free subnets returned in CIDR notation.
|
|
196
239
|
:return: An iterator for free subnets of :py:attr:`prefixlen` size.
|
|
197
240
|
"""
|
|
198
|
-
for n in net_size_iterator(self.supernet, prefixlen):
|
|
199
|
-
if
|
|
200
|
-
|
|
241
|
+
for n in net_size_iterator(self.supernet, prefixlen, self._used_network_ranges):
|
|
242
|
+
if range_overlaps(n, self._used_network_ranges):
|
|
243
|
+
msg = "This should never happen!"
|
|
244
|
+
raise IPAllocationError(msg)
|
|
201
245
|
yield self._network_class((n[0], prefixlen))
|
|
202
246
|
|
|
203
247
|
|
|
@@ -98,12 +98,45 @@ def test_merge(subnets):
|
|
|
98
98
|
covering_prefix = subnets[0]
|
|
99
99
|
assert tools.merge_ranges(subnets) == [covering_prefix]
|
|
100
100
|
|
|
101
|
+
|
|
101
102
|
@pytest.mark.parametrize(
|
|
102
103
|
"subnets",
|
|
103
104
|
mergeable_subnets,
|
|
104
105
|
)
|
|
105
106
|
def test_merge_one(subnets):
|
|
106
|
-
assert
|
|
107
|
+
assert subnets[:1] == tools.merge_ranges(subnets[:1])
|
|
108
|
+
|
|
107
109
|
|
|
108
110
|
def test_merge_empty():
|
|
109
111
|
assert tools.merge_ranges([]) == []
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@pytest.mark.parametrize(
|
|
115
|
+
("supernet", "used_nets", "cidr", "first_free"),
|
|
116
|
+
[
|
|
117
|
+
("2001:db8::/32", ["2001:db8::/48", "2001:db8:1::/120"], 64, "2001:db8:1:1::/64"),
|
|
118
|
+
("10.0.0.0/8", ["10.0.0.0/16", "10.1.0.0/24"], 16, "10.2.0.0/16"),
|
|
119
|
+
],
|
|
120
|
+
)
|
|
121
|
+
def test_netsize_iterator(supernet, used_nets, cidr, first_free):
|
|
122
|
+
supernet = ipaddress.ip_network(supernet)
|
|
123
|
+
used_nets = [(int(ipaddress.ip_network(n)[0]), int(ipaddress.ip_network(n)[-1])) for n in used_nets]
|
|
124
|
+
i = tools.net_size_iterator(supernet, cidr, used_nets)
|
|
125
|
+
first_ip, last_ip = next(i)
|
|
126
|
+
net = next(ipaddress.summarize_address_range(ipaddress.ip_address(first_ip), ipaddress.ip_address(last_ip)))
|
|
127
|
+
assert first_free == str(net)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@pytest.mark.parametrize(
|
|
131
|
+
("supernet", "used_nets", "cidr", "last_free"),
|
|
132
|
+
[
|
|
133
|
+
("10.0.0.0/24", ["10.0.0.0/29", "10.0.0.96/30"], 30, "10.0.0.252/30"),
|
|
134
|
+
],
|
|
135
|
+
)
|
|
136
|
+
def test_netsize_iterator_last(supernet, used_nets, cidr, last_free):
|
|
137
|
+
supernet = ipaddress.ip_network(supernet)
|
|
138
|
+
used_nets = [(int(ipaddress.ip_network(n)[0]), int(ipaddress.ip_network(n)[-1])) for n in used_nets]
|
|
139
|
+
i = tools.net_size_iterator(supernet, cidr, used_nets)
|
|
140
|
+
first_ip, last_ip = list(i)[-1]
|
|
141
|
+
net = next(ipaddress.summarize_address_range(ipaddress.ip_address(first_ip), ipaddress.ip_address(last_ip)))
|
|
142
|
+
assert last_free == str(net)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|