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"""IPv4 address logic."""
7
8import sys as _sys
9import struct as _struct
10
11from socket import inet_aton as _inet_aton
12
13# Check whether we need to use fallback code or not.
14if _sys.platform in ('win32', 'cygwin'):
15 # inet_pton() not available on Windows. inet_pton() under cygwin
16 # behaves exactly like inet_aton() and is therefore highly unreliable.
17 from netaddr.fbsocket import inet_pton as _inet_pton, AF_INET
18else:
19 # All other cases, use all functions from the socket module.
20 from socket import inet_pton as _inet_pton, AF_INET
21
22from netaddr.core import AddrFormatError, ZEROFILL, INET_ATON, INET_PTON
23
24from netaddr.strategy import (
25 valid_words as _valid_words,
26 valid_bits as _valid_bits,
27 bits_to_int as _bits_to_int,
28 int_to_bits as _int_to_bits,
29 valid_bin as _valid_bin,
30 int_to_bin as _int_to_bin,
31 bin_to_int as _bin_to_int,
32)
33
34
35#: The width (in bits) of this address type.
36width = 32
37
38#: The individual word size (in bits) of this address type.
39word_size = 8
40
41#: The format string to be used when converting words to string values.
42word_fmt = '%d'
43
44#: The separator character used between each word.
45word_sep = '.'
46
47#: The AF_* constant value of this address type.
48family = AF_INET
49
50#: A friendly string name address type.
51family_name = 'IPv4'
52
53#: The version of this address type.
54version = 4
55
56#: The number base to be used when interpreting word values as integers.
57word_base = 10
58
59#: The maximum integer value that can be represented by this address type.
60max_int = 2**width - 1
61
62#: The number of words in this address type.
63num_words = width // word_size
64
65#: The maximum integer value for an individual word in this address type.
66max_word = 2**word_size - 1
67
68#: A dictionary mapping IPv4 CIDR prefixes to the equivalent netmasks.
69prefix_to_netmask = dict([(i, max_int ^ (2 ** (width - i) - 1)) for i in range(0, width + 1)])
70
71#: A dictionary mapping IPv4 netmasks to their equivalent CIDR prefixes.
72netmask_to_prefix = dict([(max_int ^ (2 ** (width - i) - 1), i) for i in range(0, width + 1)])
73
74#: A dictionary mapping IPv4 CIDR prefixes to the equivalent hostmasks.
75prefix_to_hostmask = dict([(i, (2 ** (width - i) - 1)) for i in range(0, width + 1)])
76
77#: A dictionary mapping IPv4 hostmasks to their equivalent CIDR prefixes.
78hostmask_to_prefix = dict([((2 ** (width - i) - 1), i) for i in range(0, width + 1)])
79
80
81def valid_str(addr, flags=0):
82 """
83 :param addr: An IPv4 address in presentation (string) format.
84
85 :param flags: decides which rules are applied to the interpretation of the
86 addr value. Supported constants are INET_PTON and ZEROFILL. See the
87 :class:`IPAddress` documentation for details.
88
89 .. versionchanged:: 0.10.1
90 ``flags`` is scheduled to default to :data:`INET_PTON` instead of :data:`INET_ATON`
91 in the future.
92
93 :return: ``True`` if IPv4 address is valid, ``False`` otherwise.
94
95 .. versionchanged:: 1.0.0
96 Returns ``False`` instead of raising :exc:`AddrFormatError` for empty strings.
97 """
98 if not isinstance(addr, str):
99 raise TypeError('Invalid type: %s' % type(addr))
100 try:
101 str_to_int(addr, flags)
102 except AddrFormatError:
103 return False
104 return True
105
106
107def str_to_int(addr, flags=0):
108 """
109 :param addr: An IPv4 dotted decimal address in string form.
110
111 :param flags: decides which rules are applied to the interpretation of the
112 addr value. Supported constants are INET_PTON and ZEROFILL. See the
113 :class:`IPAddress` documentation for details.
114
115 :return: The equivalent unsigned integer for a given IPv4 address.
116 """
117 error = AddrFormatError('%r is not a valid IPv4 address string!' % (addr,))
118 if ':' in addr:
119 raise error
120 pton_mode = flags & INET_PTON or not flags & INET_ATON
121 if flags & ZEROFILL:
122 addr = '.'.join(['%d' % int(i) for i in addr.split('.')])
123 elif pton_mode and any(len(p) > 1 and p.startswith('0') for p in addr.split('.')):
124 raise error
125
126 try:
127 if pton_mode:
128 packed = _inet_pton(AF_INET, addr)
129 else:
130 packed = _inet_aton(addr)
131 except OSError:
132 raise error
133 return _struct.unpack('>I', packed)[0]
134
135
136def int_to_str(int_val, dialect=None):
137 """
138 :param int_val: An unsigned integer.
139
140 :param dialect: (unused) Any value passed in is ignored.
141
142 :return: The IPv4 presentation (string) format address equivalent to the
143 unsigned integer provided.
144 """
145 if 0 <= int_val <= max_int:
146 return '%d.%d.%d.%d' % (
147 int_val >> 24,
148 (int_val >> 16) & 0xFF,
149 (int_val >> 8) & 0xFF,
150 int_val & 0xFF,
151 )
152 else:
153 raise ValueError('%r is not a valid 32-bit unsigned integer!' % (int_val,))
154
155
156def int_to_arpa(int_val):
157 """
158 :param int_val: An unsigned integer.
159
160 :return: The reverse DNS lookup for an IPv4 address in network byte
161 order integer form.
162 """
163 words = ['%d' % i for i in int_to_words(int_val)]
164 words.reverse()
165 words.extend(['in-addr', 'arpa', ''])
166 return '.'.join(words)
167
168
169def int_to_packed(int_val):
170 """
171 :param int_val: the integer to be packed.
172
173 :return: a packed string that is equivalent to value represented by an
174 unsigned integer.
175 """
176 return _struct.pack('>I', int_val)
177
178
179def packed_to_int(packed_int):
180 """
181 :param packed_int: a packed string containing an unsigned integer.
182 It is assumed that string is packed in network byte order.
183
184 :return: An unsigned integer equivalent to value of network address
185 represented by packed binary string.
186 """
187 return _struct.unpack('>I', packed_int)[0]
188
189
190def valid_words(words):
191 return _valid_words(words, word_size, num_words)
192
193
194def int_to_words(int_val):
195 """
196 :param int_val: An unsigned integer.
197
198 :return: An integer word (octet) sequence that is equivalent to value
199 represented by an unsigned integer.
200 """
201 if not 0 <= int_val <= max_int:
202 raise ValueError(
203 '%r is not a valid integer value supported by' 'this address type!' % (int_val,)
204 )
205 return (int_val >> 24, (int_val >> 16) & 0xFF, (int_val >> 8) & 0xFF, int_val & 0xFF)
206
207
208def words_to_int(words):
209 """
210 :param words: A list or tuple containing integer octets.
211
212 :return: An unsigned integer that is equivalent to value represented
213 by word (octet) sequence.
214 """
215 if not valid_words(words):
216 raise ValueError('%r is not a valid octet list for an IPv4 address!' % (words,))
217 return _struct.unpack('>I', _struct.pack('4B', *words))[0]
218
219
220def valid_bits(bits):
221 return _valid_bits(bits, width, word_sep)
222
223
224def bits_to_int(bits):
225 return _bits_to_int(bits, width, word_sep)
226
227
228def int_to_bits(int_val, word_sep=None):
229 if word_sep is None:
230 word_sep = globals()['word_sep']
231 return _int_to_bits(int_val, word_size, num_words, word_sep)
232
233
234def valid_bin(bin_val):
235 return _valid_bin(bin_val, width)
236
237
238def int_to_bin(int_val):
239 return _int_to_bin(int_val, width)
240
241
242def bin_to_int(bin_val):
243 return _bin_to_int(bin_val, width)
244
245
246def expand_partial_address(addr):
247 """
248 Expands a partial IPv4 address into a full 4-octet version.
249
250 :param addr: an partial or abbreviated IPv4 address
251
252 :return: an expanded IP address in presentation format (x.x.x.x)
253
254 >>> expand_partial_address('1.2')
255 '1.2.0.0'
256
257 .. versionadded:: 1.1.0
258 """
259 tokens = []
260
261 error = AddrFormatError('invalid partial IPv4 address: %r!' % addr)
262
263 if isinstance(addr, str):
264 if ':' in addr:
265 # Ignore IPv6 ...
266 raise error
267
268 try:
269 if '.' in addr:
270 tokens = ['%d' % int(o) for o in addr.split('.')]
271 else:
272 tokens = ['%d' % int(addr)]
273 except ValueError:
274 raise error
275
276 if 1 <= len(tokens) <= 4:
277 for i in range(4 - len(tokens)):
278 tokens.append('0')
279 else:
280 raise error
281
282 if not tokens:
283 raise error
284
285 return '%s.%s.%s.%s' % tuple(tokens)