appneta/tcpreplay

View on GitHub
src/common/cidr.c

Summary

Maintainability
Test Coverage
/* $Id$ */

/*
 *   Copyright (c) 2001-2010 Aaron Turner <aturner at synfin dot net>
 *   Copyright (c) 2013-2024 Fred Klassen <tcpreplay at appneta dot com> - AppNeta
 *
 *   The Tcpreplay Suite of tools is free software: you can redistribute it
 *   and/or modify it under the terms of the GNU General Public License as
 *   published by the Free Software Foundation, either version 3 of the
 *   License, or with the authors permission any later version.
 *
 *   The Tcpreplay Suite is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with the Tcpreplay Suite.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "defines.h"
#include "config.h"
#include "common.h"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>

static tcpr_cidr_t *cidr2cidr(char *);

/**
 * prints to the given fd all the entries in mycidr
 */
void
print_cidr(tcpr_cidr_t *mycidr)
{
    tcpr_cidr_t *cidr_ptr;

    fprintf(stderr, "Cidr List: ");

    cidr_ptr = mycidr;
    while (cidr_ptr != NULL) {
        /* print it */
        fprintf(stderr, "%s/%d, ", get_cidr2name(cidr_ptr, RESOLVE), cidr_ptr->masklen);

        /* go to the next */
        if (cidr_ptr->next != NULL) {
            cidr_ptr = cidr_ptr->next;
        } else {
            break;
        }
    }
    fprintf(stderr, "\n");
}

/**
 * deletes all entries in a cidr and destroys the datastructure
 */
void
destroy_cidr(tcpr_cidr_t *cidr)
{
    if (cidr != NULL) {
        if (cidr->next != NULL)
            destroy_cidr(cidr->next);

        safe_free(cidr);
    }
}

/**
 * adds a new tcpr_cidr_t entry to cidrdata
 */
void
add_cidr(tcpr_cidr_t **cidrdata, tcpr_cidr_t **newcidr)
{
    tcpr_cidr_t *cidr_ptr;
    dbg(1, "Running new_cidr()");

    if (*cidrdata == NULL) {
        *cidrdata = *newcidr;
    } else {
        cidr_ptr = *cidrdata;

        while (cidr_ptr->next != NULL)
            cidr_ptr = cidr_ptr->next;

        cidr_ptr->next = *newcidr;
    }
}

/**
 * Mallocs and sets to sane defaults a tcpr_cidr_t structure
 */

tcpr_cidr_t *
new_cidr(void)
{
    tcpr_cidr_t *newcidr;

    newcidr = (tcpr_cidr_t *)safe_malloc(sizeof(tcpr_cidr_t));

    memset(newcidr, '\0', sizeof(tcpr_cidr_t));
    newcidr->masklen = 99;
    newcidr->next = NULL;

    return (newcidr);
}

/**
 * Creates a new tcpr_cidrmap_t structure.  Malloc's memory
 */
tcpr_cidrmap_t *
new_cidr_map(void)
{
    tcpr_cidrmap_t *new;

    new = (tcpr_cidrmap_t *)safe_malloc(sizeof(tcpr_cidrmap_t));

    memset(new, '\0', sizeof(tcpr_cidrmap_t));
    new->next = NULL;

    return (new);
}

/**
 * Converts a single cidr (string) in the form of x.x.x.x/y into a
 * tcpr_cidr_t structure.  Will malloc the tcpr_cidr_t structure.
 */
