LCOV - code coverage report
Current view: top level - libaddr - addr_range.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 52 131 39.7 %
Date: 2022-03-01 21:05:13 Functions: 15 19 78.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2012-2021  Made to Order Software Corp.  All Rights Reserved
       2             : //
       3             : // https://snapwebsites.org/project/libaddr
       4             : //
       5             : // Permission is hereby granted, free of charge, to any person obtaining a
       6             : // copy of this software and associated documentation files (the
       7             : // "Software"), to deal in the Software without restriction, including
       8             : // without limitation the rights to use, copy, modify, merge, publish,
       9             : // distribute, sublicense, and/or sell copies of the Software, and to
      10             : // permit persons to whom the Software is furnished to do so, subject to
      11             : // the following conditions:
      12             : //
      13             : // The above copyright notice and this permission notice shall be included
      14             : // in all copies or substantial portions of the Software.
      15             : //
      16             : // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      17             : // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
      18             : // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
      19             : // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
      20             : // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
      21             : // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
      22             : // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
      23             : 
      24             : 
      25             : /** \file
      26             :  * \brief The implementation of the addr_range class.
      27             :  *
      28             :  * This file includes the implementation of the addr_range class
      29             :  * and the address_match_ranges() global function.
      30             :  */
      31             : 
      32             : // self
      33             : //
      34             : #include    "libaddr/addr_range.h"
      35             : #include    "libaddr/addr_exception.h"
      36             : 
      37             : 
      38             : // C++ library
      39             : //
      40             : #include    <algorithm>
      41             : 
      42             : 
      43             : // last include
      44             : //
      45             : #include    <snapdev/poison.h>
      46             : 
      47             : 
      48             : 
      49             : namespace addr
      50             : {
      51             : 
      52             : 
      53             : /** \brief Return true if the range has a 'from' address defined.
      54             :  *
      55             :  * By default the 'from' and 'to' addresses of an addr_range are legal
      56             :  * but considered undefined. After you called the set_from() function
      57             :  * once, this function will always return true.
      58             :  *
      59             :  * \return false until 'set_from()' is called at least once.
      60             :  */
      61         325 : bool addr_range::has_from() const
      62             : {
      63         325 :     return f_has_from;
      64             : }
      65             : 
      66             : 
      67             : /** \brief Return true if the range has a 'to' address defined.
      68             :  *
      69             :  * By default the 'from' and 'to' addresses of an addr_range are legal
      70             :  * but considered undefined. After you called the set_to() function
      71             :  * once, this function will always return true.
      72             :  *
      73             :  * \return false until 'set_to()' is called at least once.
      74             :  */
      75         292 : bool addr_range::has_to() const
      76             : {
      77         292 :     return f_has_to;
      78             : }
      79             : 
      80             : 
      81             : /** \brief Determine whether an addr_range object is considered a range.
      82             :  *
      83             :  * This function returns false until both, set_from() and set_to(),
      84             :  * were called.
      85             :  *
      86             :  * Note that the order in which the two functions get called is not
      87             :  * important, although we generally expect set_from() to be called
      88             :  * first, it does not matter.
      89             :  *
      90             :  * \return true if both, 'from' and 'to', were set.
      91             :  */
      92        1045 : bool addr_range::is_range() const
      93             : {
      94        1045 :     return f_has_from && f_has_to;
      95             : }
      96             : 
      97             : 
      98             : /** \brief Check whether this range is empty.
      99             :  *
     100             :  * If you defined the 'from' and 'to' addresses of the range, then you
     101             :  * can check whether the range is empty or not.
     102             :  *
     103             :  * A range is considered empty if 'from' is larger than 'to' because
     104             :  * in that case nothing can appear in between (no IP can at the same
     105             :  * time be larger than 'from' and smaller than 'to' if 'from > to'
     106             :  * is true.)
     107             :  *
     108             :  * \return true if 'from > to' and is_range() returns true.
     109             :  *
     110             :  * \sa is_range()
     111             :  * \sa has_from()
     112             :  * \sa has_to()
     113             :  */
     114         309 : bool addr_range::is_empty() const
     115             : {
     116         309 :     if(!is_range())
     117             :     {
     118         289 :         return false;
     119             :     }
     120          20 :     return f_from > f_to;
     121             : }
     122             : 
     123             : 
     124             : /** \brief Check whether \p rhs is part of this range.
     125             :  *
     126             :  * If the address specified in rhs is part of this range, then the function
     127             :  * returns true. The 'from' and 'to' addresses are considered inclusive,
     128             :  * so if rhs is equal to 'from' or 'to', then the function returns true.
     129             :  *
     130             :  * If 'from' is larger than 'to' then the function already returns false
     131             :  * since the range represents an empty range.
     132             :  *
     133             :  * \exception addr_invalid_state_exception
     134             :  * The addr_range object must be a range or this function throws this
     135             :  * exception. To test whether you can call this function, first call
     136             :  * the is_range() function. If it returns true, then is_in() is available.
     137             :  *
     138             :  * \param[in] rhs  The address to check for inclusion.
     139             :  *
     140             :  * \return true if rhs is considered part of this range.
     141             :  */
     142         417 : bool addr_range::is_in(addr const & rhs) const
     143             : {
     144         417 :     if(!is_range())
     145             :     {
     146          10 :         throw addr_invalid_state("addr_range::is_in(): range is not complete (from or to missing.)");
     147             :     }
     148             : 
     149         407 :     if(f_from <= f_to)
     150             :     {
     151             :         //
     152         285 :         return rhs >= f_from && rhs <= f_to;
     153             :     }
     154             :     //else -- from/to are swapped... this represents an empty range
     155             : 
     156         122 :     return false;
     157             : }
     158             : 
     159             : 
     160             : /** \brief Check whether this range is an IPv4 range.
     161             :  *
     162             :  * This function verifies whether this range represents an IPv6 range or
     163             :  * an IPv4 range.
     164             :  *
     165             :  * If the range is not defined (no from and no to) then the function
     166             :  * always returns false.
     167             :  *
     168             :  * \return true if the range represents an IPv4 address.
     169             :  */
     170           0 : bool addr_range::is_ipv4() const
     171             : {
     172           0 :     if(f_has_from && f_has_to)
     173             :     {
     174           0 :         return f_from.is_ipv4() && f_to.is_ipv4();
     175             :     }
     176             : 
     177           0 :     if(f_has_from)
     178             :     {
     179           0 :         return f_from.is_ipv4();
     180             :     }
     181             : 
     182           0 :     if(f_has_to)
     183             :     {
     184           0 :         return f_to.is_ipv4();
     185             :     }
     186             : 
     187           0 :     return false;
     188             : }
     189             : 
     190             : 
     191             : /** \brief Set 'from' address.
     192             :  *
     193             :  * This function saves the 'from' address in this range object.
     194             :  *
     195             :  * Once this function was called at least once, the has_from() returns true.
     196             :  *
     197             :  * \param[in] from  The address to save as the 'from' address.
     198             :  */
     199      131869 : void addr_range::set_from(addr const & from)
     200             : {
     201      131869 :     f_has_from = true;
     202      131869 :     f_from = from;
     203      131869 : }
     204             : 
     205             : 
     206             : /** \brief Get 'from' address.
     207             :  *
     208             :  * This function return the 'from' address as set by the set_from()
     209             :  * functions.
     210             :  *
     211             :  * The get_from() function can be called even if the has_from()
     212             :  * function returns false. It will return a default address
     213             :  * (a new 'addr' object.)
     214             :  *
     215             :  * \return The address saved as the 'from' address.
     216             :  */
     217         568 : addr & addr_range::get_from()
     218             : {
     219         568 :     return f_from;
     220             : }
     221             : 
     222             : 
     223             : /** \brief Get the 'from' address when addr_range is constant.
     224             :  *
     225             :  * This function return the 'from' address as set by the set_from()
     226             :  * functions.
     227             :  *
     228             :  * The get_from() function can be called even if the has_from()
     229             :  * function returns false. It will return a default address
     230             :  * (a new 'addr' object.)
     231             :  *
     232             :  * \return The address saved as the 'from' address.
     233             :  */
     234      131793 : addr const & addr_range::get_from() const
     235             : {
     236      131793 :     return f_from;
     237             : }
     238             : 
     239             : 
     240             : /** \brief Set 'to' address.
     241             :  *
     242             :  * This function saves the 'to' address in this range object.
     243             :  *
     244             :  * Once this function was called at least once, the has_to() returns true.
     245             :  *
     246             :  * \param[in] to  The address to save as the 'to' address.
     247             :  */
     248           8 : void addr_range::set_to(addr const & to)
     249             : {
     250           8 :     f_has_to = true;
     251           8 :     f_to = to;
     252           8 : }
     253             : 
     254             : 
     255             : /** \brief Get the 'to' address.
     256             :  *
     257             :  * This function return the 'to' address as set by the set_to()
     258             :  * function.
     259             :  *
     260             :  * The get_from() function can be called even if the has_from()
     261             :  * function returns false. It will return a default address
     262             :  * (a new 'addr' object.)
     263             :  *
     264             :  * \return The address saved as the 'to' address.
     265             :  */
     266           9 : addr & addr_range::get_to()
     267             : {
     268           9 :     return f_to;
     269             : }
     270             : 
     271             : 
     272             : /** \brief Get the 'to' address when addr_range is constant.
     273             :  *
     274             :  * This function return the 'to' address as set by the set_to()
     275             :  * function.
     276             :  *
     277             :  * The get_to() function can be called even if the has_to()
     278             :  * function returns false. It will return a default address
     279             :  * (a new 'addr' object.)
     280             :  *
     281             :  * \return The address saved as the 'to' address.
     282             :  */
     283           7 : addr const & addr_range::get_to() const
     284             : {
     285           7 :     return f_to;
     286             : }
     287             : 
     288             : 
     289             : /** \brief Transform an address to a range.
     290             :  *
     291             :  * This function transforms an address (\p a) in a range.
     292             :  *
     293             :  * This is useful if you have a CIDR type of address (an address with a
     294             :  * mask length defined along with it) and want to generate a range with
     295             :  * it.
     296             :  *
     297             :  * The range is defined as (a & mask) for the "from" address and
     298             :  * (a | ~mask) for the "to" address. If the mask is all 1s, then the
     299             :  * resulting range is just (a).
     300             :  *
     301             :  * \exception addr_unsupported_as_range
     302             :  * If the address cannot be transform into a range, this exception is raised.
     303             :  * This happens if the mask is not just ending with 0s but 0s are found
     304             :  * earlier.
     305             :  *
     306             :  * \param[in] a  The address to convert to a range.
     307             :  */
     308           0 : void addr_range::from_cidr(addr const & a)
     309             : {
     310           0 :     std::uint8_t mask[16];
     311           0 :     a.get_mask(mask);
     312           0 :     bool found(false);
     313           0 :     sockaddr_in6 from = {};
     314           0 :     a.get_ipv6(from);
     315           0 :     sockaddr_in6 to(from);
     316           0 :     for(std::size_t i(0); i < sizeof(mask); ++i)
     317             :     {
     318           0 :         std::uint8_t inverse = ~mask[i];
     319             : 
     320           0 :         from.sin6_addr.s6_addr[i] &= mask[i];
     321           0 :         to.sin6_addr.s6_addr[i] |= inverse;
     322             : 
     323           0 :         if(found)
     324             :         {
     325           0 :             if(inverse != 0xFF)
     326             :             {
     327           0 :                 throw addr_unsupported_as_range("unsupported mask for a range");
     328             :             }
     329             :         }
     330             :         else
     331             :         {
     332           0 :             if(inverse != 0)
     333             :             {
     334           0 :                 found = true;
     335           0 :                 switch(inverse)
     336             :                 {
     337           0 :                 case 0x01:
     338             :                 case 0x03:
     339             :                 case 0x07:
     340             :                 case 0x0F:
     341             :                 case 0x1F:
     342             :                 case 0x3F:
     343             :                 case 0x7F:
     344             :                 case 0xFF:
     345           0 :                     break;
     346             : 
     347           0 :                 default:
     348           0 :                     throw addr_unsupported_as_range("unsupported mask for a range");
     349             : 
     350             :                 }
     351             :             }
     352             :         }
     353             :     }
     354             : 
     355           0 :     set_from(from);
     356           0 :     set_to(to);
     357           0 : }
     358             : 
     359             : 
     360             : /** \brief Compute a new range with the part that is shared between both inputs.
     361             :  *
     362             :  * This function computers a range which encompasses all the addresses found
     363             :  * in \p this range and \p rhs range.
     364             :  *
     365             :  * If the two range do not intersect, then the resulting range will be an
     366             :  * empty range (see is_empty() for details).
     367             :  *
     368             :  * The new range receives the largest 'from' address from both inputs and
     369             :  * the smallest 'to' address from both inputs.
     370             :  *
     371             :  * \param[in] rhs  The other range to compute the intersection with.
     372             :  *
     373             :  * \return The resulting intersection range.
     374             :  *
     375             :  * \sa is_empty()
     376             :  */
     377           2 : addr_range addr_range::intersection(addr_range const & rhs) const
     378             : {
     379           2 :     addr_range result;
     380             : 
     381           2 :     result.set_from(f_from > rhs.f_from ? f_from : rhs.f_from);
     382           2 :     result.set_to  (f_to   < rhs.f_to   ? f_to   : rhs.f_to);
     383             : 
     384           2 :     return result;
     385             : }
     386             : 
     387             : 
     388             : /** \brief Compute a new range with the union of two address ranges.
     389             :  *
     390             :  * This function checks whether the two ranges have any addresses in
     391             :  * common or if they are just one after the other. If so, then it
     392             :  * computes the union of both ranges. Otherwise it returns an empty
     393             :  * range.
     394             :  *
     395             :  * \note
     396             :  * If the `from` and `to` addresses of the range have a mask, it is
     397             :  * ignored.
     398             :  *
     399             :  * \param[in] rhs  The other range to compute the intersection with.
     400             :  *
     401             :  * \return The resulting intersection range.
     402             :  *
     403             :  * \sa is_empty()
     404             :  */
     405           0 : addr_range addr_range::union_if_possible(addr_range const & rhs) const
     406             : {
     407           0 :     addr_range result;
     408             : 
     409           0 :     if((f_from <= rhs.f_to || f_from.is_previous(rhs.f_to)
     410           0 :     && (f_to >= rhs.f_from || f_to.is_next(rhs.f_from))))
     411             :     {
     412           0 :         result.set_from(f_from < rhs.f_from ? f_from : rhs.f_from);
     413           0 :         result.set_from(f_to   > rhs.f_to   ? f_to   : rhs.f_to);
     414             :     }
     415             : 
     416           0 :     return result;
     417             : }
     418             : 
     419             : 
     420             : /** \brief Check whether an address matches a range.
     421             :  *
     422             :  * This function checks whether an address matches a range of addresses.
     423             :  *
     424             :  * The range may be empty, in which case the result is always false.
     425             :  *
     426             :  * If the range is a range (i.e. 'from' and 'to' are both defined,)
     427             :  * then the is_in() function is used to determine whether the address
     428             :  * is a match.
     429             :  *
     430             :  * If only one of the 'from' or 'to' addresses is defined, then that
     431             :  * one address addr::match() function is used to determine whether the
     432             :  * input \p address is a match.
     433             :  *
     434             :  * \param[in] address  The address to match against a range of addresses.
     435             :  *
     436             :  * \return true if address matches this range.
     437             :  */
     438          24 : bool addr_range::match(addr const & address) const
     439             : {
     440             :     // if neith 'from' nor 'to' were defined, return
     441             :     //
     442          24 :     if(is_empty())
     443             :     {
     444           3 :         return false;
     445             :     }
     446             : 
     447          21 :     if(is_range())
     448             :     {
     449           9 :         return is_in(address);
     450             :     }
     451             : 
     452          12 :     if(has_from())
     453             :     {
     454           6 :         return f_from.match(address);
     455             :     }
     456             :     else
     457             :     {
     458             :         // if not empty and it does not have 'from', it has to be 'to'
     459             :         //
     460           6 :         return f_to.match(address);
     461             :     }
     462             : }
     463             : 
     464             : 
     465             : /** \brief Compare an address range against another.
     466             :  *
     467             :  * Comparing two address ranges against each other can return one of many
     468             :  * possible results (See the compare_t enumeration).
     469             :  *
     470             :  * \param[in] rhs  The right hand side to compare against this range.
     471             :  *
     472             :  * \return One of the compare_t values.
     473             :  */
     474           0 : compare_t addr_range::compare(addr_range const & rhs) const
     475             : {
     476             :     // check for the empty case first
     477             :     //
     478           0 :     if(is_empty())
     479             :     {
     480           0 :         if(rhs.is_empty())
     481             :         {
     482           0 :             return compare_t::COMPARE_UNORDERED;
     483             :         }
     484             : 
     485           0 :         return compare_t::COMPARE_LAST;
     486             :     }
     487           0 :     else if(rhs.is_empty())
     488             :     {
     489           0 :         return compare_t::COMPARE_FIRST;
     490             :     }
     491             : 
     492             :     // IPv4 versus IPv6
     493             :     //
     494           0 :     if(is_ipv4())
     495             :     {
     496           0 :         if(!rhs.is_ipv4())
     497             :         {
     498           0 :             return compare_t::COMPARE_IPV4_VS_IPV6;
     499             :         }
     500             :     }
     501           0 :     else if(rhs.is_ipv4())
     502             :     {
     503           0 :         return compare_t::COMPARE_IPV6_VS_IPV4;
     504             :     }
     505             : 
     506             :     // no overlap (lhs < rhs)
     507             :     //
     508           0 :     if(f_to < rhs.f_from)
     509             :     {
     510           0 :         if(f_to.is_next(rhs.f_from))
     511             :         {
     512           0 :             return compare_t::COMPARE_FOLLOWS;
     513             :         }
     514           0 :         return compare_t::COMPARE_SMALL_VS_LARGE;
     515             :     }
     516             : 
     517             :     // no overlap (lhs > rhs)
     518             :     //
     519           0 :     if(f_from > rhs.f_to)
     520             :     {
     521           0 :         if(f_to.is_previous(rhs.f_from))
     522             :         {
     523           0 :             return compare_t::COMPARE_FOLLOWING;
     524             :         }
     525           0 :         return compare_t::COMPARE_LARGE_VS_SMALL;
     526             :     }
     527             : 
     528             :     // overlap (lhs <= rhs)
     529             :     //
     530           0 :     if(f_from <= rhs.f_from
     531           0 :     && f_to >= rhs.f_from)
     532             :     {
     533           0 :         if(f_to >= rhs.f_to)
     534             :         {
     535           0 :             if(f_from == rhs.f_from
     536           0 :             && f_to == rhs.f_to)
     537             :             {
     538           0 :                 return compare_t::COMPARE_EQUAL;
     539             :             }
     540           0 :             return compare_t::COMPARE_INCLUDED;
     541             :         }
     542           0 :         if(f_from == rhs.f_from)
     543             :         {
     544           0 :             return compare_t::COMPARE_INCLUDES;
     545             :         }
     546           0 :         return compare_t::COMPARE_OVERLAP_SMALL_VS_LARGE;
     547             :     }
     548             : 
     549             :     // overlap (lhs >= rhs)
     550             :     //
     551           0 :     if(f_to >= rhs.f_from
     552           0 :     && f_to <= rhs.f_to)
     553             :     {
     554           0 :         if(f_from >= rhs.f_from)
     555             :         {
     556             :             // the previous block already captured this case
     557             :             //
     558             :             //if(f_from == rhs.f_from
     559             :             //&& f_to == rhs.f_to)
     560             :             //{
     561             :             //    return compare_t::COMPARE_EQUAL;
     562             :             //}
     563             : 
     564           0 :             return compare_t::COMPARE_INCLUDES;
     565             :         }
     566           0 :         if(f_to == rhs.f_to)
     567             :         {
     568           0 :             return compare_t::COMPARE_INCLUDED;
     569             :         }
     570           0 :         return compare_t::COMPARE_OVERLAP_LARGE_VS_SMALL;
     571             :     }
     572             : 
     573             :     // no overlap
     574             :     //
     575           0 :     return f_to < rhs.f_from
     576           0 :             ? compare_t::COMPARE_SMALLER
     577           0 :             : compare_t::COMPARE_LARGER;
     578             : }
     579             : 
     580             : 
     581             : /** \brief Check whether an address matches a range.
     582             :  *
     583             :  * When you call the addr_parser::parse() function, you get a vector of
     584             :  * ranges as a result. This function allows you to check whether an
     585             :  * address matches any one of those ranges.
     586             :  *
     587             :  * \param[in] ranges  The vector of ranges to search for \p address.
     588             :  * \param[in] address  The address to search in \p ranges.
     589             :  *
     590             :  * \return true if \p address matches any one of the \p ranges.
     591             :  */
     592           4 : bool address_match_ranges(addr_range::vector_t ranges, addr const & address)
     593             : {
     594           4 :     auto const it(std::find_if
     595             :             ( ranges.begin()
     596             :             , ranges.end()
     597           6 :             , [&address](auto const & range)
     598           6 :                 {
     599             :                     return range.match(address);
     600           6 :                 }
     601           4 :             ));
     602             : 
     603           4 :     return it != ranges.end();
     604             : }
     605             : 
     606             : 
     607             : 
     608             : 
     609             : }
     610             : // namespace addr
     611             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13