Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/netaddr/ip/glob.py: 14%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -----------------------------------------------------------------------------
2# Copyright (c) 2008 by David P. D. Moss. All rights reserved.
3#
4# Released under the BSD license. See the LICENSE file for details.
5# -----------------------------------------------------------------------------
6"""
7Routines and classes for supporting and expressing IP address ranges using a
8glob style syntax.
10"""
11from netaddr.core import AddrFormatError, AddrConversionError
12from netaddr.ip import IPRange, IPAddress, IPNetwork, iprange_to_cidrs
15def valid_glob(ipglob):
16 """
17 :param ipglob: An IP address range in a glob-style format.
19 :return: ``True`` if IP range glob is valid, ``False`` otherwise.
20 """
21 # TODO: Add support for abbreviated ipglobs.
22 # TODO: e.g. 192.0.*.* == 192.0.*
23 # TODO: *.*.*.* == *
24 # TODO: Add strict flag to enable verbose ipglob checking.
25 if not isinstance(ipglob, str):
26 return False
28 seen_hyphen = False
29 seen_asterisk = False
31 octets = ipglob.split('.')
33 if len(octets) != 4:
34 return False
36 for octet in octets:
37 if '-' in octet:
38 if seen_hyphen:
39 return False
40 seen_hyphen = True
41 if seen_asterisk:
42 # Asterisks cannot precede hyphenated octets.
43 return False
44 try:
45 (octet1, octet2) = [int(i) for i in octet.split('-')]
46 except ValueError:
47 return False
48 if octet1 >= octet2:
49 return False
50 if not 0 <= octet1 <= 254:
51 return False
52 if not 1 <= octet2 <= 255:
53 return False
54 elif octet == '*':
55 seen_asterisk = True
56 else:
57 if seen_hyphen is True:
58 return False
59 if seen_asterisk is True:
60 return False
61 try:
62 if not 0 <= int(octet) <= 255:
63 return False
64 except ValueError:
65 return False
66 return True
69def glob_to_iptuple(ipglob):
70 """
71 A function that accepts a glob-style IP range and returns the component
72 lower and upper bound IP address.
74 :param ipglob: an IP address range in a glob-style format.
76 :return: a tuple contain lower and upper bound IP objects.
77 """
78 if not valid_glob(ipglob):
79 raise AddrFormatError('not a recognised IP glob range: %r!' % (ipglob,))
81 start_tokens = []
82 end_tokens = []
84 for octet in ipglob.split('.'):
85 if '-' in octet:
86 tokens = octet.split('-')
87 start_tokens.append(tokens[0])
88 end_tokens.append(tokens[1])
89 elif octet == '*':
90 start_tokens.append('0')
91 end_tokens.append('255')
92 else:
93 start_tokens.append(octet)
94 end_tokens.append(octet)
96 return IPAddress('.'.join(start_tokens)), IPAddress('.'.join(end_tokens))
99def glob_to_iprange(ipglob):
100 """
101 A function that accepts a glob-style IP range and returns the equivalent
102 IP range.
104 :param ipglob: an IP address range in a glob-style format.
106 :return: an IPRange object.
107 """
108 if not valid_glob(ipglob):
109 raise AddrFormatError('not a recognised IP glob range: %r!' % (ipglob,))
111 start_tokens = []
112 end_tokens = []
114 for octet in ipglob.split('.'):
115 if '-' in octet:
116 tokens = octet.split('-')
117 start_tokens.append(tokens[0])
118 end_tokens.append(tokens[1])
119 elif octet == '*':
120 start_tokens.append('0')
121 end_tokens.append('255')
122 else:
123 start_tokens.append(octet)
124 end_tokens.append(octet)
126 return IPRange('.'.join(start_tokens), '.'.join(end_tokens))
129def iprange_to_globs(start, end):
130 """
131 A function that accepts an arbitrary start and end IP address or subnet
132 and returns one or more glob-style IP ranges.
134 :param start: the start IP address or subnet.
136 :param end: the end IP address or subnet.
138 :return: a list containing one or more IP globs.
139 """
140 start = IPAddress(start)
141 end = IPAddress(end)
143 if start.version != 4 and end.version != 4:
144 raise AddrConversionError('IP glob ranges only support IPv4!')
146 def _iprange_to_glob(lb, ub):
147 # Internal function to process individual IP globs.
148 t1 = [int(_) for _ in str(lb).split('.')]
149 t2 = [int(_) for _ in str(ub).split('.')]
151 tokens = []
153 seen_hyphen = False
154 seen_asterisk = False
156 for i in range(4):
157 if t1[i] == t2[i]:
158 # A normal octet.
159 tokens.append(str(t1[i]))
160 elif (t1[i] == 0) and (t2[i] == 255):
161 # An asterisk octet.
162 tokens.append('*')
163 seen_asterisk = True
164 else:
165 # Create a hyphenated octet - only one allowed per IP glob.
166 if not seen_asterisk:
167 if not seen_hyphen:
168 tokens.append('%s-%s' % (t1[i], t2[i]))
169 seen_hyphen = True
170 else:
171 raise AddrConversionError('only 1 hyphenated octet per IP glob allowed!')
172 else:
173 raise AddrConversionError('asterisks are not allowed before hyphenated octets!')
175 return '.'.join(tokens)
177 globs = []
179 try:
180 # IP range can be represented by a single glob.
181 ipglob = _iprange_to_glob(start, end)
182 if not valid_glob(ipglob):
183 # TODO: this is a workaround, it is produces non-optimal but valid
184 # TODO: glob conversions. Fix inner function so that is always
185 # TODO: produces a valid glob.
186 raise AddrConversionError('invalid ip glob created')
187 globs.append(ipglob)
188 except AddrConversionError:
189 # Break IP range up into CIDRs before conversion to globs.
190 #
191 # TODO: this is still not completely optimised but is good enough
192 # TODO: for the moment.
193 #
194 for cidr in iprange_to_cidrs(start, end):
195 ipglob = _iprange_to_glob(cidr[0], cidr[-1])
196 globs.append(ipglob)
198 return globs
201def glob_to_cidrs(ipglob):
202 """
203 A function that accepts a glob-style IP range and returns a list of one
204 or more IP CIDRs that exactly matches it.
206 :param ipglob: an IP address range in a glob-style format.
208 :return: a list of one or more IP objects.
209 """
210 return iprange_to_cidrs(*glob_to_iptuple(ipglob))
213def cidr_to_glob(cidr):
214 """
215 A function that accepts an IP subnet in a glob-style format and returns
216 a list of CIDR subnets that exactly matches the specified glob.
218 :param cidr: an IP object CIDR subnet.
220 :return: a list of one or more IP addresses and subnets.
221 """
222 ip = IPNetwork(cidr)
223 globs = iprange_to_globs(ip[0], ip[-1])
224 if len(globs) != 1:
225 # There should only ever be a one to one mapping between a CIDR and
226 # an IP glob range.
227 raise AddrConversionError('bad CIDR to IP glob conversion!')
228 return globs[0]
231class IPGlob(IPRange):
232 """
233 Represents an IP address range using a glob-style syntax ``x.x.x-y.*``
235 Individual octets can be represented using the following shortcuts :
237 1. ``*`` - the asterisk octet (represents values ``0`` through ``255``)
238 2. ``x-y`` - the hyphenated octet (represents values ``x`` through ``y``)
240 A few basic rules also apply :
242 1. ``x`` must always be less than ``y``, therefore :
244 - ``x`` can only be ``0`` through ``254``
245 - ``y`` can only be ``1`` through ``255``
247 2. only one hyphenated octet per IP glob is allowed
248 3. only asterisks are permitted after a hyphenated octet
250 Examples:
252 +------------------+------------------------------+
253 | IP glob | Description |
254 +==================+==============================+
255 | ``192.0.2.1`` | a single address |
256 +------------------+------------------------------+
257 | ``192.0.2.0-31`` | 32 addresses |
258 +------------------+------------------------------+
259 | ``192.0.2.*`` | 256 addresses |
260 +------------------+------------------------------+
261 | ``192.0.2-3.*`` | 512 addresses |
262 +------------------+------------------------------+
263 | ``192.0-1.*.*`` | 131,072 addresses |
264 +------------------+------------------------------+
265 | ``*.*.*.*`` | the whole IPv4 address space |
266 +------------------+------------------------------+
268 .. note :: \
269 IP glob ranges are not directly equivalent to CIDR blocks. \
270 They can represent address ranges that do not fall on strict bit mask \
271 boundaries. They are suitable for use in configuration files, being \
272 more obvious and readable than their CIDR counterparts, especially for \
273 admins and end users with little or no networking knowledge or \
274 experience. All CIDR addresses can always be represented as IP globs \
275 but the reverse is not always true.
276 """
278 __slots__ = ('_glob',)
280 def __init__(self, ipglob):
281 (start, end) = glob_to_iptuple(ipglob)
282 super(IPGlob, self).__init__(start, end)
283 self.glob = iprange_to_globs(self._start, self._end)[0]
285 def __getstate__(self):
286 """:return: Pickled state of an `IPGlob` object."""
287 return super(IPGlob, self).__getstate__()
289 def __setstate__(self, state):
290 """:param state: data used to unpickle a pickled `IPGlob` object."""
291 super(IPGlob, self).__setstate__(state)
292 self.glob = iprange_to_globs(self._start, self._end)[0]
294 def _get_glob(self):
295 return self._glob
297 def _set_glob(self, ipglob):
298 (self._start, self._end) = glob_to_iptuple(ipglob)
299 self._glob = iprange_to_globs(self._start, self._end)[0]
301 glob = property(_get_glob, _set_glob, None, 'an arbitrary IP address range in glob format.')
303 def __str__(self):
304 """:return: IP glob in common representational format."""
305 return '%s' % self.glob
307 def __repr__(self):
308 """:return: Python statement to create an equivalent object"""
309 return "%s('%s')" % (self.__class__.__name__, self.glob)