static tcpr_cidr_t *
cidr2cidr(char *cidr)
{
    int count;
    unsigned int octets[4]; /* used in sscanf */
    tcpr_cidr_t *newcidr;
    char networkip[16], tempoctet[4];
    int family;
    char *p;

    assert(cidr);

    newcidr = new_cidr();

    for (p = cidr; *p; ++p) {
        if (*p == '#') {
            *p = ':';
        } else if (*p == ']') {
            *p = 0;
            break;
        }
    }

    /*
     * scan it, and make sure it scanned correctly, also copy over the
     * masklen
     */
    count = sscanf(cidr, "%u.%u.%u.%u/%d", &octets[0], &octets[1], &octets[2], &octets[3], &newcidr->masklen);

    if (count == 4) {
        newcidr->masklen = 32;
        family = AF_INET;
    } else if (count == 5) {
        family = AF_INET;
    } else {
        p = strstr(cidr, "/");
        if (p) {
            *p = 0;
            ++p;
            sscanf(p, "%d", &newcidr->masklen);
        } else {
            newcidr->masklen = 128;
        }

        if (newcidr->masklen < 0 || newcidr->masklen > 128)
            goto error;

        /* skip past the opening [ */
        if (*cidr == '[')
            cidr++;

        if (get_name2addr6(cidr, RESOLVE, &newcidr->u.network6) > 0) {
            family = AF_INET6;
        } else {
            goto error;
        }
    }

    if (family == AF_INET) {
        /* masklen better be 0 =< masklen <= 32 */
        if (newcidr->masklen > 32)
            goto error;

        /* copy in the ip address */
        memset(networkip, '\0', 16);
        for (count = 0; count < 4; count++) {
            if (octets[count] > 255)
                goto error;

            snprintf(tempoctet, sizeof(octets[count]), "%u", octets[count]);
            strcat(networkip, tempoctet);
            /* we don't want a '.' at the end of the last octet */
            if (count < 3)
                strcat(networkip, ".");
        }

        /* copy over the network address and return */
#ifdef HAVE_INET_ATON
        inet_aton(networkip, (struct in_addr *)&newcidr->u.network);
#elif HAVE_INET_ADDR
        newcidr->network = inet_addr(networkip);
#endif
    } else {
        /* Everything's done */
    }

    newcidr->family = family;
    return (newcidr);

    /* we only get here on error parsing input */
error:
    errx(-1, "%s: %s", "Unable to parse as a valid CIDR", cidr);
}

static void
mask_cidr6(char **cidrin, const char *delim)
{
    if (**cidrin == '[' && *delim == ':') {
        char *p;
        ++*cidrin;
        /* make strtok happy */
        for (p = *cidrin; *p && *p != ']'; ++p) {
            if (*p == ':')
                *p = '#';
        }
    }
}

/**
 * parses a list of tcpr_cidr_t's input from the user which should be in the form
 * of x.x.x.x/y,x.x.x.x/y...
 * returns 1 for success, or fails to return on failure (exit 1)
 * since we use strtok to process cidr, it gets zeroed out.
 */
int
parse_cidr(tcpr_cidr_t **cidrdata, char *cidrin, char *delim)
{
    tcpr_cidr_t *cidr_ptr; /* ptr to current cidr record */
    char *network;
    char *token = NULL;

    if (cidrin == NULL) {
        errx(-1, "%s", "Unable to parse empty CIDR");
    }

    mask_cidr6(&cidrin, delim);

    /* first iteration of input using strtok */
    network = strtok_r(cidrin, delim, &token);
    if (network == NULL)
        return 0;

    *cidrdata = cidr2cidr(network);
    cidr_ptr = *cidrdata;

    /* do the same with the rest of the input */
    while (1) {
        if (token)
            mask_cidr6(&token, delim);

        network = strtok_r(NULL, delim, &token);
        /* if that was the last CIDR, then kickout */
        if (network == NULL)
            break;

        /* next record */
        cidr_ptr->next = cidr2cidr(network);
        cidr_ptr = cidr_ptr->next;
    }

    return 1;
}

/**
 * parses a pair of IP addresses: <IP1>:<IP2> and processes it like:
 * -N 0.0.0.0/0:<IP1> -N 0.0.0.0/0:<IP2>
 * returns 1 for success or returns 0 on failure
 * since we use strtok to process optarg, it gets zeroed out
 */
