diff --git a/poc-code/access-list/Makefile.am b/poc-code/access-list/Makefile.am new file mode 100644 index 00000000..1a96a37e --- /dev/null +++ b/poc-code/access-list/Makefile.am @@ -0,0 +1,4 @@ + +test_ipcheck: test_ipcheck.c ipcheck.c + $(CC) -Wall -O0 -g ipcheck.c test_ipcheck.c -o test_ipcheck + diff --git a/poc-code/access-list/ipcheck.c b/poc-code/access-list/ipcheck.c new file mode 100644 index 00000000..b71ac5c1 --- /dev/null +++ b/poc-code/access-list/ipcheck.c @@ -0,0 +1,210 @@ +#include +#include +#include +#include +#include +#include "ipcheck.h" + +struct ip_match_entry { + ipcheck_type_t type; + ipcheck_acceptreject_t acceptreject; + struct sockaddr_storage addr1; /* Actual IP address, mask top or low IP */ + struct sockaddr_storage addr2; /* high IP address or address bitmask */ + struct ip_match_entry *next; +}; + + +/* Lists of things to match against. These are dummy structs to provide a quick list head */ +static struct ip_match_entry match_entry_head_v4; +static struct ip_match_entry match_entry_head_v6; + +/* + * IPv4 See if the address we have matches the current match entry + * + */ +static int ip_matches_v4(struct sockaddr_storage *checkip, struct ip_match_entry *match_entry) +{ + struct sockaddr_in *ip_to_check; + struct sockaddr_in *match1; + struct sockaddr_in *match2; + + ip_to_check = (struct sockaddr_in *)checkip; + match1 = (struct sockaddr_in *)&match_entry->addr1; + match2 = (struct sockaddr_in *)&match_entry->addr2; + + switch(match_entry->type) { + case IPCHECK_TYPE_ADDRESS: + if (ip_to_check->sin_addr.s_addr == match1->sin_addr.s_addr) + return 1; + break; + case IPCHECK_TYPE_MASK: + if ((ip_to_check->sin_addr.s_addr & match2->sin_addr.s_addr) == + match1->sin_addr.s_addr) + return 1; + break; + case IPCHECK_TYPE_RANGE: + if ((ntohl(ip_to_check->sin_addr.s_addr) >= ntohl(match1->sin_addr.s_addr)) && + (ntohl(ip_to_check->sin_addr.s_addr) <= ntohl(match2->sin_addr.s_addr))) + return 1; + break; + + } + return 0; +} + +/* Compare two IPv6 addresses */ +static int ip6addr_cmp(struct in6_addr *a, struct in6_addr *b) +{ + uint64_t a_high, a_low; + uint64_t b_high, b_low; + + /* Not sure why '&' doesn't work below, so I used '+' instead which is effectively + the same thing because the bottom 32bits are always zero and the value unsigned */ + a_high = ((uint64_t)htonl(a->s6_addr32[0]) << 32) + (uint64_t)htonl(a->s6_addr32[1]); + a_low = ((uint64_t)htonl(a->s6_addr32[2]) << 32) + (uint64_t)htonl(a->s6_addr32[3]); + + b_high = ((uint64_t)htonl(b->s6_addr32[0]) << 32) + (uint64_t)htonl(b->s6_addr32[1]); + b_low = ((uint64_t)htonl(b->s6_addr32[2]) << 32) + (uint64_t)htonl(b->s6_addr32[3]); + + if (a_high > b_high) + return 1; + if (a_high < b_high) + return -1; + + if (a_low > b_low) + return 1; + if (a_low < b_low) + return -1; + + return 0; +} + +/* + * IPv6 See if the address we have matches the current match entry + * + */ +static int ip_matches_v6(struct sockaddr_storage *checkip, struct ip_match_entry *match_entry) +{ + struct sockaddr_in6 *ip_to_check; + struct sockaddr_in6 *match1; + struct sockaddr_in6 *match2; + int i; + + ip_to_check = (struct sockaddr_in6 *)checkip; + match1 = (struct sockaddr_in6 *)&match_entry->addr1; + match2 = (struct sockaddr_in6 *)&match_entry->addr2; + + switch(match_entry->type) { + case IPCHECK_TYPE_ADDRESS: + if (!memcmp(ip_to_check->sin6_addr.s6_addr32, match1->sin6_addr.s6_addr32, sizeof(struct in6_addr))) + return 1; + break; + + case IPCHECK_TYPE_MASK: + /* + * Note that this little loop will quit early if there is a non-match so the + * comparison might look backwards compared to the IPv4 one + */ + for (i=sizeof(struct in6_addr)/4-1; i>=0; i--) { + if ((ip_to_check->sin6_addr.s6_addr32[i] & match2->sin6_addr.s6_addr32[i]) != + match1->sin6_addr.s6_addr32[i]) + return 0; + } + return 1; + case IPCHECK_TYPE_RANGE: + if ((ip6addr_cmp(&ip_to_check->sin6_addr, &match1->sin6_addr) >= 0) && + (ip6addr_cmp(&ip_to_check->sin6_addr, &match2->sin6_addr) <= 0)) + return 1; + break; + } + return 0; +} + + +/* + * YOU ARE HERE + */ +int ipcheck_validate(struct sockaddr_storage *checkip) +{ + struct ip_match_entry *match_entry; + int (*match_fn)(struct sockaddr_storage *checkip, struct ip_match_entry *match_entry); + + if (checkip->ss_family == AF_INET){ + match_entry = match_entry_head_v4.next; + match_fn = ip_matches_v4; + } else { + match_entry = match_entry_head_v6.next; + match_fn = ip_matches_v6; + } + while (match_entry) { + if (match_fn(checkip, match_entry)) { + if (match_entry->acceptreject == IPCHECK_ACCEPT) + return 1; + else + return 0; + } + match_entry = match_entry->next; + } + return 0; /* Default reject */ +} + +/* + * Routines to manuipulate the lists + */ + +void ipcheck_clear(void) +{ + struct ip_match_entry *match_entry; + struct ip_match_entry *next_match_entry; + + match_entry = match_entry_head_v4.next; + while (match_entry) { + next_match_entry = match_entry->next; + free(match_entry); + match_entry = next_match_entry; + } + + match_entry = match_entry_head_v6.next; + while (match_entry) { + next_match_entry = match_entry->next; + free(match_entry); + match_entry = next_match_entry; + } +} + +int ipcheck_addip(struct sockaddr_storage *ip1, struct sockaddr_storage *ip2, + ipcheck_type_t type, ipcheck_acceptreject_t acceptreject) +{ + struct ip_match_entry *match_entry; + struct ip_match_entry *new_match_entry; + + if (type == IPCHECK_TYPE_RANGE && + (ip1->ss_family != ip2->ss_family)) + return -1; + + if (ip1->ss_family == AF_INET){ + match_entry = &match_entry_head_v4; + } else { + match_entry = &match_entry_head_v6; + } + + + new_match_entry = malloc(sizeof(struct ip_match_entry)); + if (!new_match_entry) + return -1; + + memcpy(&new_match_entry->addr1, ip1, sizeof(struct sockaddr_storage)); + memcpy(&new_match_entry->addr2, ip2, sizeof(struct sockaddr_storage)); + new_match_entry->type = type; + new_match_entry->acceptreject = acceptreject; + new_match_entry->next = NULL; + + /* Find the end of the list */ + /* is this OK, or should we use a doubly-linked list or bulk-load API call? */ + while (match_entry->next) { + match_entry = match_entry->next; + } + match_entry->next = new_match_entry; + + return 0; +} diff --git a/poc-code/access-list/ipcheck.h b/poc-code/access-list/ipcheck.h new file mode 100644 index 00000000..c7b3fee6 --- /dev/null +++ b/poc-code/access-list/ipcheck.h @@ -0,0 +1,10 @@ + + +typedef enum {IPCHECK_TYPE_ADDRESS, IPCHECK_TYPE_MASK, IPCHECK_TYPE_RANGE} ipcheck_type_t; +typedef enum {IPCHECK_ACCEPT, IPCHECK_REJECT} ipcheck_acceptreject_t; + +int ipcheck_validate(struct sockaddr_storage *checkip); + +void ipcheck_clear(void); +int ipcheck_addip(struct sockaddr_storage *ip1, struct sockaddr_storage *ip2, + ipcheck_type_t type, ipcheck_acceptreject_t acceptreject); diff --git a/poc-code/access-list/test_ipcheck.c b/poc-code/access-list/test_ipcheck.c new file mode 100644 index 00000000..9c814748 --- /dev/null +++ b/poc-code/access-list/test_ipcheck.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "ipcheck.h" + +/* This is a test program .. remember! */ +#define BUFLEN 1024 + +static int get_ipaddress(char *buf, struct sockaddr_storage *addr) +{ + struct addrinfo *info; + struct addrinfo hints; + int res; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + + res = getaddrinfo(buf, NULL, &hints, &info); + if (!res) { + memcpy(addr, info->ai_addr, info->ai_addrlen); + free(info); + } + return res; +} + +static int read_address(char *buf, struct sockaddr_storage *addr) +{ + return get_ipaddress(buf, addr); +} + +static int read_mask(char *buf, struct sockaddr_storage *addr, struct sockaddr_storage *addr2) +{ + char tmpbuf[BUFLEN]; + char *slash; + int ret; + + slash = strchr(buf, '/'); + if (!slash) + return 1; + + strncpy(tmpbuf, buf, slash-buf); + tmpbuf[slash-buf] = '\0'; + + ret = get_ipaddress(tmpbuf, addr); + if (ret) + return ret; + + ret = get_ipaddress(slash+1, addr2); + if (ret) + return ret; + + return 0; +} + +static int read_range(char *buf, struct sockaddr_storage *addr1, struct sockaddr_storage *addr2) +{ + char tmpbuf[BUFLEN]; + char *hyphen; + int ret; + + hyphen = strchr(buf, '-'); + if (!hyphen) + return 1; + + strncpy(tmpbuf, buf, hyphen-buf); + tmpbuf[hyphen-buf] = '\0'; + + ret = get_ipaddress(tmpbuf, addr1); + if (ret) + return ret; + + ret = get_ipaddress(hyphen+1, addr2); + if (ret) + return ret; + + return 0; +} + + +static int load_file() +{ + FILE *filterfile; + char filebuf[BUFLEN]; + int line = 0; + int ret; + ipcheck_type_t type; + ipcheck_acceptreject_t acceptreject; + struct sockaddr_storage addr1; + struct sockaddr_storage addr2; + + ipcheck_clear(); + + filterfile = fopen("test_ipcheck.txt", "r"); + if (!filterfile) { + fprintf(stderr, "Cannot open test_ipcheck.txt\n"); + return 1; + } + + while (fgets(filebuf, sizeof(filebuf), filterfile)) { + filebuf[strlen(filebuf)-1] = '\0'; /* remove trailing LF */ + line++; + + /* + * First char is A (accept) or R (Reject) + */ + switch(filebuf[0] & 0x5F) { + case 'A': + acceptreject = IPCHECK_ACCEPT; + break; + case 'R': + acceptreject = IPCHECK_REJECT; + break; + default: + fprintf(stderr, "Unknown record type on line %d: %s\n", line, filebuf); + goto next_record; + } + + /* + * Second char is the filter type: + * A Address + * M Mask + * R Range + */ + switch(filebuf[1] & 0x5F) { + case 'A': + type = IPCHECK_TYPE_ADDRESS; + ret = read_address(filebuf+2, &addr1); + break; + case 'M': + type = IPCHECK_TYPE_MASK; + ret = read_mask(filebuf+2, &addr1, &addr2); + break; + case 'R': + type = IPCHECK_TYPE_RANGE; + ret = read_range(filebuf+2, &addr1, &addr2); + break; + default: + fprintf(stderr, "Unknown filter type on line %d: %s\n", line, filebuf); + goto next_record; + break; + } + if (ret) { + fprintf(stderr, "Failed to parse address on line %d: %s\n", line, filebuf); + } + else { + ipcheck_addip(&addr1, &addr2, type, acceptreject); + } + next_record: {} /* empty statement to mollify the compiler */ + } + fclose(filterfile); + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct sockaddr_storage saddr; + int ret; + int i; + + if (load_file()) + return 1; + + for (i=1; i