--- /dev/null
+# ipaddr.py -- handle IP addresses and set of IP addresses.
+# Copyright (C) 1996-2000 Cendio Systems AB
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""IP address manipulation.
+
+This module is useful if you need to manipulate IP addresses or sets
+of IP addresses.
+
+The main classes are:
+
+ ipaddr -- a single IP address.
+ netmask -- a netmask.
+ network -- an IP address/netmask combination. It is often, but
+ not always, better to use the ip_set class instead.
+ ip_set -- a set of IP addresses, that may or may not be adjacent.
+
+So, what can you do with this module? As a simple example of the kind
+of things this module can do, this code computes the set of all IP
+addresses except 127.0.0.0/8 and prints it, expressed as a union of
+network/netmask pairs.
+
+ import ipaddr
+
+ s = ipaddr.ip_set()
+ s.add_network(ipaddr.network('127.0.0.0', '255.0.0.0',
+ ipaddr.DEMAND_FILTER))
+ for nw in s.complement().as_list_of_networks():
+ print nw.ip_str() + '/' + nw.mask.netmask_bits_str
+
+Errors are reported by raising an exception from the following
+exception hierarcy:
+
+Exception # The standard Python base exception class.
+ |
+ +-- BadType # Only raised if the programmer makes an error.
+ +-- IpError # Base class for errors that depend on the data.
+ |
+ +-- SetNotRepresentable
+ +-- BrokenIpAddress
+ | |
+ | +-- PartNegative
+ | +-- PartOverflow
+ |
+ +-- BrokenNetmask
+ | |
+ | +-- NeedOneBit
+ | +-- NeedMoreBits
+ | +-- NeedLessBits
+ |
+ +-- BrokenNetwork
+ |
+ +-- EmptyIpAddress
+ +-- EmptyNetmask
+ +-- BrokenNetworkAddress
+ +-- NetworkAddressClash
+ +-- BroadcastAddressClash
+
+BadType may be raised at any time if the programmer makes an error
+(such as passing a dictionary to a function that expects a string).
+SetNotRepresentable may be raised by ip_set.as_str_range(). All other
+exceptions are raised from the constructors and helper functions only.
+
+The following constants are present in this module:
+
+ DEMAND_NONE See class network.
+ DEMAND_FILTER See class network.
+ DEMAND_NETWORK See class network.
+ DEMAND_INTERFACE See class network.
+
+ hostmask A netmask object with all 32 bits set.
+ complete_network A network object representing all IP addresses.
+ complete_set An ip_set object representing all IP addresses.
+ broadcast_network A network object representing 255.255.255.255.
+ broadcast_set An ip_set object representing 255.255.255.255.
+
+The as_ipaddr function can be used when you have an object that you
+know are an ipaddr or network, and you want to get the ipaddr part.
+
+All the other functions in this module are internal helper functions,
+and they should not be used.
+
+The internal representation used for IP addresses is currently a long
+number. That may change in the future, so where the internal
+representation is visible, you should do nothing with it except
+compare it to None.
+
+This module was developed by Cendio Systems AB for use in the Fuego
+Firewall. Bug reports can be sent to Per Cederqvist <ceder@cendio.se>
+who is currently acting as maintainer for this module.
+
+Brief history:
+ 1997-03-11 Module created, and used internally.
+ 2000-03-09 1.0: First non-public beta release outside of Cendio Systems.
+ 2000-03-17 1.1: First public release under the GNU GPL license.
+
+"""
+
+
+import copy
+import string
+import types
+
+# The error messages are marked with a call to this function, so that
+# they can easily be found and translated.
+def _(s):
+ return s
+
+# The exception hierarchy.
+class IpError(Exception):
+ """Base class for errors that are cause by errors in input data.
+ """
+ def __str__(self):
+ return self.format % self.args
+
+class SetNotRepresentable(IpError):
+ format = _("The set of IP addresses cannot be represented "
+ "as a single network range")
+
+class BrokenIpAddress(IpError):
+ format = _("Felaktigt IP-nummer")
+
+class PartNegative(BrokenIpAddress):
+ format = _("En komponent i IP-numret är negativ")
+
+class PartOverflow(BrokenIpAddress):
+ format = _("En komponent i IP-numret är större än 255")
+
+class BrokenNetmask(IpError):
+ format = _("Felaktig nätmask")
+
+class NeedOneBit(BrokenNetmask):
+ format = _("Minst en bit måste vara ettställd")
+
+class NeedMoreBits(BrokenNetmask):
+ format = _("Minst %d bitar måste vara ettställda")
+
+class NeedLessBits(BrokenNetmask):
+ format = _("Högst %d bitar får vara ettställda")
+
+class BrokenNetwork(IpError):
+ """Base class for errors regarding network objects.
+ """
+
+class EmptyIpAddress(BrokenNetwork):
+ format = _("IP-nummer ej ifyllt")
+
+class EmptyNetmask(BrokenNetwork):
+ format = _("Nätmask ej ifylld")
+
+class BrokenNetworkAddress(BrokenNetwork):
+ format = _("Med denna nätmask är %s ett otillåtet nätverksnummer; "
+ "menar du %s?")
+
+class NetworkAddressClash(BrokenNetwork):
+ format = _("Med denna nätmask krockar Fuegons adress med nätnumret")
+
+class BroadcastAddressClash(BrokenNetwork):
+ format = _("Med denna nätmask krockar Fuegons adress "
+ "med broadcastadressen")
+
+class BadType(Exception):
+ """An object of an unexpected type was passed to a function.
+ """
+ pass
+
+# These constants are used with netmasks and networks to specify what
+# the code expects.
+#
+# DEMAND_NONE: netmask 0-32 (inclusive)
+# DEMAND_FILTER: netmask 0-32, the host part must be all zeroes
+# DEMAND_NETWORK: netmask 1-32, the host part must be all zeroes
+# DEMAND_INTERFACE: netmask 1-30, the host part must *not* be all zeroes
+
+DEMAND_NONE = 1
+DEMAND_FILTER = 2
+DEMAND_NETWORK = 3
+DEMAND_INTERFACE = 4
+
+def bits_to_intrep(bits):
+ """Convert BITS to the internal representation.
+
+ BITS should be a number in the range 0-32 (inclusive).
+
+ """
+ return pow(2L, 32) - pow(2L, 32-bits)
+
+
+def intrep_with_bit_set(bit):
+ """Return an internal representation with bit BIT set.
+
+ BIT should be a number in the range 1-32, where bit 1 is the
+ leftmost. Examples:
+
+ intrep_with_bit_set(1) --> the internal representation of 128.0.0.0
+ intrep_with_bit_set(32) --> the internal representation of 0.0.0.1
+ """
+ assert 0 < bit and bit <= 32
+
+ return pow(2L, 32-bit)
+
+
+__ONES = {0:0, 128:1, 192:2, 224:3,
+ 240:4, 248:5, 252:6, 254:7}
+
+def tuple_to_bits(mask):
+ """Convert MASK to bits.
+
+ MASK should be a tuple of four integers in the range 0-255 (inclusive).
+
+ Raises BrokenNetmask if MASK is not a valid netmask.
+ """
+
+ if mask == None:
+ return None
+ else:
+ (a, b, c, d) = mask
+
+ if a == 255 and b == 255 and c == 255 and d == 255:
+ return 32
+
+ try:
+ if a == 255 and b == 255 and c == 255:
+ return 24 + __ONES[d]
+ elif a == 255 and b == 255 and d == 0:
+ return 16 + __ONES[c]
+ elif a == 255 and c == 0 and d == 0:
+ return 8 + __ONES[b]
+ elif b == 0 and c == 0 and d == 0:
+ return __ONES[a]
+ except KeyError:
+ pass
+
+ raise BrokenNetmask()
+
+
+def intrep_to_dotted_decimal(t):
+ """Convert T to dotted-decimal notation.
+
+ T should be the internal representation used py ipaddr.py.
+ """
+
+ return (str(int(t>>24)) + '.' + str(int((t>>16) & 255))
+ + '.' + str(int((t>>8) & 255)) + '.' + str(int(t & 255)))
+
+
+def as_ipaddr(nwip):
+ """Return the IP address object of NWIP.
+
+ NWIP may be an ipaddr object, which is returned unchanged,
+ or a network object, in which case the ipaddr part of it is
+ returned.
+ """
+
+ if isinstance(nwip, ipaddr):
+ return nwip
+ elif isinstance(nwip, network):
+ return nwip.ip
+ else:
+ raise BadType('Expected a network or ipaddr object', nwip)
+
+
+class ipaddr:
+ """Handle IP addresses.
+
+ Sample use:
+
+ ip1 = ipaddr('12.3.5.1')
+ ip2 = ipaddr([12, 3, 5, 1])
+ print ip1.ip_str()
+ >>> '12.3.5.1'
+ print ip1.intrep
+ >>> 201524481L
+ print ip2.ip_str()
+ >>> '12.3.5.1'
+ print ip2.intrep
+ >>> 201524481L
+
+ An ipaddr object can have two states: empty or good.
+ The status can be examined like this:
+
+ if ip.intrep == None:
+ handle_empty(m.user_input())
+ else:
+ handle_good(ip)
+
+ All other members should only be used in the good state. The
+ value stored in the intrep member should only be compared against
+ None. The type and value of it is an internal detail that may
+ change in the future.
+
+ """
+
+ def __init__(self, ip):
+ """Create an ipaddr from IP (a string, tuple or list).
+
+ The empty string or None may be given; it is handled as the
+ empty IP number.
+ """
+
+ if type(ip) == types.StringType:
+ self.__user_input = ip
+ ip = string.strip(ip)
+ else:
+ self.__user_input = None
+
+ # The empty IP number?
+
+ if ip == '' or ip == None:
+ self.__ip_str = ''
+ self.intrep = None
+ if ip == None:
+ self.__user_input = ''
+ return
+
+ if type(ip) == types.StringType:
+
+ # Convert a string.
+
+ try:
+ [a, b, c, d] = map(string.atoi, string.splitfields(ip, '.'))
+ except:
+ raise BrokenIpAddress()
+
+ if a < 0 or b < 0 or c < 0 or d < 0:
+ raise PartNegative()
+
+ if a > 255 or b > 255 or c > 255 or d > 255:
+ raise PartOverflow()
+
+ self.intrep = (long(a) << 24) + (b << 16) + (c << 8) + d
+
+ else:
+ assert type(ip) == types.LongType
+ self.intrep = ip
+
+ self.__ip_str = None
+
+ def ip_str(self):
+ if self.__ip_str == None:
+ self.__ip_str = intrep_to_dotted_decimal(self.intrep)
+ return self.__ip_str
+
+ def user_input(self):
+ if self.__user_input == None:
+ # This object was constructed from a tuple. Generate a string.
+ self.__user_input = self.ip_str()
+ return self.__user_input
+
+ def compare(self, other):
+ """Compare this IP address with OTHER.
+
+ Returns -1, 0 or 1 if this IP address is less than, equal to,
+ or greater than OTHER (which should be an ipaddr object).
+ """
+ # FIXME: should we rename this __cmp__? It needs to handle
+ # other types of the OTHER argument first.
+
+ if self.intrep == other.intrep:
+ return 0
+ if self.intrep < other.intrep:
+ return -1
+ else:
+ return 1
+
+ def __str__(self):
+ if self.intrep is None:
+ return "<ipaddr empty>"
+ else:
+ return "<ipaddr %s>" % self.ip_str()
+
+ def __repr__(self):
+ if self.intrep is None:
+ return "ipaddr.ipaddr('')"
+ else:
+ return "ipaddr.ipaddr('%s')" % self.ip_str()
+
+
+class netmask:
+ """Handle netmasks.
+
+ Sample use:
+
+ # Four ways to initialize a netmask.
+ nm1 = netmask('255.255.128.0', DEMAND_NONE)
+ nm2 = netmask([255, 255, 128, 0], DEMAND_NONE)
+ nm3 = netmask('17', DEMAND_NONE)
+ nm4 = netmask(17, DEMAND_NONE)
+ print nm1.netmask_str()
+ >>> '255.255.128.0'
+ print nm1.intrep
+ >>> (255, 255, 128, 0)
+ print nm1.netmask_bits
+ >>> 17
+ print nm1.netmask_bits_str
+ >>> '17'
+
+ A netmask can have two states: empty or good. The state
+ can be examined like this:
+
+ if m.intrep == None:
+ handle_empty(m.user_input())
+ else:
+ handle_good(m)
+
+ All other members should be used only in the good state.
+
+ """
+
+ def __check_range(self, bits, minbits, maxbits):
+ if bits < minbits:
+ if minbits == 1:
+ raise NeedOneBit()
+ else:
+ raise NeedMoreBits(minbits)
+ elif bits > maxbits:
+ raise NeedLessBits(maxbits)
+
+
+ def __set_from_bits(self, bits, minbits, maxbits):
+ self.__check_range(bits, minbits, maxbits)
+ self.intrep = bits_to_intrep(bits)
+ self.netmask_bits = bits
+
+
+ def __set_from_tuple(self, tpl, minbits, maxbits):
+ bits = tuple_to_bits(tpl)
+ self.__check_range(bits, minbits, maxbits)
+ self.intrep = bits_to_intrep(bits)
+ self.netmask_bits = bits
+
+ DEMANDS = {DEMAND_NONE:(0,32),
+ DEMAND_FILTER:(0,32),
+ DEMAND_NETWORK:(1,32),
+ DEMAND_INTERFACE:(1,30)}
+
+ def __init__(self, mask, demand):
+ """Create a netmask from MASK (a string, tuple or number) and DEMAND.
+
+ The empty string or None may be given; it is handled as the
+ empty netmask.
+
+ See class network for a description of the DEMAND parameter.
+ """
+
+ (minbits, maxbits) = self.DEMANDS[demand]
+ self.demand = demand
+
+ if type(mask) == types.StringType:
+ self.__user_input = mask
+ mask = string.strip(mask)
+ else:
+ self.__user_input = None
+
+ if mask == '' or mask == None:
+
+ # Handle empty netmasks.
+
+ self.__netmask_str = ''
+ self.intrep = None
+ self.netmask_bits_str = ''
+ self.netmask_bits = None
+ if self.__user_input == None:
+ self.input = ''
+ return
+
+ # Decode the MASK argument and set self.netmask_bits
+ # and self.intrep.
+
+ if type(mask) == types.StringType:
+
+ # Is this a string containing a single number?
+ try:
+ bits = string.atoi(mask)
+ except (OverflowError, ValueError):
+ bits = None
+
+ if bits != None:
+
+ # This is a string containing a single number.
+
+ self.__set_from_bits(bits, minbits, maxbits)
+
+ else:
+
+ # Interpret the netmask as a dotted four-tuple.
+ try:
+ [a, b, c, d] = map(string.atoi,
+ string.splitfields(mask, '.'))
+ except:
+ raise BrokenNetmask()
+
+ self.__set_from_tuple((a, b, c, d), minbits, maxbits)
+
+ elif type(mask) == types.IntType:
+
+ # This is a number, representing the number of bits in the mask.
+
+ self.__set_from_bits(mask, minbits, maxbits)
+
+ else:
+
+ # This is a tuple or list.
+
+ if len(mask) != 4:
+ raise BadType('Wrong len of tuple/list')
+
+ (a, b, c, d) = (mask[0], mask[1], mask[2], mask[3])
+
+ self.__set_from_tuple((a, b, c, d), minbits, maxbits)
+
+ self.__netmask_str = None
+ self.netmask_bits_str = repr(self.netmask_bits)
+
+ def netmask_str(self):
+ if self.__netmask_str == None:
+ self.__netmask_str = intrep_to_dotted_decimal(self.intrep)
+ return self.__netmask_str
+
+ def user_input(self):
+ if self.__user_input == None:
+ # This object was constructed from a tuple or an integer.
+ self.__user_input = self.ip_str()
+ return self.__user_input
+
+ def __str__(self):
+ if self.intrep is None:
+ return "<netmask empty>"
+ else:
+ return "<netmask /%d>" % self.netmask_bits
+
+ def __repr__(self):
+ if self.intrep is None:
+ return "ipaddr.netmask('')"
+ else:
+ return "ipaddr.netmask(%d, %d)" % (self.netmask_bits, self.demand)
+
+
+hostmask = netmask(32, DEMAND_NONE)
+
+
+class network:
+ """Designate a network or host.
+
+ The constructor takes three arguments: the IP number part, the
+ netmask part, and a demand parameter. See class ipaddr and class
+ netmask for a description of the first two arguments. The demand
+ argument can be one of the following constants:
+
+ DEMAND_NONE
+ No special demands.
+ DEMAND_FILTER
+ The host part must be all zeroes.
+ DEMAND_NETWORK
+ The netmask must be 1-32
+ The host part must be all zeroes.
+ DEMAND_INTERFACE
+ The netmask must be 1-30
+ The host part must *not* be all zeroes (the network address)
+ or all ones (the broadcast address).
+
+ The following members exist and are set by the constructor:
+
+ ip.user_input() # a caching function
+ ip_str() # a caching function
+ ip.intrep
+ mask.user_input() # a caching function
+ mask.netmask_str() # a caching function
+ mask.intrep
+ mask.netmask_bits
+ mask.netmask_bits_str
+ network_str() # a caching function
+ network_intrep
+ broadcast_str() # a caching function
+ broadcast_intrep
+ host_part_str() # a caching function
+ host_part_intrep
+
+ """
+
+ def __init__(self, ip, mask, demand):
+ self.ip = ipaddr(ip)
+ self.mask = netmask(mask, demand)
+
+ if self.ip.intrep == None:
+ raise EmptyIpAddress()
+
+ if self.mask.intrep == None:
+ raise EmptyNetmask()
+
+ self._precompute()
+
+ def _precompute(self):
+ self.__lower_str = None
+ self.__upper_str = None
+
+ self.network_intrep = self.ip.intrep & self.mask.intrep
+ self.broadcast_intrep = (self.network_intrep |
+ (pow(2L, 32)-1-self.mask.intrep))
+ self.host_part_intrep = self.ip.intrep - self.network_intrep
+
+ self.__network_str = None
+ self.__broadcast_str = None
+ self.__host_part_str = None
+
+ demand = self.mask.demand
+
+ if demand == DEMAND_NONE:
+ pass
+ elif demand == DEMAND_FILTER or demand == DEMAND_NETWORK:
+ if self.host_part_intrep != 0L:
+ raise BrokenNetworkAddress(self.ip_str(), self.network_str())
+ elif demand == DEMAND_INTERFACE:
+ if self.host_part_intrep == 0L:
+ raise NetworkAddressClash()
+ elif self.broadcast_intrep == self.ip.intrep:
+ raise BroadcastAddressClash()
+ else:
+ raise BadType('Bad value for the demand parameter', demand)
+
+ def network_str(self):
+ if self.__network_str == None:
+ self.__network_str = intrep_to_dotted_decimal(self.network_intrep)
+ return self.__network_str
+
+ def broadcast_str(self):
+ if self.__broadcast_str == None:
+ self.__broadcast_str = intrep_to_dotted_decimal(
+ self.broadcast_intrep)
+ return self.__broadcast_str
+
+ def host_part_str(self):
+ if self.__host_part_str == None:
+ self.__host_part_str = intrep_to_dotted_decimal(
+ self.host_part_intrep)
+ return self.__host_part_str
+
+ def overlaps(self, other):
+ """Returns true if the network overlaps with OTHER.
+
+ OTHER must be a network object or an ipaddr object. If it
+ is empty this method will always return false.
+
+ """
+
+ if self.network_intrep == None:
+ return 0
+
+ if isinstance(other, ipaddr):
+
+ if other.intrep == None:
+ return 0
+
+ return (self.mask.intrep & other.intrep) == self.network_intrep
+ else:
+ if other.network_intrep == None:
+ return 0
+
+ mask = self.mask.intrep & other.mask.intrep
+ return (mask & self.ip.intrep) == (mask & other.ip.intrep)
+
+ def intersection(self, other):
+ """Return the intersection of the network and OTHER.
+
+ The return value is a network object with DEMAND_FILTER. If
+ the intersection is empty this method will return None.
+
+ OTHER must be a network object or an ipaddr object. The
+ intersection will be empty if it is empty.
+ """
+
+ if self.network_intrep == None:
+ return None
+
+ if isinstance(other, ipaddr):
+
+ if other.intrep == None:
+ return None
+
+ prefix_mask = self.mask.intrep
+ short_net = self.network_intrep
+ long_ip = other.intrep
+ result = network(other.intrep, 32, DEMAND_FILTER)
+ else:
+ if other.network_intrep == None:
+ return None
+
+ if self.mask.netmask_bits < other.mask.netmask_bits:
+ prefix_mask = self.mask.intrep
+ short_net = self.network_intrep
+ long_ip = other.network_intrep
+ result = network(other.network_intrep, other.mask.netmask_bits,
+ DEMAND_FILTER)
+ else:
+ prefix_mask = other.mask.intrep
+ short_net = other.network_intrep
+ long_ip = self.network_intrep
+ result = network(self.network_intrep, self.mask.netmask_bits,
+ DEMAND_FILTER)
+
+ if (long_ip & prefix_mask) != (short_net & prefix_mask):
+ return None
+
+ return result
+
+ def is_subset(self, nwip):
+ """Return true if NWIP is a subset of this network.
+
+ NWIP must be a network object or an ipaddr object.
+ """
+
+ if not self.overlaps(nwip):
+ return 0
+
+ if isinstance(nwip, ipaddr):
+ return 1
+
+ return nwip.mask.netmask_bits <= self.mask.netmask_bits
+
+ def is_same_set(self, nwip):
+ """Return true if NWIP contains the same set as this network.
+
+ NWIP must be a network object or an ipaddr object.
+ """
+
+ if isinstance(nwip, ipaddr):
+ return (self.mask.netmask_bits == 32
+ and self.ip.intrep == nwip.intrep)
+ else:
+ return (self.mask.netmask_bits == nwip.mask.netmask_bits
+ and self.network_intrep == nwip.network_intrep)
+
+ def subtract(self, nwip):
+ """Create a list of new network objects by subtracting NWIP from self.
+
+ The result consists of networks that together span all
+ IP addresses that are present in self, except those that are
+ present in NWIP. (The result may be empty or contain several
+ disjoint network objects.)
+
+ Don't use this! This method is slow. The ip_set class can do
+ this kind of things in a more efficient way.
+ """
+
+ if not self.overlaps(nwip):
+ # No overlap at all, so NWIP cannot affect the result.
+ return [self]
+
+ if isinstance(nwip, ipaddr):
+ bits = 32
+ intrep = nwip.intrep
+ else:
+ assert isinstance(nwip, network)
+ bits = nwip.mask.netmask_bits
+ intrep = nwip.ip.intrep
+ nets = []
+ while bits > self.mask.netmask_bits:
+ nets.append(network(compute_neighbor(intrep, bits),
+ bits, DEMAND_FILTER))
+ bits = bits - 1
+ return nets
+
+ def subtract_nwips(self, nwips):
+ """Create a list of new network objects by subtracting NWIPS.
+
+ The result consists of networks that together span all
+ IP addresses that are present in self, except those that are
+ present in NWIPS. (The result may be empty or contain
+ several disjoint network objects.) NWIPS should be a list
+ of network or ipaddr objects.
+
+ Don't use this! This method is slow. The ip_set class can do
+ this kind of things in a more efficient way.
+ """
+
+ subtracted = [self]
+ for s in nwips:
+ # precondition<A>: SUBTRACTED is a list of networks
+ tmp = []
+ for nw in subtracted:
+ tmp = tmp + nw.subtract(s)
+ subtracted = tmp
+ # postcondition: SUBTRACTED is a list of networks that
+ # spans all IP addresses that were present in
+ # precondition<A>, except those that are present in S.
+
+ return subtracted
+
+ def __compute_lower_upper(self):
+ if self.__lower_str != None:
+ return
+ assert self.network_intrep != None and self.broadcast_intrep != None
+
+ self.__lower_str = intrep_to_dotted_decimal(self.network_intrep + 1)
+ self.__upper_str = intrep_to_dotted_decimal(self.broadcast_intrep - 1)
+
+ def lower_host(self):
+ self.__compute_lower_upper()
+ return self.__lower_str
+
+ def upper_host(self):
+ self.__compute_lower_upper()
+ return self.__upper_str
+
+ def __repr__(self):
+ return _("{network %s/%d}") % (self.ip_str(), self.mask.netmask_bits)
+
+ def ip_str(self):
+ return self.ip.ip_str()
+
+
+class ip_set:
+ def __init__(self, nwip=None):
+ """Create an ip_set.
+
+ If the optional argument NWIP is supplied, the set is
+ initialized to it, otherwise the created set will be empty.
+ NWIP must be a network or ipaddr object.
+ """
+
+ # [[0L, 3L], [5L, 7L]] means 0.0.0.0/29 \ 0.0.0.4/32
+ self.__set = []
+
+ if nwip != None:
+ self.append(nwip)
+
+ def subtract_set(self, other):
+ """Remove all IP-numbers in OTHER from this.
+
+ OTHER should be an ip_set object.
+ """
+
+ self.subtract_list(other.__set)
+
+ def subtract_ips(self, ips):
+ """Remove all IP-numbers in IPS from this.
+
+ IPS should be a list of ipaddr objects.
+ """
+
+ for ip in ips:
+ self.subtract_list([[ip.intrep, ip.intrep]])
+
+ def subtract_list(self, other):
+ # Don't use this method directly, unless you are the test suite.
+ ix = 0
+ iy = 0
+ while ix < len(self.__set) and iy < len(other):
+ if self.__set[ix][1] < other[iy][0]:
+ # The entire range survived.
+ ix = ix + 1
+ elif self.__set[ix][0] > other[iy][1]:
+ # The entire other range is unused, so discard it.
+ iy = iy + 1
+ elif self.__set[ix][0] >= other[iy][0]:
+ if self.__set[ix][1] <= other[iy][1]:
+ # The entire range is subtracted.
+ del self.__set[ix]
+ else:
+ # The start of the range is subtracted, but
+ # the rest of the range may survive. (As a matter
+ # of fact, at least one number *will* survive,
+ # since there should be a gap between other[iy][1]
+ # and other[iy+1][0], but we don't use that fact.)
+ self.__set[ix][0] = other[iy][1] + 1
+ iy = iy + 1
+ else:
+ # The first part of the range survives.
+ end = self.__set[ix][1]
+ assert self.__set[ix][1] >= other[iy][0]
+ self.__set[ix][1] = other[iy][0] - 1
+ ix = ix + 1
+ if end > other[iy][1]:
+ # The part that extends past the subtractor may survive.
+ self.__set[ix:ix] = [[other[iy][1] + 1, end]]
+ # Retain the subtractor -- it may still kill some
+ # other range.
+
+ def add_set(self, other):
+ """Add all IP-numbers in OTHER to this.
+
+ OTHER should be an ip_set object.
+ """
+
+ self.add_list(other.__set)
+
+ def add_list(self, other):
+ # Don't use this method directly, unless you are the test suite.
+ ix = 0
+ iy = 0
+ res = []
+ while ix < len(self.__set) or iy < len(other):
+ # Remove the first range
+ if ix < len(self.__set):
+ if iy < len(other):
+ if self.__set[ix][0] < other[iy][0]:
+ rng = self.__set[ix]
+ ix = ix + 1
+ else:
+ rng = other[iy]
+ iy = iy + 1
+ else:
+ rng = self.__set[ix]
+ ix = ix + 1
+ else:
+ rng = other[iy]
+ iy = iy + 1
+
+ # Join this range to the list we already have collected.
+ if len(res) == 0:
+ # This is the first element.
+ res.append(rng)
+ elif rng[0] <= res[-1][1] + 1:
+ # This extends (or is consumed by) the last range.
+ res[-1][1] = max(res[-1][1], rng[1])
+ else:
+ # There is a gap between the previous range and this one.
+ res.append(rng)
+
+ self.__set = res
+
+ def append(self, nwip):
+ """Add NWIP to this.
+
+ NWIP should be a network object or ipaddr object.
+ """
+
+ if isinstance(nwip, network):
+ self.add_network(nwip)
+ else:
+ self.add_ipaddr(nwip)
+
+ def add_network(self, nw):
+ """Add NW to this.
+
+ NW should be a network object.
+ """
+ self.add_list([[nw.network_intrep, nw.broadcast_intrep]])
+
+ def add_range(self, lo_ip, hi_ip):
+ """Add the range of IP numbers specified by LO_IP and HI_IP to this.
+
+ LO_IP and HI_IP should be ipaddr objects. They specify a
+ range of IP numbers. Both LO_IP and HI_IP are included in the
+ range.
+ """
+
+ assert lo_ip.intrep != None
+ assert hi_ip.intrep != None
+ assert lo_ip.intrep <= hi_ip.intrep
+ self.add_list([[lo_ip.intrep, hi_ip.intrep]])
+
+ def add_ipaddr(self, ip):
+ """Add IP to this.
+
+ IP should be an ipaddr object.
+ """
+
+ assert ip.intrep != None
+ self.add_list([[ip.intrep, ip.intrep]])
+
+ def complement(self):
+ """Return everything not contained in this ip_set.
+
+ The return value is a new ip_set. This is not modified.
+ """
+
+ pre = -1L
+ lst = []
+ for [lo, hi] in self.__set:
+ if lo != 0:
+ lst.append([pre+1, lo-1])
+ pre = hi
+ if pre < pow(2L, 32) - 1:
+ lst.append([pre+1, pow(2L, 32) - 1])
+ res = ip_set()
+ res.add_list(lst)
+ return res
+
+ def intersection(self, other):
+ """Return the intersection of this and OTHER.
+
+ The return value is a new ip_set. This is not modified.
+ OTHER should be an ip_set, network or ipaddr object.
+ """
+
+ res = []
+ ix = 0
+ iy = 0
+ x = copy.deepcopy(self.__set)
+
+ if isinstance(other, ip_set):
+ y = copy.deepcopy(other.__set)
+ elif isinstance(other, network):
+ y = [[other.network_intrep, other.broadcast_intrep]]
+ elif isinstance(other, ipaddr):
+ y = [[other.intrep, other.intrep]]
+ else:
+ raise BadType('expected an ip_set, network or ipaddr argument')
+
+ while ix < len(x) and iy < len(y):
+ if x[ix][1] < y[iy][0]:
+ # The first entry on x doesn't overlap with anything on y.
+ ix = ix + 1
+ elif x[ix][0] > y[iy][1]:
+ # The first entry on y doesn't overlap with anything on x.
+ iy = iy + 1
+ else:
+ # Some overlap exists.
+
+ # Trim away any leading edges.
+ if x[ix][0] < y[iy][0]:
+ # x starts before y
+ x[ix][0] = y[iy][0]
+ elif x[ix][0] > y[iy][0]:
+ # y starts before x
+ y[iy][0] = x[ix][0]
+
+ # The ranges start at the same point (at least after
+ # the trimming).
+ if x[ix][1] == y[iy][1]:
+ # The ranges are equal.
+ res.append(x[ix])
+ ix = ix + 1
+ iy = iy + 1
+ elif x[ix][1] < y[iy][1]:
+ # x is the smaller range
+ res.append(x[ix])
+ ix = ix + 1
+ else:
+ # y is the smaller range
+ res.append(y[iy])
+ iy = iy + 1
+
+ result = ip_set()
+ result.add_list(res)
+ return result
+
+
+ def as_list_of_networks(self):
+ """Return this set as a list of networks.
+
+ The returned value is a list of network objects, that are
+ created with DEMAND_FILTER. This method may be expensive, so
+ it should only be used when necessary.
+ """
+
+ bm = []
+ for [a, b] in self.__set:
+
+ lomask = 1L
+ lobit = 1L
+ himask = pow(2L, 32)-2
+ bits = 32
+ while a <= b:
+ if a & lomask != 0L:
+ bm.append((bits, a))
+ a = a + lobit
+ elif b & lomask != lomask:
+ bm.append((bits, b & himask))
+ b = b - lobit
+ else:
+ lomask = (lomask << 1) | 1
+ lobit = lobit << 1
+ himask = himask ^ lobit
+ bits = bits - 1
+ assert(bits >= 0)
+ bm.sort()
+ res = []
+ for (mask, ip) in bm:
+ res.append(network(ip, mask, DEMAND_FILTER))
+ return res
+
+ def as_list_of_ranges(self):
+ """Return the set of IP addresses as a list of ranges.
+
+ Each range is a list of two long numbers. Sample return
+ value: [[1L, 3L], [0x7f000001L, 0x7f000001L]], meaning
+ the set 0.0.0.1, 0.0.0.2, 0.0.0.3, 127.0.0.1.
+ """
+
+ # This method is currently very cheap, since this is the
+ # current internal representation.
+
+ return self.__set
+
+ def as_str_range(self):
+ """Return the set as a string, such as "1.2.3.4-1.2.3.8".
+
+ The returned value always has the form a.b.c.d-e.f.g.h.
+ Raises SetNotRepresentable if the set cannot be represented as a
+ single interval, or if it is the empty set.
+ """
+ if len(self.__set) != 1:
+ raise SetNotRepresentable()
+ return "%s-%s" % (intrep_to_dotted_decimal(self.__set[0][0]),
+ intrep_to_dotted_decimal(self.__set[0][1]))
+
+ def contains(self, ip):
+ """Return true if IP is contained in the set.
+
+ IP should be an ipaddr object. The empty ipaddr is never contained.
+ """
+
+ if ip.intrep == None:
+ return 0
+
+ for [lo, hi] in self.__set:
+ if lo <= ip.intrep <= hi:
+ return 1
+ return 0
+
+ def overlaps(self, nwip):
+ """Return true if NWIP overlaps the set of IP addresses.
+
+ NWIP may be an ipaddr, network or ip_set object.
+ """
+
+ if isinstance(nwip, ipaddr):
+ return self.contains(nwip)
+ elif isinstance(nwip, ip_set):
+ # This could be optimized -- we don't really need
+ # to compute the intersection.
+ return not self.intersection(nwip).is_empty()
+ elif isinstance(nwip, network):
+ wanted_low = nwip.network_intrep
+ wanted_high = nwip.broadcast_intrep
+ if wanted_low == None or wanted_high == None:
+ return 0
+ for [lo, hi] in self.__set:
+ if lo > wanted_high:
+ # We are past the interresting interval.
+ return 0
+ if lo >= wanted_low or hi >= wanted_low:
+ return 1
+ return 0
+ else:
+ raise BadType('Expected an ipaddr, ip_set or network instance')
+
+ def is_empty(self):
+ """Return true if this ip_set is empty.
+ """
+
+ return len(self.__set) == 0
+
+ def any_ip(self):
+ """Return one of the IP addresses contained in ip_set.
+
+ This method may only be called if the set is non-empty. You
+ can use the is_empty method to test for emptiness.
+
+ This picks an IP address from the set and returns it as an
+ ipaddr object. Given the same set of IP addresses, this
+ method will always return the same IP address, but which IP
+ address it chooses is explicitly undocumented and may change
+ if the underlying implementation of ip_set ever changes.
+ """
+
+ assert not self.is_empty()
+ return ipaddr(self.__set[0][0])
+
+ def __str__(self):
+ res = []
+ for rng in self.__set:
+ if rng[0] == rng[1]:
+ res.append(intrep_to_dotted_decimal(rng[0]))
+ else:
+ res.append('%s-%s' % (intrep_to_dotted_decimal(rng[0]),
+ intrep_to_dotted_decimal(rng[1])))
+ return '<ipaddr.ip_set(%s)>' % string.join(res, ', ')
+
+complete_network = network(0L, 0, DEMAND_FILTER)
+complete_set = ip_set(complete_network)
+broadcast_network = network('255.255.255.255', 32, DEMAND_FILTER)
+broadcast_set = ip_set(broadcast_network)
+
+def compute_neighbor(intrep, bits):
+ xor_mask = intrep_with_bit_set(bits)
+ and_mask = bits_to_intrep(bits)
+ return (intrep ^ xor_mask) & and_mask
+
+
+if __name__ == '__main__':
+ # Test/demo code. With no arguments, this will print a page
+ # of data that can be useful when trying to interpret an
+ # ipnumber/netmask pair. With two arguments, it will print some
+ # information about the IP number and netmask that was entered.
+
+ import sys
+ if len(sys.argv) == 1:
+ print "Netmasks\n========"
+ for i in range(0, 17):
+ if i != 16:
+ print '%2d' % i,
+ print '%-13s' % netmask(i, DEMAND_NONE).netmask_str(),
+ else:
+ print ' ' * 16,
+ print i + 16, '%-16s' % netmask(i + 16, DEMAND_NONE).netmask_str()
+ print _("\n\nIP intervals\n============")
+ for i in range(9):
+ for j in range(0, 4):
+ print '%2d' % (8*j + i),
+ print '%3d' % (netmask(i, DEMAND_NONE).intrep >> 24),
+ x = 0
+ need_break = 0
+ if i < 8:
+ for j in range(0, 256, pow(2, 8-i)):
+ if need_break:
+ print
+ print ' ' * 15,
+ need_break = 0
+ print '%3d-%-3d' % (j, j + pow(2, 8-i)-1),
+ x = x + 1
+ if x % 8 == 0:
+ need_break = 1
+ else:
+ print '0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13...',
+ print
+ sys.exit(0)
+
+ if len(sys.argv) != 3:
+ sys.stderr.write(_("Usage: python ipaddr.py IP_ADDRESS NETMASK\n"))
+ sys.exit(1)
+ nw = network(sys.argv[1], sys.argv[2], DEMAND_NONE)
+ print nw
+ print "IP address: ", nw.ip.ip_str()
+ print "Netmask: ", nw.mask.netmask_str(),
+ print " (/" + nw.mask.netmask_bits_str + ")"
+ print "Network address: ", nw.network_str()
+ print "Broadcast address:", nw.broadcast_str()