diff options
Diffstat (limited to 'src/common/acl.c')
-rw-r--r-- | src/common/acl.c | 137 |
1 files changed, 74 insertions, 63 deletions
diff --git a/src/common/acl.c b/src/common/acl.c index 26d3847..fa20d62 100644 --- a/src/common/acl.c +++ b/src/common/acl.c @@ -19,88 +19,99 @@ #include <assert.h> #include <sys/types.h> #include <sys/socket.h> +#include <limits.h> #include "common/acl.h" +#include "libknot/util/endian.h" -static inline uint32_t acl_sa_ipv4(sockaddr_t *a) { +static inline uint32_t ipv4_chunk(sockaddr_t *a) +{ + /* Stored as big end first. */ return a->addr4.sin_addr.s_addr; } -static inline uint32_t acl_fill_mask32(short c) { - /*! \todo Consider optimizing using LUT. */ - assert(c >= 0 && c <= 32); - unsigned r = 0; - /*! This actually builds big-endian mask - * as we will match against addresses in - * network byte-order (big-endian). - * Otherwise it should be built from - * HO bit -> LO bit. - */ - for (char i = 0; i < c; ++i) { - r |= (1 << i); - } - return r; +static inline uint32_t ipv6_chunk(sockaddr_t *a, uint8_t idx) +{ + /* Is byte array, 4x 32bit value. */ + return ((uint32_t *)&a->addr6.sin6_addr)[idx]; } -static int acl_compare(void *k1, void *k2) +static inline uint32_t ip_chunk(sockaddr_t *a, uint8_t idx) { - sockaddr_t* a1 = (sockaddr_t *)k1; - sockaddr_t* a2 = (sockaddr_t *)k2; + if (sockaddr_family(a) == AF_INET) + return ipv4_chunk(a); - /* Check different length, IPv4 goes first. */ - int ldiff = a1->len - a2->len; - if (ldiff != 0) { - return ldiff < 0 ? -1 : 1; - } + return ipv6_chunk(a, idx); +} - /* Compare integers if IPv4. */ - if (sockaddr_family(a1) == AF_INET) { +/*! \brief Compare chunks using given mask. */ +static int cmp_chunk(sockaddr_t *a, sockaddr_t *b, uint8_t idx, uint32_t mask) +{ + const uint32_t c1 = ip_chunk(a, idx) & mask; + const uint32_t c2 = ip_chunk(b, idx) & mask; - /* Compute mask .*/ - uint32_t mask = acl_fill_mask32(a1->prefix); + if (c1 > c2) + return 1; + if (c1 < c2) + return -1; + return 0; +} - /* Compare address. */ - int cmp1 = (acl_sa_ipv4(a1) & mask); - int cmp2 = (acl_sa_ipv4(a2) & mask); - if (cmp1 > cmp2) return 1; - if (cmp1 < cmp2) return -1; - return 0; +/*! + * \brief Calculate bitmask for byte array from the MSB. + * + * \note i.e. 8 means top 8 bits set, 11111111000000000000000000000000 + * + * \param nbits number of bits set to 1 + * \return mask + */ +static uint32_t acl_fill_mask32(short nbits) +{ + assert(nbits >= 0 && nbits <= 32); + uint32_t r = 0; + for (char i = 0; i < nbits; ++i) { + r |= 1 << (31 - i); } - /* IPv6 matching. */ -#ifndef DISABLE_IPV6 - if (sockaddr_family(a1) == AF_INET6) { - - /* Get mask .*/ - short chunk = a1->prefix; - - /* Compare address by 32bit chunks. */ - uint32_t* a1p = (uint32_t *)(&a1->addr6.sin6_addr); - uint32_t* a2p = (uint32_t *)(&a2->addr6.sin6_addr); - - /* Mask 0 = 0 bits to compare from LO->HO (in big-endian). - * Mask 128 = 128 bits to compare. - */ - while (chunk > 0) { - uint32_t mask = 0xffffffff; - if ((size_t)chunk > sizeof(mask) << 3) { - chunk -= sizeof(mask) << 3; - } else { - mask = acl_fill_mask32(chunk); - chunk = 0; - } - - int cmp1 = (*(a1p++) & mask); - int cmp2 = (*(a2p++) & mask); - if (cmp1 > cmp2) return 1; - if (cmp1 < cmp2) return -1; + /* Make sure the mask is in network byte order. */ + return htonl(r); +} + +static int acl_compare(void *k1, void *k2) +{ + int ret = 0; + sockaddr_t* a1 = (sockaddr_t *)k1; + sockaddr_t* a2 = (sockaddr_t *)k2; + uint32_t mask = 0xffffffff; + short mask_bits = a1->prefix; + const short chunk_bits = sizeof(mask) * CHAR_BIT; + + /* Check different length, IPv4 goes first. */ + if (a1->len != a2->len) { + if (a1->len < a2->len) + return -1; + else + return 1; + } + + /* At most 4xchunk_bits for IPv6 */ + unsigned i = 0; + while (ret == 0 && mask_bits > 0) { + /* Compute mask for current chunk. */ + if (mask_bits <= chunk_bits) { + mask = acl_fill_mask32(mask_bits); + mask_bits = 0; /* Last chunk */ + } else { + mask_bits -= chunk_bits; } - return 0; + /* Empty mask - shortcut, we're done. */ + if (mask > 0) + ret = cmp_chunk(a1, a2, i, mask); + ++i; } -#endif - return 0; + return ret; } acl_t *acl_new(acl_rule_t default_rule, const char *name) |