module Cidr ( Cidr(..), cidr_from_string, combine_all ) where import IPv4Address import ListUtils import Maskable import Maskbits import Octet data Cidr = None | Cidr { ipv4address :: IPv4Address, maskbits :: Maskbits } deriving (Eq, Show) -- Returns the mask portion of a CIDR address. That is, everything -- after the trailing slash. maskbits_from_cidr_string :: String -> Maskbits maskbits_from_cidr_string s = maskbits_from_string ((splitWith (`elem` "/") s) !! 1) -- Takes an IP address String in CIDR notation, and returns a list of -- its octets (as Ints). octets_from_cidr_string :: String -> [Octet] octets_from_cidr_string s = map octet_from_string (take 4 (splitWith (`elem` "./") s)) cidr_from_string :: String -> Cidr cidr_from_string s | addr == IPv4Address.None = Cidr.None | mbits == Maskbits.None = Cidr.None | otherwise = Cidr addr mbits where addr = ipv4address_from_octets (oct1) (oct2) (oct3) (oct4) oct1 = (octs !! 0) oct2 = (octs !! 1) oct3 = (octs !! 2) oct4 = (octs !! 3) octs = octets_from_cidr_string s mbits = maskbits_from_cidr_string s -- Return true if the first argument (a CIDR range) contains the -- second (another CIDR range). There are a lot of ways we can be fed -- junk here. For lack of a better alternative, just return False when -- we are given nonsense. contains :: Cidr -> Cidr -> Bool contains Cidr.None _ = False contains _ Cidr.None = False contains (Cidr _ Maskbits.None) _ = False contains (Cidr IPv4Address.None _) _ = False contains _ (Cidr _ Maskbits.None) = False contains _ (Cidr IPv4Address.None _) = False -- If the number of bits in the network part of the first address is -- larger than the number of bits in the second, there is no way that -- the first range can contain the second. For, if the number of -- network bits is larger, then the number of host bits must be -- smaller, and if cidr1 has fewer hosts than cidr2, cidr1 most -- certainly does not contain cidr2. -- -- On the other hand, if the first argument (cidr1) has fewer (or the -- same number of) network bits as the second, it can contain the -- second. In this case, we need to check that every host in cidr2 is -- contained in cidr1. If a host in cidr2 is contained in cidr1, then -- at least mbits1 of an address in cidr2 will match cidr1. For -- example, -- -- cidr1 = 192.168.1.0/23, cidr2 = 192.168.1.100/24 -- -- Here, cidr2 contains all of 192.168.1.0 through -- 192.168.1.255. However, cidr1 contains BOTH 192.168.0.0 through -- 192.168.0.255 and 192.168.1.0 through 192.168.1.255. In essence, -- what we want to check is that cidr2 "begins with" something that -- cidr1 CAN begin with. Since cidr1 can begin with 192.168.1, and -- cidr2 DOES, cidr1 contains cidr2.. -- -- The way that we check this is to apply cidr1's mask to cidr2's -- address and see if the result is the same as cidr1's mask applied -- to cidr1's address. -- contains (Cidr addr1 mbits1) (Cidr addr2 mbits2) | mbits1 > mbits2 = False | otherwise = addr1masked == addr2masked where addr1masked = apply_mask addr1 mbits1 addr2masked = apply_mask addr2 mbits1 contains_proper :: Cidr -> Cidr -> Bool contains_proper cidr1 cidr2 = (cidr1 `contains` cidr2) && (not (cidr1 == cidr2)) -- A CIDR range is redundant (with respect to the given list) if -- another CIDR range in that list properly contains it. redundant :: [Cidr] -> Cidr -> Bool redundant cidrlist cidr = any ((flip contains_proper) cidr) cidrlist -- We want to get rid of all the Cidrs that are properly contained -- in some other Cidr. combine_all :: [Cidr] -> [Cidr] combine_all cidrs = filter (not . (redundant cidrs)) cidrs