]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
more operations for ip addresses and prefixes
authorAlan T. DeKok <aland@freeradius.org>
Fri, 17 Dec 2021 20:09:34 +0000 (15:09 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Tue, 21 Dec 2021 17:22:10 +0000 (12:22 -0500)
src/lib/util/calc.c
src/tests/unit/calc.txt

index b74b0e64db1a2a1a2b6b3d00e387ee0f377e91cd..0921c2c3640ad677da02eac20773b7bcf4224a07 100644 (file)
@@ -43,6 +43,18 @@ RCSID("$Id$")
  *  sync.
  */
 static const fr_type_t upcast[FR_TYPE_MAX + 1][FR_TYPE_MAX + 1] = {
+       [FR_TYPE_IPV4_ADDR] = {
+               /*
+                *      ipaddr + int --> prefix (generally only "and")
+                */
+               [FR_TYPE_UINT32] =  FR_TYPE_IPV4_PREFIX,
+
+               /*
+                *      192.168.0.255 - 192.168.0.1 -> int64
+                */
+               [FR_TYPE_IPV4_ADDR] =  FR_TYPE_INT64,
+       },
+
        /*
         *      Prefix + int --> ipaddr
         */
@@ -734,6 +746,7 @@ static int calc_ipv4_addr(UNUSED TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_
 
        switch (op) {
        case T_ADD:
+       case T_OR:
                /*
                 *      For simplicity, make sure that the prefix is first.
                 */
@@ -756,24 +769,90 @@ static int calc_ipv4_addr(UNUSED TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_
                if (b->vb_uint32 >= (((uint32_t) 1) << a->vb_ip.prefix)) return ERR_OVERFLOW;
 
                dst->vb_ip.af = AF_INET;
-               dst->vb_ip.addr.v4.s_addr = htonl(ntohl(a->vb_ip.addr.v4.s_addr) + b->vb_uint32);
+               dst->vb_ip.addr.v4.s_addr = htonl(ntohl(a->vb_ip.addr.v4.s_addr) | b->vb_uint32);
                dst->vb_ip.prefix = 0;
                break;
 
-       case T_SUB:
-               /*
-                *      Subtract two addresses to see how far apart
-                *      they are.  If the caller does "0.0.0.0 -
-                *      255.xxxx", well, they deserve what they get.
-                */
-               if ((a->type != FR_TYPE_IPV4_ADDR) && (b->type != FR_TYPE_IPV4_ADDR)) {
-                       fr_strerror_const("Cannot perform operation on two 'ipaddr' types");
+       default:
+               return ERR_INVALID;
+       }
+
+       return 0;
+}
+
+static int calc_ipv4_prefix(UNUSED TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t const *a, fr_token_t op, fr_value_box_t const *b)
+{
+       int prefix;
+       fr_value_box_t one, two, tmp;
+
+       fr_assert(dst->type == FR_TYPE_IPV4_PREFIX);
+
+       switch (op) {
+       case T_AND:
+               if (fr_type_is_integer(a->type)) {
+                       if (fr_value_box_cast(NULL, &one, FR_TYPE_UINT32, NULL, a) < 0) return -1;
+
+                       a = &one;
+                       swap(a,b);
+
+               } else if (fr_type_is_integer(b->type)) {
+                       if (fr_value_box_cast(NULL, &two, FR_TYPE_UINT32, NULL, b) < 0) return -1;
+                       b = &two;
+
+               } else {
+                       fr_strerror_const("Invalid input types for ipv4prefix");
                        return -1;
                }
 
+               switch (a->type) {
+               case FR_TYPE_IPV6_ADDR:
+                       if (fr_value_box_cast(NULL, &tmp, FR_TYPE_IPV4_ADDR, NULL, a) < 0) return -1;
+                       break;
+
+               case FR_TYPE_IPV4_ADDR:
+                       break;
+
+               default:
+                       fr_strerror_printf("Invalid input data type '%s' for logical 'and'",
+                                          fr_table_str_by_value(fr_value_box_type_table, a->type, "<INVALID>"));
+
+                       return -1;
+               }
+
+               if (b->vb_uint32 == 0) { /* set everything to zero */
+                       dst->vb_ip.addr.v4.s_addr = 0;
+                       prefix = 0;
+
+               } else if ((~b->vb_uint32) == 0) { /* all 1's */
+                       dst->vb_ip.addr.v4.s_addr = a->vb_ip.addr.v4.s_addr;
+                       prefix = 32;
+
+               } else {
+                       uint32_t mask;
+
+                       mask = ~b->vb_uint32;   /* 0xff00 -> 0x00ff */
+                       mask++;                 /* 0x00ff -> 0x0100 */
+                       if ((mask & b->vb_uint32) != mask) {
+                               fr_strerror_printf("Invalid network mask '0x%08x'", b->vb_uint32);
+                               return -1;
+                       }
+
+                       mask = 0xfffffffe;
+                       prefix = 31;
+
+                       while (prefix > 0) {
+                               if (mask == b->vb_uint32) break;
+
+                               prefix--;
+                               mask <<= 1;
+                       }
+                       fr_assert(prefix > 0);
+
+                       dst->vb_ip.addr.v4.s_addr = htonl(ntohl(a->vb_ip.addr.v4.s_addr) & b->vb_uint32);
+               }
+
                dst->vb_ip.af = AF_INET;
-               dst->vb_ip.addr.v4.s_addr = htonl(ntohl(a->vb_ip.addr.v4.s_addr) - ntohl(b->vb_ip.addr.v4.s_addr));
-               dst->vb_ip.prefix = 0;
+               dst->vb_ip.prefix = prefix;
                break;
 
        default:
@@ -1147,6 +1226,7 @@ static const fr_binary_op_t calc_type[FR_TYPE_MAX + 1] = {
        [FR_TYPE_STRING]        = calc_string,
 
        [FR_TYPE_IPV4_ADDR]     = calc_ipv4_addr,
+       [FR_TYPE_IPV4_PREFIX]   = calc_ipv4_prefix,
        [FR_TYPE_IPV6_ADDR]     = calc_ipv6_addr,
 
        [FR_TYPE_UINT8]         = calc_integer,
index b9a44d3f68a04ab0ba3c232499e1479bc87c67f3..a85ea644e4ea13b370db4186e15124ee753b20d0 100644 (file)
@@ -121,7 +121,20 @@ match no
 calc ipv4prefix 127/8 + uint8 1 -> ipaddr
 match 127.0.0.1
 
-# Math tests
+calc ipv4addr 192.168.2.1 & uint32 0xffff0000 -> ipv4prefix
+match 192.168.0.0/16
+
+#  unknown destination type results in an IPv4 prefix for this operation.
+calc ipv4addr 192.168.2.1 & uint32 0xffff0000 -> null
+match 192.168.0.0/16
+
+calc ipv4addr 192.168.2.1 & uint32 0xffff0080 -> ipv4prefix
+match Invalid network mask '0xffff0080'
+
+######################################################################
+#
+#  Float
+#
 calc float32 1.0 + float32 2.5 -> float32
 match 3.500000
 
@@ -142,4 +155,4 @@ calc string "2" += string "test"
 match 2test
 
 count
-match 58
+match 64