int
parse_endpoints(tcpr_cidrmap_t **cidrmap1, tcpr_cidrmap_t **cidrmap2, const char *optarg)
{
#define NEWMAP_LEN (INET6_ADDRSTRLEN * 2)
    char *map = NULL, newmap[NEWMAP_LEN];
    char *token = NULL;
    char *string;
    char *p;
    int res = 0;

    string = safe_strdup(optarg);

    if (*string == '[') {
        /* ipv6 mode */
        memset(newmap, '\0', NEWMAP_LEN);
        p = strstr(string, "]:[");
        if (!p)
            goto done;

        *p = 0;
        strlcpy(newmap, "[::/0]:", NEWMAP_LEN);
        strlcat(newmap, string, NEWMAP_LEN);
        strlcat(newmap, "]", NEWMAP_LEN);

        if (!parse_cidr_map(cidrmap1, newmap))
            goto done;

        /* do again with the second IP */
        memset(newmap, '\0', NEWMAP_LEN);
        strlcpy(newmap, "[::/0]:", NEWMAP_LEN);
        strlcat(newmap, p + 2, NEWMAP_LEN);

        if (!parse_cidr_map(cidrmap2, newmap))
            goto done;

    } else {
        /* ipv4 mode */
        memset(newmap, '\0', NEWMAP_LEN);
        map = strtok_r(string, ":", &token);
        if (map == NULL)
            goto done;

        strlcpy(newmap, "0.0.0.0/0:", NEWMAP_LEN);
        strlcat(newmap, map, NEWMAP_LEN);
        if (!parse_cidr_map(cidrmap1, newmap))
            goto done;

        /* do again with the second IP */
        memset(newmap, '\0', NEWMAP_LEN);
        map = strtok_r(NULL, ":", &token);
        if (map == NULL)
            goto done;

        strlcpy(newmap, "0.0.0.0/0:", NEWMAP_LEN);
        strlcat(newmap, map, NEWMAP_LEN);
        if (!parse_cidr_map(cidrmap2, newmap))
            goto done;
    }

    /* success */
    res = 1;

done:
    safe_free(string);
    return res;
}

/**
 * parses a list of tcpr_cidrmap_t's input from the user which should be in the form
 * of x.x.x.x/y:x.x.x.x/y,...
 * IPv6 syntax: [addr/y]:[addr/y],...
 * returns 1 for success, or returns 0 on failure
 * since we use strtok to process optarg, it gets zeroed out.
 */
int
parse_cidr_map(tcpr_cidrmap_t **cidrmap, const char *optarg)
{
    tcpr_cidr_t *cidr = NULL;
    char *map;
    char *token = NULL, *string;
    tcpr_cidrmap_t *ptr;
    int res = 0;

    string = safe_strdup(optarg);

    /* first iteration */
    map = strtok_r(string, ",", &token);
    if (!parse_cidr(&cidr, map, ":"))
        goto done;

    /* must return a linked list of two */
    if (cidr->next == NULL)
        goto done;

    /* copy over */
    *cidrmap = new_cidr_map();
    ptr = *cidrmap;

    ptr->from = cidr;
    ptr->to = cidr->next;
    ptr->from->next = NULL;

    /* do the same with the reset of the input */
    while (1) {
        map = strtok_r(NULL, ",", &token);
        if (map == NULL)
            break;

        if (!parse_cidr(&cidr, map, ":"))
            goto done;

        /* must return a linked list of two */
        if (cidr->next == NULL)
            goto done;

        /* copy over */
        ptr->next = new_cidr_map();
        ptr = ptr->next;
        ptr->from = cidr;
        ptr->to = cidr->next;
        ptr->from->next = NULL;
    }

    /* success */
    res = 1;

done:
    safe_free(string);
    return res;
}

/**
 * checks to see if the ip address is in the cidr
 * returns 1 for true, 0 for false
 */
int
ip_in_cidr(const tcpr_cidr_t *mycidr, const unsigned long ip)
{
    unsigned long ipaddr, network, mask;
    int ret;
#ifdef DEBUG
    char netstr[20];
#endif

    if (mycidr->family != AF_INET)
        return 0;

    /* always return 1 if 0.0.0.0/0 */
    if (mycidr->masklen == 0 && mycidr->u.network == 0)
        return 1;

    mask = ~0; /* turn on all the bits */

    /* shift over by the correct number of bits */
    mask = mask << (32 - mycidr->masklen);

    /* apply the mask to the network and ip */
    ipaddr = ntohl(ip) & mask;

    network = htonl(mycidr->u.network) & mask;

#ifdef DEBUG
    /* copy this for debug purposes, since it's not re-entrant */
    strlcpy(netstr, get_addr2name4(mycidr->u.network, RESOLVE), 20);
#endif

    /* if they're the same, then ip is in network */
    if (network == ipaddr) {
#ifdef DEBUG
        dbgx(1, "The ip %s is inside of %s/%d", get_addr2name4(ip, RESOLVE), netstr, mycidr->masklen);
#endif
        ret = 1;
    } else {
#ifdef DEBUG
        dbgx(1, "The ip %s is not inside of %s/%d", get_addr2name4(ip, RESOLVE), netstr, mycidr->masklen);
#endif
        ret = 0;
    }
    return ret;
}

