summaryrefslogtreecommitdiff
path: root/src/common/acl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/acl.c')
-rw-r--r--src/common/acl.c137
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)