1# Licensed to the Apache Software Foundation (ASF) under one or more
2# contributor license agreements.  See the NOTICE file distributed with
3# this work for additional information regarding copyright ownership.
4# The ASF licenses this file to You under the Apache License, Version 2.0
5# (the "License"); you may not use this file except in compliance with
6# the License.  You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import socket
17import struct
18
19__all__ = [
20    'is_private_subnet',
21    'is_public_subnet',
22    'is_valid_ip_address',
23    'join_ipv4_segments',
24    'increment_ipv4_segments'
25]
26
27
28def is_private_subnet(ip):
29    """
30    Utility function to check if an IP address is inside a private subnet.
31
32    :type ip: ``str``
33    :param ip: IP address to check
34
35    :return: ``bool`` if the specified IP address is private.
36    """
37    priv_subnets = [{'subnet': '10.0.0.0', 'mask': '255.0.0.0'},
38                    {'subnet': '172.16.0.0', 'mask': '255.240.0.0'},
39                    {'subnet': '192.168.0.0', 'mask': '255.255.0.0'}]
40
41    ip = struct.unpack('I', socket.inet_aton(ip))[0]
42
43    for network in priv_subnets:
44        subnet = struct.unpack('I', socket.inet_aton(network['subnet']))[0]
45        mask = struct.unpack('I', socket.inet_aton(network['mask']))[0]
46
47        if (ip & mask) == (subnet & mask):
48            return True
49
50    return False
51
52
53def is_public_subnet(ip):
54    """
55    Utility function to check if an IP address is inside a public subnet.
56
57    :type ip: ``str``
58    :param ip: IP address to check
59
60    :return: ``bool`` if the specified IP address is public.
61    """
62    return not is_private_subnet(ip=ip)
63
64
65def is_valid_ip_address(address, family=socket.AF_INET):
66    """
67    Check if the provided address is valid IPv4 or IPv6 address.
68
69    :param address: IPv4 or IPv6 address to check.
70    :type address: ``str``
71
72    :param family: Address family (socket.AF_INTET / socket.AF_INET6).
73    :type family: ``int``
74
75    :return: ``bool`` True if the provided address is valid.
76    """
77    try:
78        socket.inet_pton(family, address)
79    except socket.error:
80        return False
81
82    return True
83
84
85def join_ipv4_segments(segments):
86    """
87    Helper method to join ip numeric segment pieces back into a full
88    ip address.
89
90    :param segments: IPv4 segments to join.
91    :type segments: ``list`` or ``tuple``
92
93    :return: IPv4 address.
94    :rtype: ``str``
95    """
96    return '.'.join([str(s) for s in segments])
97
98
99def increment_ipv4_segments(segments):
100    """
101    Increment an ip address given in quad segments based on ipv4 rules
102
103    :param segments: IPv4 segments to increment.
104    :type segments: ``list`` or ``tuple``
105
106    :return: Incremented segments.
107    :rtype: ``list``
108    """
109    segments = [int(segment) for segment in segments]
110
111    segments[3] += 1
112
113    if segments[3] == 256:
114        segments[3] = 0
115        segments[2] += 1
116
117        if segments[2] == 256:
118            segments[2] = 0
119            segments[1] += 1
120
121            if segments[1] == 256:
122                segments[1] = 0
123                segments[0] += 1
124
125    return segments
126