You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
342 lines
10 KiB
342 lines
10 KiB
/* -*- coding: utf-8 -*-
|
|
* ----------------------------------------------------------------------
|
|
* Copyright © 2010-2012, RedJack, LLC.
|
|
* All rights reserved.
|
|
*
|
|
* Please see the LICENSE.txt file in this distribution for license
|
|
* details.
|
|
* ----------------------------------------------------------------------
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include <libcork/core.h>
|
|
|
|
#include "ipset/bdd/nodes.h"
|
|
#include "ipset/bits.h"
|
|
#include "ipset/ipset.h"
|
|
#include "ipset/logging.h"
|
|
|
|
|
|
#define IPV4_BIT_SIZE 32
|
|
#define IPV6_BIT_SIZE 128
|
|
|
|
|
|
/* Forward declarations */
|
|
|
|
static void
|
|
process_assignment(struct ipset_iterator *iterator);
|
|
|
|
static void
|
|
expand_ipv6(struct ipset_iterator *iterator);
|
|
|
|
|
|
/**
|
|
* Find the highest non-EITHER bit in an assignment, starting from the
|
|
* given bit index.
|
|
*/
|
|
static unsigned int
|
|
find_last_non_either_bit(struct ipset_assignment *assignment,
|
|
unsigned int starting_bit)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = starting_bit; i >= 1; i--) {
|
|
enum ipset_tribool value = ipset_assignment_get(assignment, i);
|
|
if (value != IPSET_EITHER) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Create a generic IP address object from the current expanded
|
|
* assignment.
|
|
*/
|
|
static void
|
|
create_ip_address(struct ipset_iterator *iterator)
|
|
{
|
|
struct cork_ip *addr = &iterator->addr;
|
|
struct ipset_expanded_assignment *exp = iterator->assignment_iterator;
|
|
|
|
/* Initialize the address to all 0 bits. */
|
|
memset(addr, 0, sizeof(struct cork_ip));
|
|
|
|
/* Check variable 0 to see if this is an IPv4 or IPv6 address. */
|
|
addr->version = IPSET_BIT_GET(exp->values.buf, 0)? 4: 6;
|
|
|
|
/* Copy bits from the expanded assignment. The number of bits to
|
|
* copy is given as the current netmask. We'll have calculated that
|
|
* already based on the non-expanded assignment. */
|
|
unsigned int i;
|
|
for (i = 0; i < iterator->cidr_prefix; i++) {
|
|
IPSET_BIT_SET(&addr->ip, i, IPSET_BIT_GET(exp->values.buf, i+1));
|
|
}
|
|
|
|
#if IPSET_DEBUG
|
|
char buf[CORK_IP_STRING_LENGTH];
|
|
cork_ip_to_raw_string(addr, buf);
|
|
DEBUG("Current IP address is %s/%u", buf, iterator->cidr_prefix);
|
|
#endif
|
|
}
|
|
|
|
|
|
/**
|
|
* Advance the BDD iterator, taking into account that some assignments
|
|
* need to be expanded twice.
|
|
*/
|
|
static void
|
|
advance_assignment(struct ipset_iterator *iterator)
|
|
{
|
|
/* Check the current state of the iterator to determine how to
|
|
* advance. */
|
|
|
|
/* In most cases, the assignment we just finished only needed to be
|
|
* expanded once. So we move on to the next assignment and process
|
|
* it. */
|
|
if (CORK_LIKELY(iterator->multiple_expansion_state ==
|
|
IPSET_ITERATOR_NORMAL))
|
|
{
|
|
ipset_bdd_iterator_advance(iterator->bdd_iterator);
|
|
process_assignment(iterator);
|
|
return;
|
|
}
|
|
|
|
/* If the assignment needs to be expanded twice, we'll do the IPv4
|
|
* expansion first. If that's what we've just finished, do the IPv6
|
|
* expansion next. */
|
|
|
|
if (iterator->multiple_expansion_state == IPSET_ITERATOR_MULTIPLE_IPV4) {
|
|
DEBUG("Expanding IPv6 second");
|
|
|
|
iterator->multiple_expansion_state = IPSET_ITERATOR_MULTIPLE_IPV6;
|
|
ipset_assignment_set
|
|
(iterator->bdd_iterator->assignment, 0, IPSET_FALSE);
|
|
expand_ipv6(iterator);
|
|
return;
|
|
}
|
|
|
|
/* If we've just finished the IPv6 expansion, then we've finished
|
|
* with this assignment. Before moving on to the next one, we have
|
|
* to reset variable 0 to EITHER (which it was before we started
|
|
* this whole mess). */
|
|
|
|
if (iterator->multiple_expansion_state == IPSET_ITERATOR_MULTIPLE_IPV6) {
|
|
DEBUG("Finished both expansions");
|
|
|
|
ipset_assignment_set
|
|
(iterator->bdd_iterator->assignment, 0, IPSET_EITHER);
|
|
ipset_bdd_iterator_advance(iterator->bdd_iterator);
|
|
process_assignment(iterator);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Process the current expanded assignment in the current BDD
|
|
* assignment.
|
|
*/
|
|
static void
|
|
process_expanded_assignment(struct ipset_iterator *iterator)
|
|
{
|
|
if (iterator->assignment_iterator->finished) {
|
|
/* If there isn't anything in the expanded assignment, advance
|
|
* to the next BDD assignment. */
|
|
|
|
DEBUG("Expanded assignment is finished");
|
|
ipset_expanded_assignment_free(iterator->assignment_iterator);
|
|
iterator->assignment_iterator = NULL;
|
|
advance_assignment(iterator);
|
|
} else {
|
|
/* Otherwise, we've found a fully expanded assignment, so create
|
|
* an IP address for it and return. */
|
|
create_ip_address(iterator);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Expand the current assignment as IPv4 addresses.
|
|
*/
|
|
static void
|
|
expand_ipv4(struct ipset_iterator *iterator)
|
|
{
|
|
unsigned int last_bit;
|
|
|
|
if (iterator->summarize) {
|
|
last_bit = find_last_non_either_bit
|
|
(iterator->bdd_iterator->assignment, IPV4_BIT_SIZE);
|
|
DEBUG("Last non-either bit is %u", last_bit);
|
|
} else {
|
|
last_bit = IPV4_BIT_SIZE;
|
|
}
|
|
|
|
iterator->assignment_iterator =
|
|
ipset_assignment_expand
|
|
(iterator->bdd_iterator->assignment, last_bit + 1);
|
|
iterator->cidr_prefix = last_bit;
|
|
|
|
process_expanded_assignment(iterator);
|
|
}
|
|
|
|
|
|
/**
|
|
* Expand the current assignment as IPv4 addresses.
|
|
*/
|
|
static void
|
|
expand_ipv6(struct ipset_iterator *iterator)
|
|
{
|
|
unsigned int last_bit;
|
|
|
|
if (iterator->summarize) {
|
|
last_bit = find_last_non_either_bit
|
|
(iterator->bdd_iterator->assignment, IPV6_BIT_SIZE);
|
|
DEBUG("Last non-either bit is %u", last_bit);
|
|
} else {
|
|
last_bit = IPV6_BIT_SIZE;
|
|
}
|
|
|
|
iterator->assignment_iterator =
|
|
ipset_assignment_expand
|
|
(iterator->bdd_iterator->assignment, last_bit + 1);
|
|
iterator->cidr_prefix = last_bit;
|
|
|
|
process_expanded_assignment(iterator);
|
|
}
|
|
|
|
|
|
/**
|
|
* Process the current assignment in the BDD iterator.
|
|
*/
|
|
|
|
static void
|
|
process_assignment(struct ipset_iterator *iterator)
|
|
{
|
|
while (!iterator->bdd_iterator->finished) {
|
|
if (iterator->bdd_iterator->value == iterator->desired_value) {
|
|
/* If the BDD iterator hasn't finished, and the result of
|
|
* the function with this assignment matches what the caller
|
|
* wants, then we've found an assignment to generate IP
|
|
* addresses from.
|
|
*
|
|
* Try to expand this assignment, and process the first
|
|
* expanded assignment. We want 32 + 1 variables if the
|
|
* current address is IPv4; 128 + 1 if it's IPv6. */
|
|
|
|
DEBUG("Got a matching BDD assignment");
|
|
enum ipset_tribool address_type = ipset_assignment_get
|
|
(iterator->bdd_iterator->assignment, 0);
|
|
|
|
if (address_type == IPSET_FALSE) {
|
|
/* FALSE means IPv6*/
|
|
DEBUG("Assignment is IPv6");
|
|
iterator->multiple_expansion_state = IPSET_ITERATOR_NORMAL;
|
|
expand_ipv6(iterator);
|
|
return;
|
|
} else if (address_type == IPSET_TRUE) {
|
|
/* TRUE means IPv4*/
|
|
DEBUG("Assignment is IPv4");
|
|
iterator->multiple_expansion_state = IPSET_ITERATOR_NORMAL;
|
|
expand_ipv4(iterator);
|
|
return;
|
|
} else {
|
|
/* EITHER means that this assignment contains both IPv4
|
|
* and IPv6 addresses. Expand it as IPv4 first. */
|
|
DEBUG("Assignment is both IPv4 and IPv6");
|
|
DEBUG("Expanding IPv4 first");
|
|
iterator->multiple_expansion_state =
|
|
IPSET_ITERATOR_MULTIPLE_IPV4;
|
|
ipset_assignment_set
|
|
(iterator->bdd_iterator->assignment, 0, IPSET_TRUE);
|
|
expand_ipv4(iterator);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* The BDD iterator has a value, but it doesn't match the one we
|
|
* want. Advance the BDD iterator and try again. */
|
|
DEBUG("Value is %d, skipping", iterator->bdd_iterator->value);
|
|
ipset_bdd_iterator_advance(iterator->bdd_iterator);
|
|
}
|
|
|
|
/* If we fall through, then the BDD iterator has finished. That
|
|
* means there's nothing left for the set iterator. */
|
|
|
|
DEBUG("Set iterator is finished");
|
|
ipset_expanded_assignment_free(iterator->assignment_iterator);
|
|
iterator->assignment_iterator = NULL;
|
|
|
|
ipset_bdd_iterator_free(iterator->bdd_iterator);
|
|
iterator->bdd_iterator = NULL;
|
|
iterator->finished = true;
|
|
}
|
|
|
|
|
|
static struct ipset_iterator *
|
|
create_iterator(struct ip_set *set, bool desired_value, bool summarize)
|
|
{
|
|
/* First allocate the iterator itself. */
|
|
struct ipset_iterator *iterator = cork_new(struct ipset_iterator);
|
|
iterator->finished = false;
|
|
iterator->assignment_iterator = NULL;
|
|
iterator->desired_value = desired_value;
|
|
iterator->summarize = summarize;
|
|
|
|
/* Then create the iterator that returns each BDD assignment. */
|
|
DEBUG("Iterating set");
|
|
iterator->bdd_iterator = ipset_node_iterate(set->cache, set->set_bdd);
|
|
|
|
/* Then drill down from the current BDD assignment, creating an
|
|
* expanded assignment for it. */
|
|
process_assignment(iterator);
|
|
return iterator;
|
|
}
|
|
|
|
|
|
struct ipset_iterator *
|
|
ipset_iterate(struct ip_set *set, bool desired_value)
|
|
{
|
|
return create_iterator(set, desired_value, false);
|
|
}
|
|
|
|
|
|
struct ipset_iterator *
|
|
ipset_iterate_networks(struct ip_set *set, bool desired_value)
|
|
{
|
|
return create_iterator(set, desired_value, true);
|
|
}
|
|
|
|
|
|
void
|
|
ipset_iterator_free(struct ipset_iterator *iterator)
|
|
{
|
|
if (iterator->bdd_iterator != NULL) {
|
|
ipset_bdd_iterator_free(iterator->bdd_iterator);
|
|
}
|
|
if (iterator->assignment_iterator != NULL) {
|
|
ipset_expanded_assignment_free(iterator->assignment_iterator);
|
|
}
|
|
free(iterator);
|
|
}
|
|
|
|
|
|
void
|
|
ipset_iterator_advance(struct ipset_iterator *iterator)
|
|
{
|
|
/* If we're already at the end of the iterator, don't do anything. */
|
|
|
|
if (CORK_UNLIKELY(iterator->finished)) {
|
|
return;
|
|
}
|
|
|
|
/* Otherwise, advance the expanded assignment iterator to the next
|
|
* assignment, and then drill down into it. */
|
|
|
|
DEBUG("Advancing set iterator");
|
|
ipset_expanded_assignment_advance(iterator->assignment_iterator);
|
|
process_expanded_assignment(iterator);
|
|
}
|