static int
ip6_addr_is_unspec(const struct tcpr_in6_addr *addr)
{
    return addr->tcpr_s6_addr32[0] == 0 && addr->tcpr_s6_addr32[1] == 0 && addr->tcpr_s6_addr32[2] == 0 &&
           addr->tcpr_s6_addr32[3] == 0;
}

int
ip6_in_cidr(const tcpr_cidr_t *mycidr, const struct tcpr_in6_addr *addr)
{
    int ret;
#ifdef DEBUG
    char netstr[INET6_ADDRSTRLEN];
#endif
    uint32_t i, j, k;

    if (mycidr->family != AF_INET6)
        return 0;

    /* always return 1 if ::/0 */
    if (mycidr->masklen == 0 && ip6_addr_is_unspec(addr))
        return 1;

    j = mycidr->masklen / 8;

    for (i = 0; i < j; i++) {
        if (addr->tcpr_s6_addr[i] != mycidr->u.network6.tcpr_s6_addr[i]) {
            ret = 0;
            goto out;
        }
    }

    if ((k = mycidr->masklen % 8) == 0) {
        ret = 1;
        goto out;
    }

    k = (uint32_t)~0 << (8 - k);
    i = addr->tcpr_s6_addr[j] & k;
    j = mycidr->u.network6.tcpr_s6_addr[j] & k;
    ret = i == j;
out:

#ifdef DEBUG
    /* copy this for debug purposes, since it's not re-entrant */
    strlcpy(netstr, get_addr2name6(&mycidr->u.network6, RESOLVE), INET6_ADDRSTRLEN);
#endif

    /* if they're the same, then ip is in network */
    if (ret) {
#ifdef DEBUG
        dbgx(1, "The ip %s is inside of %s/%d", get_addr2name6(addr, RESOLVE), netstr, mycidr->masklen);
#endif
    } else {
#ifdef DEBUG
        dbgx(1, "The ip %s is not inside of %s/%d", get_addr2name6(addr, RESOLVE), netstr, mycidr->masklen);
#endif
    }
    return ret;
}

/**
 * iterates over cidrdata to find if a given ip matches
 * returns 1 for true, 0 for false
 */

int
check_ip_cidr(tcpr_cidr_t *cidrdata, const unsigned long ip)
{
    tcpr_cidr_t *mycidr;

    /* if we have no cidrdata, of course it isn't in there
     * this actually should happen occasionally, so don't put an assert here
     */
    if (cidrdata == NULL)
        return 1;

    mycidr = cidrdata;

    /* loop through cidr */
    while (1) {
        /* if match, return 1 */
        if (ip_in_cidr(mycidr, ip)) {
            dbgx(3, "Found %s in cidr", get_addr2name4(ip, RESOLVE));
            return 1;
        }

        /* check for next record */
        if (mycidr->next != NULL) {
            mycidr = mycidr->next;
        } else {
            break;
        }
    }

    /* if we get here, no match */
    dbgx(3, "Didn't find %s in cidr", get_addr2name4(ip, RESOLVE));
    return 0;
}

int
check_ip6_cidr(tcpr_cidr_t *cidrdata, const struct tcpr_in6_addr *addr)
{
    tcpr_cidr_t *mycidr;

    /* if we have no cidrdata, of course it isn't in there
     * this actually should happen occasionally, so don't put an assert here
     */
    if (cidrdata == NULL) {
        return 1;
    }

    mycidr = cidrdata;

    /* loop through cidr */
    while (1) {
        /* if match, return 1 */
        if (ip6_in_cidr(mycidr, addr)) {
            dbgx(3, "Found %s in cidr", get_addr2name6(addr, RESOLVE));
            return 1;
        }

        /* check for next record */
        if (mycidr->next != NULL) {
            mycidr = mycidr->next;
        } else {
            break;
        }
    }

    /* if we get here, no match */
    dbgx(3, "Didn't find %s in cidr", get_addr2name6(addr, RESOLVE));
    return 0;
}