Search
j0ke.net Open Build Service
>
Projects
>
ha
:
firewall
>
snort292
> snort-2.9.2.2_ipv6_preproc.patch.old
Sign Up
|
Log In
Username
Password
Cancel
Overview
Repositories
Revisions
Requests
Users
Advanced
Attributes
Meta
File snort-2.9.2.2_ipv6_preproc.patch.old of Package snort292
--- src/dynamic-preprocessors/Makefile.in.orig 2012-05-12 20:26:37.760390797 +0200 +++ src/dynamic-preprocessors/Makefile.in 2012-05-12 20:26:45.329269245 +0200 @@ -128,7 +128,7 @@ ETAGS = etags CTAGS = ctags DIST_SUBDIRS = . libs ftptelnet pop imap smtp ssh dns ssl dcerpc2 sdf \ - sip reputation gtp modbus dnp3 rzb_saac + sip reputation gtp modbus dnp3 ipv6 rzb_saac DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) am__relativize = \ dir0=`pwd`; \ --- src/dynamic-preprocessors/Makefile.am.orig 2012-05-12 20:26:11.234263661 +0200 +++ src/dynamic-preprocessors/Makefile.am 2012-05-12 20:26:25.801269762 +0200 @@ -488,7 +488,7 @@ endif if HAVE_DYNAMIC_PLUGINS -SUBDIRS = . libs ftptelnet pop imap smtp ssh dns ssl dcerpc2 sdf sip reputation gtp modbus dnp3 $(RZB_SAAC_DIR) +SUBDIRS = . libs ftptelnet pop imap smtp ssh dns ssl dcerpc2 sdf sip reputation gtp modbus dnp3 ipv6 $(RZB_SAAC_DIR) endif clean-local: diff -uNr src/dynamic-preprocessors/ipv6//Makefile.am src/dynamic-preprocessors/ipv6//Makefile.am --- src/dynamic-preprocessors/ipv6//Makefile.am 1970-01-01 01:00:00.000000000 +0100 +++ src/dynamic-preprocessors/ipv6//Makefile.am 2012-05-12 20:25:08.669267964 +0200 @@ -0,0 +1,33 @@ +## $Id +AUTOMAKE_OPTIONS=foreign no-dependencies + +INCLUDES = -I../include -I${srcdir}/../libs + +libdir = ${exec_prefix}/lib/snort_dynamicpreprocessor + +lib_LTLIBRARIES = libsf_ipv6_preproc.la + +libsf_ipv6_preproc_la_LDFLAGS = -shared -export-dynamic -module @XCCFLAGS@ +if SO_WITH_STATIC_LIB +libsf_ipv6_preproc_la_LIBADD = ../libsf_dynamic_preproc.la +else +nodist_libsf_ipv6_preproc_la_SOURCES = \ +../include/sf_dynamic_preproc_lib.c \ +../include/sfPolicyUserData.c +endif + +libsf_ipv6_preproc_la_SOURCES = \ +sf_ip.c \ +spp_ipv6.c \ +spp_ipv6_constants.h \ +spp_ipv6_data_structs.c \ +spp_ipv6_data_structs.h \ +spp_ipv6.h \ +spp_ipv6_parse.c \ +spp_ipv6_ruleopt.c + +EXTRA_DIST = + +all-local: $(LTLIBRARIES) + $(MAKE) DESTDIR=`pwd`/../build install-libLTLIBRARIES + diff -uNr src/dynamic-preprocessors/ipv6//sf_ip.c src/dynamic-preprocessors/ipv6//sf_ip.c --- src/dynamic-preprocessors/ipv6//sf_ip.c 1970-01-01 01:00:00.000000000 +0100 +++ src/dynamic-preprocessors/ipv6//sf_ip.c 2012-05-12 20:19:37.317266955 +0200 @@ -0,0 +1,589 @@ +/* +** Copyright (C) 1998-2012 Sourcefire, Inc. +** Adam Keeton +** Kevin Liu <kliu@sourcefire.com> +** +** $Id$ +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License Version 2 as +** published by the Free Software Foundation. You may not use, modify or +** distribute this program under any other version of the GNU General +** Public License. +** +** This program 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 this program; if not, write to the Free Software +** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/* + * Adam Keeton + * sf_ip.c + * 11/17/06 + * + * Library for managing IP addresses of either v6 or v4 families. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <math.h> /* For ceil */ +#include "sf_ip.h" + +/* For inet_pton */ +#ifndef WIN32 +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#endif /* WIN32 */ + +#if 0 +/* Support function .. but could see some external uses */ +static inline int sfip_length(sfip_t *ip) { + ARG_CHECK1(ip, 0); + + if(sfip_family(ip) == AF_INET) return 4; + return 16; +} +#endif + +/* Support function */ +// note that an ip6 address may have a trailing dotted quad form +// but that it always has at least 2 ':'s; furthermore there is +// no valid ip4 format (including mask) with 2 ':'s +// we don't have to figure out if the format is entirely legal +// we just have to be able to tell correct formats apart +static inline int sfip_str_to_fam(const char *str) { + const char* s; + ARG_CHECK1(str, 0); + s = strchr(str, (int)':'); + if ( s && strchr(s+1, (int)':') ) return AF_INET6; + if ( strchr(str, (int)'.') ) return AF_INET; + return AF_UNSPEC; +} + +/* Place-holder allocation incase we want to do something more indepth later */ +static inline sfip_t *_sfip_alloc() { + /* Note: using calloc here instead of SnortAlloc since the dynamic libs + * can't presently resolve SnortAlloc */ + return (sfip_t*)calloc(sizeof(sfip_t), 1); +} + +/* Masks off 'val' bits from the IP contained within 'ip' */ +static inline int sfip_cidr_mask(sfip_t *ip, int val) { + int i; + unsigned int mask = 0; + unsigned int *p; + int index = (int)ceil(val / 32.0) - 1; + + ARG_CHECK1(ip, SFIP_ARG_ERR); + + p = ip->ip32; + + if( val < 0 || + ((sfip_family(ip) == AF_INET6) && val > 128) || + ((sfip_family(ip) == AF_INET) && val > 32) ) { + return SFIP_ARG_ERR; + } + + /* Build the netmask by converting "val" into + * the corresponding number of bits that are set */ + for(i = 0; i < 32- (val - (index * 32)); i++) + mask = (mask<<1) + 1; + + p[index] = htonl((ntohl(p[index]) & ~mask)); + + index++; + + /* 0 off the rest of the IP */ + for( ; index<4; index++) p[index] = 0; + + return SFIP_SUCCESS; +} + +/* Allocate IP address from a character array describing the IP */ +sfip_t *sfip_alloc(const char *ip, SFIP_RET *status) { + SFIP_RET tmp; + sfip_t *ret; + + if(!ip) { + if(status) + *status = SFIP_ARG_ERR; + return NULL; + } + + if((ret = _sfip_alloc()) == NULL) { + if(status) + *status = SFIP_ALLOC_ERR; + return NULL; + } + + if( (tmp = sfip_pton(ip, ret)) != SFIP_SUCCESS) { + if(status) + *status = tmp; + + sfip_free(ret); + return NULL; + } + + if(status) + *status = SFIP_SUCCESS; + + return ret; +} + +/* Allocate IP address from an array of 8 byte integers */ +sfip_t *sfip_alloc_raw(void *ip, int family, SFIP_RET *status) { + sfip_t *ret; + + if(!ip) { + if(status) + *status = SFIP_ARG_ERR; + return NULL; + } + + if((ret = _sfip_alloc()) == NULL) { + if(status) + *status = SFIP_ALLOC_ERR; + return NULL; + } + + ret->bits = (family==AF_INET?32:128); + ret->family = family; + /* XXX Replace with appropriate "high speed" copy */ + memcpy(ret->ip8, ip, ret->bits/8); + + if(status) + *status = SFIP_SUCCESS; + + return ret; +} + +/* Support function for _netmask_str_to_bit_count */ +static inline int _count_bits(unsigned int val) { + unsigned int count; + + for (count = 0; val; count++) { + val &= val - 1; + } + + return count; +} + +/* Support function for sfip_pton. Used for converting a netmask string + * into a number of bits to mask off */ +static inline int _netmask_str_to_bit_count(char *mask, int family) { + uint32_t buf[4]; + int bits, i, nBits, nBytes; + uint8_t* bytes = (uint8_t*)buf; + + /* XXX + * Mask not validated. + * Only sfip_pton should be using this function, and using it safely. + * XXX */ + + if(inet_pton(family, mask, buf) < 1) + return -1; + + bits = _count_bits(buf[0]); + + if(family == AF_INET6) { + bits += _count_bits(buf[1]); + bits += _count_bits(buf[2]); + bits += _count_bits(buf[3]); + nBytes = 16; + } else { + nBytes = 4; + } + + // now make sure that only the most significant bits are set + nBits = bits; + for ( i = 0; i < nBytes; i++ ) { + if ( nBits >= 8 ) { + if ( bytes[i] != 0xff ) return -1; + nBits -= 8; + + } else if ( nBits == 0 ) { + if ( bytes[i] != 0x00 ) return -1; + + } else { + if ( bytes[i] != ((0xff00 >> nBits) & 0xff) ) return -1; + nBits = 0; + } + } + return bits; +} + +/* Parses "src" and stores results in "dst" */ +SFIP_RET sfip_pton(const char *src, sfip_t *dst) { + char *mask; + char *sfip_buf; + char *ip; + int bits; + + if(!dst || !src) + return SFIP_ARG_ERR; + + if((sfip_buf = strdup(src)) == NULL) + return SFIP_ALLOC_ERR; + + ip = sfip_buf; + dst->family = sfip_str_to_fam(src); + + /* skip whitespace or opening bracket */ + while(isspace((int)*ip) || (*ip == '[')) ip++; + + /* check for and extract a mask in CIDR form */ + if( (mask = strchr(ip, (int)'/')) != NULL ) { + /* NULL out this character so inet_pton will see the + * correct ending to the IP string */ + char* end = mask++; + while ( (end > ip) && isspace((int)end[-1]) ) end--; + *end = 0; + + while(isspace((int)*mask)) mask++; + + /* verify a leading digit */ + if(((dst->family == AF_INET6) && !isxdigit((int)*mask)) || + ((dst->family == AF_INET) && !isdigit((int)*mask))) { + free(sfip_buf); + return SFIP_CIDR_ERR; + } + + /* Check if there's a netmask here instead of the number of bits */ + if(strchr(mask, (int)'.') || strchr(mask, (int)':')) + bits = _netmask_str_to_bit_count(mask, sfip_str_to_fam(mask)); + else + bits = atoi(mask); + } + else if( + /* If this is IPv4, ia ':' may used specified to indicate a netmask */ + ((dst->family == AF_INET) && (mask = strchr(ip, (int)':')) != NULL) || + + /* We've already skipped the leading whitespace, if there is more + * whitespace, then there's probably a netmask specified after it. */ + (mask = strchr(ip, (int)' ')) != NULL + ) { + char* end = mask++; + while ( (end > ip) && isspace((int)end[-1]) ) end--; + *end = 0; /* Now the IP will end at this point */ + + /* skip whitespace */ + while(isspace((int)*mask)) mask++; + + /* Make sure we're either looking at a valid digit, or a leading + * colon, such as can be the case with IPv6 */ + if(((dst->family == AF_INET) && isdigit((int)*mask)) || + ((dst->family == AF_INET6) && (isxdigit((int)*mask) || *mask == ':'))) { + bits = _netmask_str_to_bit_count(mask, sfip_str_to_fam(mask)); + } + /* No netmask */ + else { + if(dst->family == AF_INET) bits = 32; + else bits = 128; + } + } + /* No netmask */ + else { + if(dst->family == AF_INET) bits = 32; + else bits = 128; + } + + if(inet_pton(dst->family, ip, dst->ip8) < 1) { + free(sfip_buf); + return SFIP_INET_PARSE_ERR; + } + + /* Store mask */ + dst->bits = bits; + + /* Apply mask */ + if(sfip_cidr_mask(dst, bits) != SFIP_SUCCESS) { + free(sfip_buf); + return SFIP_INVALID_MASK; + } + + free(sfip_buf); + return SFIP_SUCCESS; +} + +/* Sets existing IP, "dst", to be source IP, "src" */ +SFIP_RET sfip_set_raw(sfip_t *dst, void *src, int family) { + + ARG_CHECK3(dst, src, dst->ip32, SFIP_ARG_ERR); + + dst->family = family; + + if(family == AF_INET) { + dst->ip32[0] = *(uint32_t*)src; + memset(&dst->ip32[1], 0, 12); + dst->bits = 32; + } else if(family == AF_INET6) { + memcpy(dst->ip8, src, 16); + dst->bits = 128; + } else { + return SFIP_ARG_ERR; + } + + return SFIP_SUCCESS; +} + +/* Sets existing IP, "dst", to be source IP, "src" */ +SFIP_RET sfip_set_ip(sfip_t *dst, const sfip_t *src) { + ARG_CHECK2(dst, src, SFIP_ARG_ERR); + + dst->family = src->family; + dst->bits = src->bits; + dst->ip32[0] = src->ip32[0]; + dst->ip32[1] = src->ip32[1]; + dst->ip32[2] = src->ip32[2]; + dst->ip32[3] = src->ip32[3]; + + return SFIP_SUCCESS; +} + +/* Obfuscates an IP + * Makes 'ip': ob | (ip & mask) */ +void sfip_obfuscate(sfip_t *ob, sfip_t *ip) { + unsigned int *ob_p, *ip_p; + int index, i; + unsigned int mask = 0; + + if(!ob || !ip) + return; + + ob_p = ob->ip32; + ip_p = ip->ip32; + + /* Build the netmask by converting "val" into + * the corresponding number of bits that are set */ + index = (int)ceil(ob->bits / 32.0) - 1; + + for(i = 0; i < 32- (ob->bits - (index * 32)); i++) + mask = (mask<<1) + 1; + + /* Note: The old-Snort obfuscation code uses !mask for masking. + * hence, this code uses the same algorithm as sfip_cidr_mask + * except the mask below is not negated. */ + ip_p[index] = htonl((ntohl(ip_p[index]) & mask)); + + /* 0 off the start of the IP */ + while ( index > 0 ) ip_p[--index] = 0; + + /* OR remaining pieces */ + ip_p[0] |= ob_p[0]; + ip_p[1] |= ob_p[1]; + ip_p[2] |= ob_p[2]; + ip_p[3] |= ob_p[3]; +} + + +/* Check if ip is contained within the network specified by net */ +/* Returns SFIP_EQUAL if so. + * XXX sfip_contains assumes that "ip" is + * not less-specific than "net" XXX +*/ +SFIP_RET sfip_contains(sfip_t *net, sfip_t *ip) { + unsigned int bits, mask, temp, i; + int net_fam, ip_fam; + unsigned int *p1, *p2; + + /* SFIP_CONTAINS is returned here due to how IpAddrSetContains + * handles zero'ed IPs" */ + ARG_CHECK2(net, ip, SFIP_CONTAINS); + + bits = sfip_bits(net); + net_fam = sfip_family(net); + ip_fam = sfip_family(ip); + + /* If the families are mismatched, check if we're really comparing + * an IPv4 with a mapped IPv4 (in IPv6) address. */ + if(net_fam != ip_fam) { + if((net_fam != AF_INET) || !sfip_ismapped(ip)) + return SFIP_ARG_ERR; + + /* Both are really IPv4. Only compare last 4 bytes of 'ip'*/ + p1 = net->ip32; + p2 = &ip->ip32[3]; + + /* Mask off bits */ + bits = 32 - bits; + temp = (ntohl(*p2) >> bits) << bits; + + if(ntohl(*p1) == temp) return SFIP_CONTAINS; + + return SFIP_NOT_CONTAINS; + } + + p1 = net->ip32; + p2 = ip->ip32; + + /* Iterate over each 32 bit segment */ + for(i=0; i < bits/32 && i < 3; i++, p1++, p2++) { + if(*p1 != *p2) + return SFIP_NOT_CONTAINS; + } + + mask = 32 - (bits - 32*i); + if ( mask == 32 ) return SFIP_CONTAINS; + + /* At this point, there are some number of remaining bits to check. + * Mask the bits we don't care about off of "ip" so we can compare + * the ints directly */ + temp = ntohl(*p2); + temp = (temp >> mask) << mask; + + /* If p1 was setup correctly through this library, there is no need to + * mask off any bits of its own. */ + if(ntohl(*p1) == temp) + return SFIP_CONTAINS; + + return SFIP_NOT_CONTAINS; + +} + +void sfip_raw_ntop(int family, const void *ip_raw, char *buf, int bufsize) +{ + if(!ip_raw || !buf || + (family != AF_INET && family != AF_INET6) || + /* Make sure if it's IPv6 that the buf is large enough. */ + /* Need atleast a max of 8 fields of 4 bytes plus 7 for colons in + * between. Need 1 more byte for null. */ + (family == AF_INET6 && bufsize < INET6_ADDRSTRLEN) || + /* Make sure if it's IPv4 that the buf is large enough. */ + /* 4 fields of 3 numbers, plus 3 dots and a null byte */ + (family == AF_INET && bufsize < INET_ADDRSTRLEN) ) + { + if(buf && bufsize > 0) buf[0] = 0; + return; + } + +#if defined(HAVE_INET_NTOP) && !defined(REG_TEST) + if (!inet_ntop(family, ip_raw, buf, bufsize)) + snprintf(buf, bufsize, "ERROR"); +#else + /* 4 fields of at most 3 characters each */ + if(family == AF_INET) { + int i; + uint8_t *p = (uint8_t*)ip_raw; + + for(i=0; p < ((uint8_t*)ip_raw) + 4; p++) { + i += sprintf(&buf[i], "%d", *p); + + /* If this is the last iteration, this could technically cause one + * extra byte to be written past the end. */ + if(i < bufsize && ((p + 1) < ((uint8_t*)ip_raw+4))) + buf[i] = '.'; + + i++; + } + + /* Check if this is really just an IPv4 address represented as 6, + * in compatible format */ +#if 0 + } + else if(!field[0] && !field[1] && !field[2]) { + unsigned char *p = (unsigned char *)(&ip->ip[12]); + + for(i=0; p < &ip->ip[16]; p++) + i += sprintf(&buf[i], "%d.", *p); +#endif + } + else { + int i; + uint16_t *p = (uint16_t*)ip_raw; + + for(i=0; p < ((uint16_t*)ip_raw) + 8; p++) { + i += sprintf(&buf[i], "%04x", ntohs(*p)); + + /* If this is the last iteration, this could technically cause one + * extra byte to be written past the end. */ + if(i < bufsize && ((p + 1) < ((uint16_t*)ip_raw) + 8)) + buf[i] = ':'; + + i++; + } + } +#endif +} + +void sfip_ntop(const sfip_t *ip, char *buf, int bufsize) +{ + if(!ip) + { + if(buf && bufsize > 0) buf[0] = 0; + return; + } + + sfip_raw_ntop(sfip_family(ip), ip->ip32, buf, bufsize); +} + +/* Uses a static buffer to return a string representation of the IP */ +char *sfip_to_str(const sfip_t *ip) +{ + static char buf[INET6_ADDRSTRLEN]; + + sfip_ntop(ip, buf, sizeof(buf)); + + return buf; +} + +void sfip_free(sfip_t *ip) { + if(ip) free(ip); +} + +/* Returns 1 if the IP is non-zero. 0 otherwise */ +int sfip_is_loopback(sfip_t *ip) { + unsigned int *p; + + ARG_CHECK1(ip, 0); + + if(sfip_family(ip) == AF_INET) { + // 127.0.0.0/8 is IPv4 loopback + return (ip->ip8[0] == 0x7f); + } + + p = ip->ip32; + + /* Check the first 64 bits in an IPv6 address, and */ + /* verify they're zero. If not, it's not a loopback */ + if(p[0] || p[1]) return 0; + + /* Check if the 3rd 32-bit int is zero */ + if ( p[2] == 0 ) { + /* ::7f00:0/104 is ipv4 compatible ipv6 */ + /* ::1 is the IPv6 loopback */ + return ( (ip->ip8[12] == 0x7f) || (ntohl(p[3]) == 0x1) ); + } + /* Check the 3rd 32-bit int for a mapped IPv4 address */ + if ( ntohl(p[2]) == 0xffff ) { + /* ::ffff:127.0.0.0/104 is IPv4 loopback mapped over IPv6 */ + return ( ip->ip8[12] == 0x7f ); + } + return 0; +} + +int sfip_ismapped(sfip_t *ip) { + unsigned int *p; + + ARG_CHECK1(ip, 0); + + if(sfip_family(ip) == AF_INET) + return 0; + + p = ip->ip32; + + if(p[0] || p[1] || (ntohl(p[2]) != 0xffff && p[2] != 0)) return 0; + + return 1; +} + diff -uNr src/dynamic-preprocessors/ipv6//spp_ipv6.c src/dynamic-preprocessors/ipv6//spp_ipv6.c --- src/dynamic-preprocessors/ipv6//spp_ipv6.c 1970-01-01 01:00:00.000000000 +0100 +++ src/dynamic-preprocessors/ipv6//spp_ipv6.c 2012-05-12 20:19:37.333273975 +0200 @@ -0,0 +1,655 @@ +/* + * spp_ipv6.c + * + * Copyright (C) 2011 Martin Schuette <info@mschuette.name> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Description: + * The IPv6 Preprocessor. + * + */ + +#include "sf_ip.h" +// for sf_ip.c: +//#define INLINE inline +//#include "sf_ip.c" + +#include "spp_ipv6.h" +#include "spp_ipv6_constants.h" +#include "spp_ipv6_data_structs.h" +#include "spp_ipv6_data_structs.c" +#include "spp_ipv6_ruleopt.c" + +/* snort boilerplate code to support contexts and profiling */ +tSfPolicyUserContextId ipv6_config = NULL; +#ifdef SNORT_RELOAD +tSfPolicyUserContextId ipv6_swap_config = NULL; +#endif +extern DynamicPreprocessorData _dpd; + +#include "profiler.h" +#ifdef PERF_PROFILING +PreprocStats ipv6PerfStats; +#endif + +#include "spp_ipv6_parse.c" + +/** + * Register init functions when library is loaded. + */ +void IPv6_Preproc_Setup(void) +{ +#ifndef SNORT_RELOAD + _dpd.registerPreproc("ipv6", IPv6_Init); +#else + _dpd.registerPreproc("ipv6", IPv6_Init, NULL, NULL, NULL); +#endif +} + +/** + * Reset Stats + * (only used when reading multiple PCAP files, cf. README.pcap_readmode) + */ +static void IPv6_ResetStats(int signal, void *foo) +{ + struct IPv6_State *context; + sfPolicyUserPolicySet(ipv6_config, _dpd.getRuntimePolicy()); + context = (struct IPv6_State *) sfPolicyUserDataGetCurrent(ipv6_config); + + memset(context->stat, 0, sizeof(struct IPv6_Statistics)); +} + +/** + * Print some statistics on snort exit. + */ +static void IPv6_PrintStats(int exiting) +{ + struct IPv6_State *context; + sfPolicyUserPolicySet(ipv6_config, _dpd.getRuntimePolicy()); + context = (struct IPv6_State *) sfPolicyUserDataGetCurrent(ipv6_config); + if (!context) return; + + _dpd.logMsg("IPv6 statistics:\n"); + _dpd.logMsg("% 10u seen Packets\n", context->stat->pkt_seen); + _dpd.logMsg("% 10u invalid Packets\n", context->stat->pkt_invalid); + _dpd.logMsg("% 10u Fragments\n", context->stat->pkt_fragments); + _dpd.logMsg("% 10u IPv6\n", context->stat->pkt_ip6h); + _dpd.logMsg("% 10u ICMPv6\n", context->stat->pkt_icmpv6); + _dpd.logMsg("% 10u Other Upper Layer\n", context->stat->pkt_other); + _dpd.logMsg("\n"); + + _dpd.logMsg("% 10u router solicitation\n", context->stat->pkt_icmp_rsol); + _dpd.logMsg("% 10u router announcement\n", context->stat->pkt_icmp_radv); + _dpd.logMsg("% 10u neighbour solicitation\n", context->stat->pkt_icmp_nsol); + _dpd.logMsg("% 10u neighbour announcement\n", context->stat->pkt_icmp_nadv); + _dpd.logMsg("% 10u Mcast query\n", context->stat->pkt_icmp_mlquery); + _dpd.logMsg("% 10u Mcast report\n", context->stat->pkt_icmp_mlreport); + _dpd.logMsg("% 10u dst unreachable\n", context->stat->pkt_icmp_unreach); + _dpd.logMsg("% 10u Other\n", context->stat->pkt_icmp_other); + + _dpd.logMsg("\nAll routers (%d entries):\n", context->routers->entry_counter); + state_host_printlist(context->routers); + + _dpd.logMsg("\nAll hosts (%d entries):\n", context->hosts->entry_counter); + state_host_printlist(context->hosts); + + _dpd.logMsg("\nAll hosts in DAD state (%d entries):\n", context->unconfirmed->entry_counter); + state_host_printlist(context->unconfirmed); + + size_t size = 0; + size_t total = 0; + total += sizeof (*context); + total += sizeof (*context->stat); + total += sizeof (*context->config); + _dpd.logMsg("\n\nlast memory usage\n\t is %6d bytes fix\n", total); + + size = state_host_memusage(context->routers); + _dpd.logMsg("\tand %6d bytes for routers\n", size); + total += size; + size = state_host_memusage(context->hosts); + _dpd.logMsg("\tand %6d bytes for hosts\n", size); + total += size; + size = state_host_memusage(context->unconfirmed); + _dpd.logMsg("\tand %6d bytes for unconfirmed\n", size); + total += size; + + _dpd.logMsg("\t==> %6d bytes total (IPv6_Host size: %d bytes)\n", + total, sizeof(struct IPv6_Host)); +} + +/** + * Initialization function, invoked when preprocessor is activated. + * + * Has to parse our configuration options, add preprocessing callbacks, + * and init data structures. + */ +static void IPv6_Init(char *args) +{ + struct IP_List_head *prefixwl; + struct MAC_Entry_head *routerwl; + struct MAC_Entry_head *hostwl; + struct IPv6_Hosts_head *routers; + struct IPv6_Hosts_head *hosts; + struct IPv6_Hosts_head *unconf; + struct IPv6_Statistics *stat; + struct IPv6_State *context; + struct IPv6_Config *config; + + if (ipv6_config == NULL) { + ipv6_config = sfPolicyConfigCreate(); + } + + // allocate everything + prefixwl = (struct IP_List_head *) calloc(1, sizeof (struct IP_List_head)); + routerwl = (struct MAC_Entry_head *) calloc(1, sizeof (struct MAC_Entry_head)); + hostwl = (struct MAC_Entry_head *) calloc(1, sizeof (struct MAC_Entry_head)); + routers = (struct IPv6_Hosts_head *) calloc(1, sizeof (struct IPv6_Hosts_head)); + hosts = (struct IPv6_Hosts_head *) calloc(1, sizeof (struct IPv6_Hosts_head)); + unconf = (struct IPv6_Hosts_head *) calloc(1, sizeof (struct IPv6_Hosts_head)); + stat = (struct IPv6_Statistics *) calloc(1, sizeof (struct IPv6_Statistics)); + config = (struct IPv6_Config *) calloc(1, sizeof (struct IPv6_Config)); + context = (struct IPv6_State *) calloc(1, sizeof (struct IPv6_State)); + if (!routerwl || !hostwl || !prefixwl || !routers || !hosts || !unconf + || !stat || !config || !context || !ipv6_config) + _dpd.fatalMsg("Could not allocate IPv6 dyn-pp configuration struct.\n"); + + // initialize + STAILQ_INIT(prefixwl); + RB_INIT(&routerwl->data); + RB_INIT(&hostwl->data); + RB_INIT(&routers->data); + RB_INIT(&hosts->data); + RB_INIT(&unconf->data); + + config->router_whitelist = routerwl; + config->host_whitelist = hostwl; + config->prefix_whitelist = prefixwl; + + IPv6_Parse(args, config); + context->config = config; + context->stat = stat; + + context->routers = routers; + context->hosts = hosts; + context->unconfirmed = unconf; + context->routers->entry_limit = context->config->max_routers; + context->hosts->entry_limit = context->config->max_hosts; + context->unconfirmed->entry_limit = context->config->max_unconfirmed; + + sfPolicyUserPolicySet(ipv6_config, _dpd.getParserPolicy()); + sfPolicyUserDataSetCurrent(ipv6_config, context); + + /* Register the preprocessor function, priority, ICMP, ID PP_IPv6 + * -- we use PRIORITY_NORMALIZE because it's ok to run after frag3 + * and receive only reassembled packets. + * Change to PRIORITY_FIRST if any checks have to see fragments as well. + */ + _dpd.addPreproc(IPv6_Process, PRIORITY_NORMALIZE, PP_IPv6, PROTO_BIT__ICMP); + //_dpd.addPreproc(IPv6_Process, PRIORITY_FIRST, PP_IPv6, PROTO_BIT__ALL); + + _dpd.addPreprocResetStats(IPv6_ResetStats, NULL, PRIORITY_FIRST, PP_IPv6); + _dpd.registerPreprocStats("ipv6", IPv6_PrintStats); +#ifdef PERF_PROFILING + _dpd.addPreprocProfileFunc("ipv6", (void *)&ipv6PerfStats, 0, _dpd.totalPerfStats); +#endif + + // and now all rule options: + _dpd.preprocOptRegister("ipv", IPv6_Rule_Init, IPv6_Rule_Eval, + free, IPv6_Rule_Hash, IPv6_Rule_KeyCompare, NULL, NULL); + _dpd.preprocOptRegister("ip6_exthdr", IPv6_Rule_Init, IPv6_Rule_Eval, + free, IPv6_Rule_Hash, IPv6_Rule_KeyCompare, NULL, NULL); + _dpd.preprocOptRegister("ip6_extnum", IPv6_Rule_Init, IPv6_Rule_Eval, + free, IPv6_Rule_Hash, IPv6_Rule_KeyCompare, NULL, NULL); + _dpd.preprocOptRegister("ip6_flow", IPv6_Rule_Init, IPv6_Rule_Eval, + free, IPv6_Rule_Hash, IPv6_Rule_KeyCompare, NULL, NULL); + _dpd.preprocOptRegister("ip6_tclass", IPv6_Rule_Init, IPv6_Rule_Eval, + free, IPv6_Rule_Hash, IPv6_Rule_KeyCompare, NULL, NULL); + _dpd.preprocOptRegister("ip6_option", IPv6_Rule_Init, IPv6_Rule_Eval, + free, IPv6_Rule_Hash, IPv6_Rule_KeyCompare, NULL, NULL); + _dpd.preprocOptRegister("ip6_optval", IPv6_Rule_Init, IPv6_Rule_Eval, + free, IPv6_Rule_Hash, IPv6_Rule_KeyCompare, NULL, NULL); + _dpd.preprocOptRegister("ip6_rh", IPv6_Rule_Init, IPv6_Rule_Eval, + free, IPv6_Rule_Hash, IPv6_Rule_KeyCompare, NULL, NULL); + _dpd.preprocOptRegister("ip6_ext_ordered", IPv6_Rule_Init, IPv6_Rule_Eval, + free, IPv6_Rule_Hash, IPv6_Rule_KeyCompare, NULL, NULL); + _dpd.preprocOptRegister("icmp6_nd", IPv6_Rule_Init, IPv6_Rule_Eval, + free, IPv6_Rule_Hash, IPv6_Rule_KeyCompare, NULL, NULL); + _dpd.preprocOptRegister("icmp6_nd_option", IPv6_Rule_Init, IPv6_Rule_Eval, + free, IPv6_Rule_Hash, IPv6_Rule_KeyCompare, NULL, NULL); + + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "Preprocessor: IPv6 is initialized\n");); +} + +/** + * Count packet in statistics. + * + * @param p current packet + * @param stat my statistics + * + * @return void function + */ +inline static void IPv6_UpdateStats(const SFSnortPacket *p, struct IPv6_Statistics *stat) +{ + /* IPv6_Process already handles + * pkt_seen and pkt_invalid + * and aborts if not pkt_ip6h + */ + stat->pkt_ip6h++; + + if (p->icmp_header) { + switch(p->icmp_header->type) { + case ICMP6_SOLICITATION: + stat->pkt_icmp_rsol++; + break; + case ICMP6_N_SOLICITATION: + stat->pkt_icmp_nsol++; + break; + case ICMP6_ADVERTISEMENT: + stat->pkt_icmp_radv++; + break; + case ICMP6_N_ADVERTISEMENT: + stat->pkt_icmp_nadv++; + break; + case ICMP6_UNREACH: + stat->pkt_icmp_unreach++; + break; + case ICMP6_MEMBERSHIP_QUERY: + stat->pkt_icmp_mlquery++; + break; + case ICMP6_MEMBERSHIP_REPORT: + case MLDV2_LISTENER_REPORT: + stat->pkt_icmp_mlreport++; + break; + default: + stat->pkt_icmp_other++; + break; + } + } +} + +/* simple stateless checks of extension headers */ +inline static void IPv6_Process_Extensions(const SFSnortPacket *p, struct IPv6_State *context) +{ + uint_fast8_t i; + DEBUG_WRAP(DebugMessage(DEBUG_PLUGBASE, + "IPv6_Process_Extensions() ext num = %d\n", + p->num_ip6_extensions);); + for(i = 0; i < p->num_ip6_extensions; i++) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGBASE, + "IPv6_Process_Extensions() ext type = %d\n", + p->ip6_extensions[i].option_type);); + + if (p->ip6_extensions[i].option_type != IPPROTO_HOPOPTS + && p->ip6_extensions[i].option_type != IPPROTO_DSTOPTS) { + continue; + } else { + /* hbh_hdr is the Ext Hdr with type and length + * hbh_hdr+1 is the first option with type and length + * cursor iterates over the options + */ + struct ip6_hbh *hbh_hdr = (struct ip6_hbh*) p->ip6_extensions[i].option_data; + u_int16_t ext_len = (hbh_hdr->ip6h_len + 1) << 3; + u_int8_t *cursor = (u_int8_t *) (hbh_hdr+1); + u_int8_t *ext_end = ((u_int8_t *) hbh_hdr) + ext_len; + bool only_padding = true; + + while (cursor < ext_end) { + struct ip6_opt *opt = (struct ip6_opt*) cursor; + switch (opt->ip6o_type) { + case 0: // Pad1 + cursor += 1; + break; + case 1: // PadN + cursor += 2 + opt->ip6o_len; + break; + default: // everything else + cursor += 2 + opt->ip6o_len; + only_padding = false; + break; + } + } + if (cursor != ext_end) + ALERT(SID_IP6_OPTION_LENGTH_ERR); + if (only_padding) + ALERT(SID_IP6_ONLY_PADDING_EXT); + } + } +} + +/** + * Processing callback function for all packets. + * + * @param p packet to detect anomalies and overwrite attacks on + * @param context unused + * + * @return void function + */ +void IPv6_Process(void *pkt, void *snortcontext) +{ + PROFILE_VARS; + SFSnortPacket *p = (SFSnortPacket *) pkt; + struct IPv6_State *context; + + sfPolicyUserPolicySet(ipv6_config, _dpd.getRuntimePolicy()); + context = (struct IPv6_State *) sfPolicyUserDataGetCurrent(ipv6_config); + + //DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "IPv6_Process() called, pkt type = %d\n", + //(p && p->ip6h) ? p->ip6h->next : 0);); + + context->stat->pkt_seen++; + /* is the packet and the configuration valid? */ + if ((p == NULL) || (context == NULL)) { + context->stat->pkt_invalid++; + return; + } else if (!p->ip6h) { + return; + } else if (p->ip_fragmented) { /* skip incomplete packets */ + context->stat->pkt_fragments++; + return; + } + // else + + PREPROC_PROFILE_START(ipv6PerfStats); + IPv6_UpdateStats(p, context->stat); + if (p->ip6h->next != IPPROTO_ICMPV6) { + context->stat->pkt_other++; + } else { + context->stat->pkt_icmpv6++; + + IPv6_Process_Extensions(p, context); + + if (ND_hdrlen[p->icmp_header->type]) { + IPv6_Process_ND_Options(p, context); + } + if (p->icmp_header->type == ICMP6_ADVERTISEMENT) { + IPv6_Process_ICMPv6_RA_stateless(p); + } + if (context->config->track_ndp) { + IPv6_Process_ICMPv6(p, context); + } + } + PREPROC_PROFILE_END(ipv6PerfStats); +} + +/** + * Check ICMPv6 ND Options. + * icmp_hdr_len is givenby caller, because it varies depending on msg type + */ +inline static void IPv6_Process_ND_Options(const SFSnortPacket *p, struct IPv6_State *context) +{ + size_t icmp_hdr_len = ND_hdrlen[p->icmp_header->type]; + size_t len = p->ip_payload_size - icmp_hdr_len; + const u_int8_t *ptr = p->ip_payload + icmp_hdr_len; + struct nd_opt_hdr *option = (struct nd_opt_hdr *) ptr; + + while (len) { + uint_fast8_t optlen = 8 * (option->nd_opt_len); + if (option->nd_opt_type == ND_OPT_SOURCE_LINKADDR + && memcmp(p->ether_header->ether_source, option + 1, MAC_LENGTH)) + ALERT(SID_ICMP6_LINKADDR_MISMATCH); + + if (optlen > len) { + // should this be an alert of its own? + _dpd.logMsg("IPv6 decoder problem. malformed ND option lenghts."); + break; + } + len -= optlen; + option = (struct nd_opt_hdr *) ((u_int8_t*) option + (8 * option->nd_opt_len)); + } +} + +/** + * Process an IPv6 ICMP packet. + */ +static void IPv6_Process_ICMPv6(const SFSnortPacket *p, struct IPv6_State *context) +{ + // safety check + if (!p || !p->icmp_header) { + _dpd.logMsg("IPv6 decoder problem. ICMP packet without struct icmp_header"); + return; + } + + /* check if ongoing DAD */ + struct IPv6_Host *ip_entry; + ip_entry = get_host_entry(context->unconfirmed, &(p->ip6h->ip_dst)); + if (ip_entry) { + ip_entry->type.dad.contacted++; + } + + /* check type */ + switch (p->icmp_header->type) { + case ICMP6_ADVERTISEMENT: + IPv6_Process_ICMPv6_RA(p, context); + break; + case ICMP6_N_ADVERTISEMENT: + IPv6_Process_ICMPv6_NA(p, context); + break; + case ICMP6_N_SOLICITATION: + IPv6_Process_ICMPv6_NS(p, context); + break; + default: + break; + } + + /* periodically expire old entries from state */ + if (p->pkt_header->ts.tv_sec >= context->next_expire) { + //DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "%s expire state\n", pprint_ts(p->pkt_header->ts.tv_sec));); + context->next_expire = p->pkt_header->ts.tv_sec + context->config->expire_run_interval; + state_host_expirelist(context->routers, p->pkt_header->ts.tv_sec, context->config->keep_state_duration); + state_host_expirelist(context->hosts, p->pkt_header->ts.tv_sec, context->config->keep_state_duration); + state_host_expirelist(context->unconfirmed, p->pkt_header->ts.tv_sec, context->config->keep_state_duration); + }; + +} + +/** + * Process an IPv6 RA packet stateless, ie. check lifetime. + */ +static void IPv6_Process_ICMPv6_RA_stateless(const SFSnortPacket *p) +{ + struct ICMPv6_RA *radv = (struct ICMPv6_RA*) p->ip_payload; + + if (radv->nd_ra_lifetime == 0) { + ALERT(SID_ICMP6_RA_LIFETIME0); + } +} + +/** + * Process an IPv6 RA packet. + */ +static void IPv6_Process_ICMPv6_RA(const SFSnortPacket *p, struct IPv6_State *context) +{ + struct IPv6_Host *hostentry; + struct ICMPv6_RA *radv; + struct nd_opt_hdr *option; + struct nd_opt_prefix_info *prefix_info; + sfip_t *prefix = NULL; + uint_fast16_t len = p->ip_payload_size; + LISTOP_RET rc; + SFIP_RET sfip_rc; + + radv = (struct ICMPv6_RA*) p->ip_payload; + option = (struct nd_opt_hdr *) (radv + 1); + len -= sizeof (struct ICMPv6_RA); + + while (len) { + // check some known options + switch (option->nd_opt_type) { + case ND_OPT_PREFIX_INFORMATION: + prefix_info = (struct nd_opt_prefix_info *) option; + prefix = sfip_alloc_raw(&prefix_info->nd_opt_pi_prefix, AF_INET6, &sfip_rc); + if (sfip_rc != SFIP_SUCCESS) { + _dpd.errMsg("sfip_alloc_raw() failed\n"); + return; + } + sfip_set_bits(prefix, prefix_info->nd_opt_pi_prefix_len); + if (context->config->report_prefix_change && !ip_inlist(context->config->prefix_whitelist, prefix)) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "IP prefix %s/%d not in " + "configured list\n", sfip_to_str(prefix), sfip_bits(prefix));); + ALERT(SID_ICMP6_RA_UNKNOWN_PREFIX); + } + + break; + default: + break; + } + len -= 8 * (option->nd_opt_len); + option = (struct nd_opt_hdr *) ((u_int8_t*) option + (8 * option->nd_opt_len)); + } + // add state + rc = state_router_add(context->routers, + &hostentry, + &p->pkt_header->ts, + p->ether_header->ether_source, + &p->ip6h->ip_src, + prefix, radv); + + if (rc == LISTOP_ADDED) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "new IPv6 router advertised: %s / %s\n", + pprint_mac(p->ether_header->ether_source), + sfip_to_str(&p->ip6h->ip_src));); + + // different events, depending on existing whitelist + if (context->config->router_whitelist + && !RB_EMPTY(&context->config->router_whitelist->data) + && !get_mac_entry(context->config->router_whitelist, + p->ether_header->ether_source)) { + ALERT(SID_ICMP6_INVALID_ROUTER_MAC); + } else { + ALERT(SID_ICMP6_RA_NEW_ROUTER); + } + } +} + +/** + * Process neighbour advertisement msgs + */ +static void IPv6_Process_ICMPv6_NA(const SFSnortPacket *p, struct IPv6_State *context) +{ + /* check if part of DAD */ + struct nd_neighbor_advert *na = (struct nd_neighbor_advert *) p->ip_payload; + SFIP_RET sfrc; + sfip_t *target_ip; + struct IPv6_Host *ip_entry; + + target_ip = sfip_alloc_raw(&na->nd_na_target, AF_INET6, &sfrc); + if (sfrc != SFIP_SUCCESS) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "sfip_alloc_raw failed in %s:%d\n", __FILE__, __LINE__);); + return; + }; + + ip_entry = get_host_entry(context->unconfirmed, target_ip); + if (!ip_entry) { + /* IP is yet unknown --> put into DAD state */ + ip_entry = create_dad_entry_ifnew(context->unconfirmed, + &p->pkt_header->ts, + p->ether_header->ether_source, + target_ip); + /* no DAD info, so simply trust NA + * + * TODO: this leads to new DoS opportunity + * --> only confirm after some other communication occurs (TCP, UDP or MLD) + + confirm_host(p, context, target_ip); + */ + return; + } + + /* current DAD for this IP, now check details */ + if (!memcmp(p->ether_header->ether_source, ip_entry->ether_source, MAC_LENGTH)) { + /* MAC matches -- same node */ + if (ip_entry->type.dad.contacted) { + /* host entered the network */ + // possible optimization: keep IPv6_Host object to save free/malloc + confirm_host(p, context, target_ip); + del_dad_entry(context->unconfirmed, ip_entry); + } + /* else DAD exists, but still unconfirmed, so do nothing + * (could be result of NA flood/spoof) */ + } else { + /* MAC does not match -- collision + * --> check if NA from known MAC (legitimate) or not (suspicious) */ + if (get_host_entry(context->hosts, target_ip) + || get_host_entry(context->routers, target_ip) + || get_mac_entry(context->config->host_whitelist, p->ether_header->ether_source) + || get_mac_entry(context->config->router_whitelist, p->ether_header->ether_source)) { + // looks legitimate + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "DAD collision with host %s / %s\n", + pprint_mac(p->ether_header->ether_source), + sfip_to_str(target_ip));); + ALERT(SID_ICMP6_DAD_COLLISION); + } else { + // never seen the 2nd host before --> probably an attack + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "DAD DoS from (prob. fake MAC) %s / %s\n", + pprint_mac(p->ether_header->ether_source), + sfip_to_str(target_ip));); + ALERT(SID_ICMP6_DAD_DOS); + } + } +} + +/** + * Process neighbour solicitation msgs + */ +static void IPv6_Process_ICMPv6_NS(const SFSnortPacket *p, struct IPv6_State *context) +{ + struct nd_neighbor_solicit *ns = (struct nd_neighbor_solicit *) p->ip_payload; + SFIP_RET rc; + sfip_t *target_ip; + struct IPv6_Host *ip_entry; + + target_ip = sfip_alloc_raw(&ns->nd_ns_target, AF_INET6, &rc); + if (rc != SFIP_SUCCESS) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "sfip_alloc_raw failed in %s:%d\n", __FILE__, __LINE__);); + return; + }; + + // sfip_compare() is confusing, roll my own test for "::" + if (p->ip6h->ip_src.ip.u6_addr32[0] + || p->ip6h->ip_src.ip.u6_addr32[1] + || p->ip6h->ip_src.ip.u6_addr32[2] + || p->ip6h->ip_src.ip.u6_addr32[3]) { + /* src address set --> LLA resolution or reachability check */ + return; + } + + /* else: + * unspecified address + * --> i.e. new node tries to get address + * --> check if already known + */ + ip_entry = get_host_entry(context->hosts, target_ip); + if (ip_entry) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "Neighbour solicitation from known host\n");); + return; + } + + /* this is the expected part: the IP is yet unknown --> put into DAD state */ + ip_entry = create_dad_entry_ifnew(context->unconfirmed, + &p->pkt_header->ts, + p->ether_header->ether_source, + target_ip); + if (!ip_entry) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "create_dad_entry_ifnew failed in %s:%d\n", __FILE__, __LINE__);); + return; + } + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "%s DAD started by %s / %s\n", + pprint_ts(ip_entry->last_adv_ts), + pprint_mac(ip_entry->ether_source), + sfip_to_str(&ip_entry->ip));); + ALERT(SID_ICMP6_ND_NEW_DAD); +} diff -uNr src/dynamic-preprocessors/ipv6//spp_ipv6_constants.h src/dynamic-preprocessors/ipv6//spp_ipv6_constants.h --- src/dynamic-preprocessors/ipv6//spp_ipv6_constants.h 1970-01-01 01:00:00.000000000 +0100 +++ src/dynamic-preprocessors/ipv6//spp_ipv6_constants.h 2012-05-12 20:19:37.341266025 +0200 @@ -0,0 +1,190 @@ +/* + * spp_ipv6_constants.h + * + * Copyright (C) 2011 Martin Schuette <info@mschuette.name> + * + * Include file for + * - network protocol constants and data structures not included in all OS + * - own plugin-specific constants + * - the plugin's SIDs + * + */ + +#ifndef CONSTANTS_H +#define CONSTANTS_H + +#include "sf_snort_packet.h" +#include <netinet/icmp6.h> + +/********************************************************************** + ** Protocol Constants ** + **********************************************************************/ + +/* some constants, as already used in decode.c */ +#define IPPROTO_MOBILITY 135 + +/* ICMPv6 types, http://www.iana.org/assignments/icmpv6-parameters */ +#define ICMP6_UNREACH 1 +#define ICMP6_BIG 2 +#define ICMP6_TIME 3 +#define ICMP6_PARAMS 4 +#define ICMP6_ECHO 128 +#define ICMP6_REPLY 129 +// NDP +#define ICMP6_SOLICITATION 133 +#define ICMP6_ADVERTISEMENT 134 +#define ICMP6_N_SOLICITATION 135 +#define ICMP6_N_ADVERTISEMENT 136 +#define ICMP6_REDIRECT 137 +// Inverse ND +#define ICMP6_INV_SOLICITATION 141 +#define ICMP6_INV_ADVERTISEMENT 142 +// Mobile IPv6 +#define ICMP6_HOME_AD_REQUEST 144 +#define ICMP6_HOME_AD_REPLY 145 +#define ICMP6_MOBILEPREFIX_SOL 146 +#define ICMP6_MOBILEPREFIX_ADV 147 +// SEND +#define ICMP6_CRT_SOLICITATION 148 +#define ICMP6_CRT_ADVERTISEMENT 149 +// MIP6 Fast Handovers +#define ICMP6_MOBILE_FH 154 + +/* IPv6 ND Options, http://www.iana.org/assignments/icmpv6-parameters */ +#define ICMP6_OPT_HOMEAGENT 8 +#define ICMP6_OPT_CGA 11 +#define ICMP6_OPT_RSA 12 +#define ICMP6_OPT_TIMESTAMP 13 +#define ICMP6_OPT_NONCE 14 +#define ICMP6_OPT_ANCHOR 15 +#define ICMP6_OPT_CERT 16 +#define ICMP6_OPT_EXP1 253 +#define ICMP6_OPT_EXP2 254 + +/* some more constants copied from BSD's <netinet/icmp6.h> */ +/* RFC2292 decls */ +#define ICMP6_MEMBERSHIP_QUERY 130 /* group membership query */ +#define ICMP6_MEMBERSHIP_REPORT 131 /* group membership report */ +#define ICMP6_MEMBERSHIP_REDUCTION 132 /* group membership termination */ +#define ICMP6_WRUREQUEST 139 /* who are you request */ +#define ICMP6_WRUREPLY 140 /* who are you reply */ +#define ICMP6_FQDN_QUERY 139 /* FQDN query */ +#define ICMP6_FQDN_REPLY 140 /* FQDN reply */ +#define ICMP6_NI_QUERY 139 /* node information request */ +#define ICMP6_NI_REPLY 140 /* node information reply */ +#define MLDV2_LISTENER_REPORT 143 /* RFC3810 listener report */ + +/* and some more constants copied from BSD's <netinet/in.h> */ +#define IPPROTO_IPV4 4 /* IPv4 encapsulation */ +#define IPPROTO_IPEIP 94 /* IP encapsulated in IP */ +#define IPPROTO_ETHERIP 97 /* Ethernet IP encapsulation */ + +/********************************************************************** + ** Protocol Data Formats ** + **********************************************************************/ + +/* + * RA packet format + */ +struct ICMPv6_RA { + /* fixed part */ + struct _ICMP6 icmp6h; + u_int8_t nd_ra_cur_hop_limit; + union { + u_int8_t m:1, + o:1, + h:1, + prf:2, + res:3; + u_int8_t all; + } flags; + u_int16_t nd_ra_lifetime; + u_int32_t nd_ra_reachable; + u_int32_t nd_ra_retransmit; +} __attribute__((packed)); + +/* This array defines which ICMPv6 types may contain neighbor discovery options + * and contain their header lengths, i.e. the right offsets to find their options. + * + * (Most lengths are sizeof(struct nd_router_solicit) -- this is the basic ICMPv6 + * type with 32 bits for type/code/checksum, 32 bits reserved or for identifiers, + * and possibly ND options starting in the 3nd 32-bit block.) + */ +uint_fast8_t ND_hdrlen[255] = { + [ICMP6_SOLICITATION] = sizeof(struct nd_router_solicit), + [ICMP6_ADVERTISEMENT] = sizeof(struct nd_router_advert), + [ICMP6_N_SOLICITATION] = sizeof(struct nd_neighbor_solicit), + [ICMP6_N_ADVERTISEMENT] = sizeof(struct nd_neighbor_advert), + [ICMP6_REDIRECT] = sizeof(struct nd_redirect), + [ICMP6_INV_SOLICITATION] = sizeof(struct nd_router_solicit), + [ICMP6_INV_ADVERTISEMENT] = sizeof(struct nd_router_solicit), + [ICMP6_MOBILEPREFIX_ADV] = sizeof(struct nd_router_solicit), + [ICMP6_CRT_SOLICITATION] = sizeof(struct nd_router_solicit), + [ICMP6_CRT_ADVERTISEMENT] = sizeof(struct certpath_adv {struct icmp6_hdr hdr; + u_int16_t compact; u_int16_t reserved;}), + [ICMP6_MOBILE_FH] = sizeof(struct nd_router_solicit), +}; + +/********************************************************************** + ** Snort-Plugin Constants ** + **********************************************************************/ + +/* + * every preprocessor has a 32-bit ID. + * preprocids.h defines 20 standard IDs, we use magic numbers + */ +#define PP_IPv6 0xC0FFEE +/* generator.h defines about 170 standard GIDs, we use a magic number */ +#define GEN_ID_IPv6 248 + +// useful for memcpy calls +#define MAC_LENGTH (6*sizeof(u_int8_t)) + +/********************************************************************** + ** SIDs & descriptions for all alerts/warnings ** + **********************************************************************/ +#define SID_ICMP6_RA_NEW_ROUTER 1 +#define SID_ICMP6_RA_NEW_ROUTER_TEXT "ipv6: RA from new router" +#define SID_ICMP6_INVALID_ROUTER_MAC 2 +#define SID_ICMP6_INVALID_ROUTER_MAC_TEXT "ipv6: RA from non-router MAC address" +#define SID_ICMP6_RA_PREFIX_CHANGED 3 +#define SID_ICMP6_RA_PREFIX_CHANGED_TEXT "ipv6: RA prefix changed" +#define SID_ICMP6_RA_FLAGS_CHANGED 4 +#define SID_ICMP6_RA_FLAGS_CHANGED_TEXT "ipv6: RA flags changed" +#define SID_ICMP6_RA_UNKNOWN_PREFIX 5 +#define SID_ICMP6_RA_UNKNOWN_PREFIX_TEXT "ipv6: RA for non-local net prefix" +#define SID_ICMP6_RA_LIFETIME0 6 +#define SID_ICMP6_RA_LIFETIME0_TEXT "ipv6: RA with lifetime 0" +#define SID_ICMP6_ND_NEW_DAD 7 +#define SID_ICMP6_ND_NEW_DAD_TEXT "ipv6: new DAD started" +#define SID_ICMP6_ND_NEW_HOST 8 +#define SID_ICMP6_ND_NEW_HOST_TEXT "ipv6: new host in network" +#define SID_ICMP6_INVALID_HOST_MAC 9 +#define SID_ICMP6_INVALID_HOST_MAC_TEXT "ipv6: new host with non-allowed MAC address" +#define SID_ICMP6_DAD_COLLISION 10 +#define SID_ICMP6_DAD_COLLISION_TEXT "ipv6: DAD with collision" +#define SID_ICMP6_DAD_DOS 11 +#define SID_ICMP6_DAD_DOS_TEXT "ipv6: DAD with spoofed collision" +#define SID_ICMP6_LINKADDR_MISMATCH 12 +#define SID_ICMP6_LINKADDR_MISMATCH_TEXT "ipv6: mismatch in MAC and NDP source linkaddress option" +#define SID_IP6_ONLY_PADDING_EXT 13 +#define SID_IP6_ONLY_PADDING_EXT_TEXT "ipv6: extension header has only padding options (evasion?)" +#define SID_IP6_OPTION_LENGTH_ERR 14 +#define SID_IP6_OPTION_LENGTH_ERR_TEXT "ipv6: option lengths != ext length" + + +/* TODO: + * is it desirable to have only one alert per SID per packet? + * currently some SIDs test multiple attributes, thus can be raised more than once per packet. + */ + +/* Macro for alerts */ +/* Arguments are: gid, sid, rev, classification, priority, message, rule_info */ +#define ALERT(x) { DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "Raise Alert: %d, %s\n", x, x##_TEXT);); \ + _dpd.alertAdd(GEN_ID_IPv6, x, 1, 0, 3, x##_TEXT, 0 ); } +/* different classification (?) +#define WARN(x) { DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "Raise Warning: %d, %s\n", x, x##_TEXT);); \ + _dpd.alertAdd(GEN_ID_IPv6, x, 1, 1, 3, x##_TEXT, 0 ); } +*/ + +#endif /* CONSTANTS_H */ diff -uNr src/dynamic-preprocessors/ipv6//spp_ipv6_data_structs.c src/dynamic-preprocessors/ipv6//spp_ipv6_data_structs.c --- src/dynamic-preprocessors/ipv6//spp_ipv6_data_structs.c 1970-01-01 01:00:00.000000000 +0100 +++ src/dynamic-preprocessors/ipv6//spp_ipv6_data_structs.c 2012-05-12 20:19:37.349265420 +0200 @@ -0,0 +1,520 @@ +/* + * spp_ipv6_data_structs.c + * + * Copyright (C) 2011 Martin Schuette <info@mschuette.name> + * + * Insert/remove methods all data structures representing network state, + * especially lists and trees. + * + */ + +#include "spp_ipv6_constants.h" +#include "spp_ipv6_data_structs.h" + + +/********************************************************************** + ** STAILQ IP_List ** + **********************************************************************/ + +/** + * Add IP (sfip_t) to a list. + * no input checking, arguments have to be valid. + */ +static void add_ip(struct IP_List_head *listhead, sfip_t *ip) +{ + struct IP_List *entry; + if (!(entry = (struct IP_List *) calloc(1, sizeof(struct IP_List)))) + _dpd.fatalMsg("Could not allocate IPv6 dyn-pp configuration struct.\n"); + + entry->ip = ip; + STAILQ_INSERT_TAIL(listhead, entry, entries); +} + +/** + * Check if IP matches a prefix in list. + */ +static bool ip_inprefixlist(struct IP_List_head *listhead, sfip_t *ip) +{ + struct IP_List *entry; + + STAILQ_FOREACH(entry, listhead, entries) { + if (SFIP_EQUAL == sfip_contains(entry->ip, ip)) { + return true; + } + } + return false; +} + +/** + * Check if IP is in list. + */ +static bool ip_inlist(struct IP_List_head *listhead, sfip_t *ip) +{ + struct IP_List *entry; + + STAILQ_FOREACH(entry, listhead, entries) { + if (SFIP_EQUAL == sfip_compare(entry->ip, ip)) { + return true; + } + } + return false; +} + +/********************************************************************** + ** RB MAC_List / IPv6_Host ** + **********************************************************************/ + +/** + * Compare MAC addesses + */ +static short mac_cmp(struct MAC_Entry *a, struct MAC_Entry *b) +{ + return memcmp(&a->mac, &b->mac, sizeof(a->mac)); +} + +/** + * Compare IPv6 hosts (only by IP) + * + * NB: no input checking; assume two valid IPv6 addresses + */ +static short host_cmp(struct IPv6_Host *a, struct IPv6_Host *b) +{ + // optimized + return memcmp(&a->ip.ip, &b->ip.ip, sizeof(b->ip.ip)); + + /* alternative with sfip api + SFIP_RET rc; + rc = sfip_compare(&a->ip, &b->ip); + switch (rc) { + case SFIP_LESSER: return -1; + case SFIP_GREATER: return +1; + default: return 0; + } + */ +} + +/** + * Parse a string MAC into binary data + * no input checking, arguments have to be valid + */ +static void mac_parse(const char* string, u_int8_t dst[]) +{ + dst[0] = (u_int8_t) strtoul(&string[ 0], NULL, 16); + dst[1] = (u_int8_t) strtoul(&string[ 3], NULL, 16); + dst[2] = (u_int8_t) strtoul(&string[ 6], NULL, 16); + dst[3] = (u_int8_t) strtoul(&string[ 9], NULL, 16); + dst[4] = (u_int8_t) strtoul(&string[12], NULL, 16); + dst[5] = (u_int8_t) strtoul(&string[15], NULL, 16); +} + +/** + * Aux. function to format MAC address (in static buffer). + */ +static char *pprint_mac(const u_int8_t ether_source[]) +{ + static char buf[18]; + snprintf(buf, sizeof(buf), + "%02x:%02x:%02x:%02x:%02x:%02x", + ether_source[0], ether_source[1], + ether_source[2], ether_source[3], + ether_source[4], ether_source[5]); + return buf; +} + +/** + * Aux. function to format timestamp (in static buffer). + */ +static char *pprint_ts(const time_t ts) +{ + struct tm *printtm; + static char buf[64]; + + printtm = localtime(&ts); + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", printtm); + + return buf; +} + + +/** + * Add string MAC to a tree. + * no input checking, arguments have to be valid. + * + * TODO: merge with normal add method + */ +static void mac_add(struct MAC_Entry_head *head, const char *mac) +{ + struct MAC_Entry *entry; + + if (!(entry = (struct MAC_Entry *) calloc(1, sizeof(struct MAC_Entry)))) + _dpd.fatalMsg("Could not allocate IPv6 dyn-pp configuration struct.\n"); + + mac_parse(mac, entry->mac); + RB_INSERT(MAC_Entry_data, &head->data, entry); +} + +RB_GENERATE_STATIC(MAC_Entry_data, MAC_Entry, entries, mac_cmp); +RB_GENERATE_STATIC(IPv6_Hosts_data, IPv6_Host, entries, host_cmp); + +/** + * deletes an IPv6_Host entry + */ +static void del_host_entry(struct IPv6_Hosts_head *head, + struct IPv6_Host *ip) +{ + if (RB_REMOVE(IPv6_Hosts_data, &head->data, ip)) { + sfip_free(ip->type.router.prefix); + free(ip); + head->entry_counter--; + } else { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "del_host_entry: RB_REMOVE failed\n");); + } +} + +/** + * creates an IPv6_Host entry if it does not exist already (NULL on error) + * Note: only for DADs, i.e. the MAC layer uses 'ff:ff:ff:ff:ff:ff' + */ +static struct IPv6_Host *create_dad_entry_ifnew(struct IPv6_Hosts_head *head, + const struct timeval *tv, + const u_int8_t ether_source[], + const sfip_t *ip_src) +{ + return create_host_entry(head, tv, ether_source, ip_src); +} + +/** + * deletes an IPv6_Host entry from DAD + */ +static void del_dad_entry(struct IPv6_Hosts_head *head, + struct IPv6_Host *ip) +{ + del_host_entry(head, ip); +} + +/** + * creates an IPv6_Host entry (or NULL on error) + */ +static struct IPv6_Host *create_host_entry(struct IPv6_Hosts_head *head, + const struct timeval *tv, + const u_int8_t ether_source[], + const sfip_t *ip_src) +{ + struct IPv6_Host *ip_entry = NULL; + struct IPv6_Host *ip_dupl = NULL; + + if (head->entry_limit && head->entry_limit <= head->entry_counter) { + _dpd.logMsg("MAC Tree @ 0x%x is full with %d elements," + " cannot add new entry\n", + head, head->entry_counter); + return NULL; + } + + if (!(ip_entry = (struct IPv6_Host *) calloc(1, sizeof (struct IPv6_Host)))) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "malloc failed in %s:%d\n", __FILE__, __LINE__);); + return NULL; + } + + // get IP (link-local) + if (sfip_set_ip(&ip_entry->ip, ip_src)) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "error in sfip_set_ip in %s:%d\n", __FILE__, __LINE__);); + free(ip_entry); + return NULL; + } + // get MAC + memcpy(&ip_entry->ether_source, ether_source, MAC_LENGTH); + // get timestamp + ip_entry->last_adv_ts = tv->tv_sec; + + // insert + if ((ip_dupl = RB_INSERT(IPv6_Hosts_data, &head->data, ip_entry))) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, + "RB_INSERT failed in %s:%d\n\tnode %s/%s\n", + __FILE__, __LINE__, + pprint_mac(ip_dupl->ether_source), + sfip_to_str(&ip_dupl->ip));); + return NULL; + } else { + head->entry_counter++; + //DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "RB_INSERT @ 0x%x, now %d entries\n", mac_head, mac_head->entry_counter);); + return ip_entry; + } +} + +/** + * use MAC list to retrieve on IPv6_Host entry (or NULL on error) + * i.e. search both layers + * + * FIXME: unused? + */ +static struct IPv6_Host *get_machost_entry(struct MAC_Entry_head *head, + const u_int8_t ether_source[], + const sfip_t *ip_src) +{ + struct MAC_Entry *mac = get_mac_entry(head, ether_source); + + if (mac) return get_host_entry(mac->ips, ip_src); + else return NULL; +} + +/** + * retrieves an IPv6_Host entry (or NULL on error) + */ +static struct IPv6_Host *get_host_entry(struct IPv6_Hosts_head *head, + const sfip_t *ip_src) +{ + struct IPv6_Host ip_pivot; + if (sfip_set_ip(&ip_pivot.ip, ip_src)) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "error in sfip_set_ip in %s:%d\n", __FILE__, __LINE__);); + return NULL; + } + return RB_FIND(IPv6_Hosts_data, &head->data, &ip_pivot); +} + +/** + * retrieves a MAC_List entry (or NULL on error) + */ +static struct MAC_Entry *get_mac_entry(struct MAC_Entry_head *head, + const u_int8_t ether_source[]) +{ + struct MAC_Entry mac_pivot = { + .mac = {ether_source[0], ether_source[1], ether_source[2], + ether_source[3], ether_source[4], ether_source[5]} + }; + return RB_FIND(MAC_Entry_data, &head->data, &mac_pivot); +} + +/** + * creates a MAC_List entry, including the IP-tree head + * returns entry, or NULL on error + */ +static struct MAC_Entry *create_mac_entry(struct MAC_Entry_head *head, + const u_int8_t ether_source[]) +{ + struct MAC_Entry *new_mac = NULL; + struct IPv6_Hosts_head *ip_head = NULL; + + if (!(new_mac = (struct MAC_Entry *) calloc(1, sizeof (struct MAC_Entry)))) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "malloc failed in %s:%d\n", __FILE__, __LINE__);); + return NULL; + } + if (!(ip_head = (struct IPv6_Hosts_head *) calloc(1, sizeof (struct IPv6_Hosts_head)))) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "malloc failed in %s:%d\n", __FILE__, __LINE__);); + free(new_mac); + return NULL; + } + + new_mac->ips = ip_head; + RB_INIT(&new_mac->ips->data); + memcpy(&new_mac->mac, ether_source, MAC_LENGTH); + if (RB_INSERT(MAC_Entry_data, &head->data, new_mac)) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "RB_INSERT failed in %s:%d\n", __FILE__, __LINE__);); + free(new_mac); + free(ip_head); + return NULL; + } else { + return new_mac; + } +} + +/** + * Add a router to a sorted list. + */ +static LISTOP_RET state_router_add(struct IPv6_Hosts_head *head, + struct IPv6_Host **elem, + const struct timeval* tv, + const u_int8_t ether_source[], + const sfip_t* ip_src, + sfip_t* prefix, + struct ICMPv6_RA* radv + ) +{ + LISTOP_RET rc; + char old[64], new[64]; + + rc = state_host_add(head, elem, tv, ether_source, ip_src); + + if (rc == LISTOP_ADDED) { + // new router: add prefix & flags + if (((*elem)->type.router.prefix = (sfip_t*) calloc(1, sizeof (sfip_t)))) + (*elem)->type.router.prefix = prefix; + (*elem)->type.router.flags.all = radv->flags.all; + (*elem)->type.router.lifetime = radv->nd_ra_lifetime; + return LISTOP_ADDED; + } else if (rc == LISTOP_UPDATED) { + // known router: verify prefix & flags + + // TODO: check if one router may announce multiple prefixes + // the second cmp is a workaround because sfip_compare + if (sfip_compare((*elem)->type.router.prefix, prefix) != SFIP_EQUAL + || sfip_bits((*elem)->type.router.prefix) != sfip_bits(prefix)) { + snprintf(old, sizeof(old), "%s/%d", + sfip_to_str((*elem)->type.router.prefix), + sfip_bits((*elem)->type.router.prefix)); + snprintf(new, sizeof(new), "%s/%d", + sfip_to_str(prefix), + sfip_bits(prefix)); + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "announced router prefix changed " + "from %s to %s\n", old, new);); + ALERT(SID_ICMP6_RA_PREFIX_CHANGED); + } + + if ((*elem)->type.router.flags.all != radv->flags.all || + (*elem)->type.router.lifetime != radv->nd_ra_lifetime + ) { + ALERT(SID_ICMP6_RA_FLAGS_CHANGED); + // keep it simple and just update everything after alert + (*elem)->type.router.flags.all = radv->flags.all; + (*elem)->type.router.lifetime = radv->nd_ra_lifetime; + } + } + return rc; +} + +/** + * add host to state + * ip_src is optional (if NULL, then package ip_src is used) + */ +static void confirm_host(const SFSnortPacket *p, + struct IPv6_State *context, + const sfip_t* ip_src) +{ + LISTOP_RET rc; + struct IPv6_Host *hostentry; + const sfip_t* target_ip = ip_src ? ip_src : &p->ip6h->ip_src; + + rc = state_host_add(context->hosts, + &hostentry, + &p->pkt_header->ts, + p->ether_header->ether_source, + target_ip); + if (rc == LISTOP_ADDED) { + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "%s new IPv6 host: %s / %s\n", + pprint_ts(p->pkt_header->ts.tv_sec), + pprint_mac(p->ether_header->ether_source), + sfip_to_str(target_ip));); + if (context->config->host_whitelist + && !RB_EMPTY(&context->config->host_whitelist->data) + && !get_mac_entry(context->config->host_whitelist, p->ether_header->ether_source)) { + ALERT(SID_ICMP6_INVALID_HOST_MAC); + } else { + ALERT(SID_ICMP6_ND_NEW_HOST); + } + } else if (rc == LISTOP_UPDATED) { // nothing + } else { // (rc == LISTOP_ERROR) + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "IPv6 PP: cannot add state for IPv6 host %s / %s\n", + pprint_mac(p->ether_header->ether_source), + sfip_to_str(target_ip));); + } +} + +/** + * Add a host to a sorted MAC/IP list. If entry exists then only update timestamp. + * + * Returns address of added host entry in *elem. + */ +static LISTOP_RET state_host_add(struct IPv6_Hosts_head *head, + struct IPv6_Host **elem, + const struct timeval* tv, + const u_int8_t ether_source[], + const sfip_t* ip_src) +{ + + /* MAC entry exists --> check if IP is already known */ + struct IPv6_Host *ip_entry = get_host_entry(head, ip_src); + + if (ip_entry) { // IP exists --> update timestamp and exit + ip_entry->last_adv_ts = tv->tv_sec; + *elem = ip_entry; + return LISTOP_UPDATED; + } + + /* else: either MAC existed without IP, or MAC was just created * + * --> anyway, now create IPv6_Host */ + *elem = create_host_entry(head, tv, ether_source, ip_src); + if (!*elem) + return LISTOP_ERROR; + else + return LISTOP_ADDED; +} + +/** + * Auxillary function to print uniform lists of hosts and routers. + */ +static void state_host_printlist(struct IPv6_Hosts_head *head) +{ + struct IPv6_Host *host; + char routerinfo[128]; + + RB_FOREACH(host, IPv6_Hosts_data, &head->data) { + if (host->type.router.prefix && sfip_is_set(host->type.router.prefix)) { + // for routers: + snprintf(routerinfo, sizeof (routerinfo), + "\n\t-- prefix %s/%d, lifetime %d sec, flags %s%s%s, pref %s", + sfip_to_str(host->type.router.prefix), + sfip_bits(host->type.router.prefix), + host->type.router.lifetime, + host->type.router.flags.m ? "M" : "-", + host->type.router.flags.o ? "O" : "-", + host->type.router.flags.h ? "H" : "-", + (host->type.router.flags.prf == 0 ? "default" : + (host->type.router.flags.prf == 3 ? "low" : + (host->type.router.flags.prf == 1 ? "high" : "reserved"))) + ); + } else + routerinfo[0] = '\0'; + + _dpd.logMsg("MAC %s -- IP %s -- last seen: %s%s\n", + pprint_mac(host->ether_source), + sfip_to_str(&host->ip), + pprint_ts(host->last_adv_ts), + routerinfo); + } +} + +/** + * Auxillary function to expire entries from state. + * now is the current timestamp, + * keep indicates the hold time, should come from config->keep_state + * + * returns number of entries after deletions + */ +static u_int32_t state_host_expirelist(struct IPv6_Hosts_head *head, time_t now, time_t keep) +{ + struct IPv6_Host *var, *nxt; + u_int32_t entries = 0; + + for (var = RB_MIN(IPv6_Hosts_data, &head->data); var != NULL; var = nxt) { + nxt = RB_NEXT(IPv6_Hosts_data, &head->data, var); + + if (now - var->last_adv_ts > keep) { // expired + DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "%s expire %s / %s after %d secs (leaves %d entries)\n", + pprint_ts(now), + pprint_mac(var->ether_source), + sfip_to_str(&var->ip), + now - var->last_adv_ts, + head->entry_counter - 1);); + del_host_entry(head, var); + } else { + entries++; + } + } + return entries; +} + +/** + * Aux. function to get memory consumtion + */ +static size_t state_host_memusage(struct IPv6_Hosts_head *head) +{ + struct IPv6_Host *var; + size_t size = 0; + + size += sizeof(*head); + RB_FOREACH(var, IPv6_Hosts_data, &head->data) { + size += sizeof(*var); + if (var->type.router.prefix) + size += sizeof(*var->type.router.prefix); + } + return size; +} diff -uNr src/dynamic-preprocessors/ipv6//spp_ipv6_data_structs.h src/dynamic-preprocessors/ipv6//spp_ipv6_data_structs.h --- src/dynamic-preprocessors/ipv6//spp_ipv6_data_structs.h 1970-01-01 01:00:00.000000000 +0100 +++ src/dynamic-preprocessors/ipv6//spp_ipv6_data_structs.h 2012-05-12 20:19:37.357266311 +0200 @@ -0,0 +1,215 @@ +/* + * spp_ipv6_data_structs.h + * + * Copyright (C) 2011 Martin Schuette <info@mschuette.name> + * + * Include file for all data structures representing network state, + * especially lists and trees with prototypes of their respective + * insert/remove methods. + * + */ + +#ifndef DATASTRUCTS_H +#define DATASTRUCTS_H +#include "spp_ipv6.h" + +/********************************************************************** + ** Structures/Data Types ** + **********************************************************************/ + +/* return values for list operations */ +typedef enum _list_op_return_values { + LISTOP_ADDED = 0, // entry added + LISTOP_UPDATED, // entry updatet (e.g. new timestamp) + LISTOP_EXISTS, // entry already exists, no change + LISTOP_ERROR // error, e.g. malloc() failed +} LISTOP_RET; + +/* + * State for this module, i.e. seen hosts and adresses. + * (current size: 60 bytes on i386, 88 bytes on amd64) + */ + +// second level is a tree of IPs (per MAC) +struct IPv6_Host { + // common data for all hosts + RB_ENTRY(IPv6_Host) entries; + u_int8_t ether_source[6]; + time_t last_adv_ts; + sfip_t ip; + // extra data for routers/DAD detection + union { + struct { + sfip_t *prefix; + u_int16_t lifetime; + union { + u_int8_t m:1, + o:1, + h:1, + prf:2; + u_int8_t all; + } flags; + } router; + struct { + sfip_t *noprefix; // FIXME: currently only non-NULL in this field indicates a router entry + u_int32_t contacted; // count if any 2nd host sends request + } dad; + } type; +}; +RB_HEAD(IPv6_Hosts_data, IPv6_Host); + +struct IPv6_Hosts_head { + struct IPv6_Hosts_data data; + u_int32_t entry_limit; + u_int32_t entry_counter; +}; + +/* + * this struct has two uses: + * - with ips = NULL it holds a list of MAC addresses for configuration options + * - with ips set it is the first level of state information + */ +struct MAC_Entry { + RB_ENTRY(MAC_Entry) entries; + u_int8_t mac[6]; + struct IPv6_Hosts_head *ips; +} __attribute__((aligned)); +RB_HEAD(MAC_Entry_data, MAC_Entry); + +struct MAC_Entry_head { + struct MAC_Entry_data data; + u_int32_t entry_limit; + u_int32_t entry_counter; +}; + +// List because it has few entries and may use prefixes instead of IPs +struct IP_List { + STAILQ_ENTRY(IP_List) entries; + sfip_t *ip; +}; +STAILQ_HEAD(IP_List_head, IP_List); + +/* + * Note on the data structures used: + * o configuration settings + * - router_whitelist, host_whitelist + * each is one rb-tree of MAC_List entries + * - prefix_whitelist + * is one stailq of IP_List + * o network state + * - routers, hosts, unconfirmed/dads + * each one is an rb-tree of IPv6_Host entries + * + */ + +/********************************************************************** + ** Function Prototypes ** + **********************************************************************/ +static void confirm_host( + const SFSnortPacket *p, + struct IPv6_State *context, + const sfip_t* ip_src +) __attribute__((nonnull(1, 2))); +static LISTOP_RET state_host_add( + struct IPv6_Hosts_head*, + struct IPv6_Host**, + const struct timeval*, + const u_int8_t[], + const sfip_t* +) __attribute__((nonnull(1, 3, 4, 5))); +static LISTOP_RET state_router_add( + struct IPv6_Hosts_head*, + struct IPv6_Host **, + const struct timeval*, + const u_int8_t[], + const sfip_t*, + sfip_t*, + struct ICMPv6_RA* +) __attribute__((nonnull(1, 3, 4, 5, 6))); +static u_int32_t state_host_expirelist( + struct IPv6_Hosts_head*, + time_t, + time_t +) __attribute__((nonnull)); +static size_t state_host_memusage( + struct IPv6_Hosts_head* +) __attribute__((nonnull)); +static void state_host_printlist( + struct IPv6_Hosts_head * +) __attribute__((nonnull)); +static char *pprint_mac( + const u_int8_t[] +) __attribute__((nonnull)); +static char *pprint_ts( + const time_t ts +); +static void mac_parse( + const char* string, + u_int8_t dst[] +)__attribute__((nonnull)); +static void mac_add( + struct MAC_Entry_head*, + const char* +)__attribute__((nonnull)); +static short mac_cmp( + struct MAC_Entry*, + struct MAC_Entry* +)__attribute__((nonnull)); +static short host_cmp( + struct IPv6_Host*, + struct IPv6_Host* +)__attribute__((nonnull)); +static void add_ip( + struct IP_List_head*, + sfip_t* +)__attribute__((nonnull)); +static bool ip_inprefixlist( + struct IP_List_head*, + sfip_t* +)__attribute__((nonnull,unused)); +static bool ip_inlist( + struct IP_List_head*, + sfip_t* +)__attribute__((nonnull)); +static struct IPv6_Host *get_machost_entry( + struct MAC_Entry_head*, + const u_int8_t[], + const sfip_t* +)__attribute__((nonnull,unused)); +static struct MAC_Entry *get_mac_entry( + struct MAC_Entry_head*, + const u_int8_t[] +)__attribute__((nonnull)); +static struct MAC_Entry *create_mac_entry( + struct MAC_Entry_head*, + const u_int8_t[] +)__attribute__((malloc,nonnull,unused)); +static struct IPv6_Host *get_host_entry( + struct IPv6_Hosts_head*, + const sfip_t* +)__attribute__((nonnull)); +static struct IPv6_Host *create_host_entry( + struct IPv6_Hosts_head*, + const struct timeval*, + const u_int8_t[], + const sfip_t* +)__attribute__((malloc,nonnull)); +static struct IPv6_Host *create_dad_entry_ifnew( + struct IPv6_Hosts_head*, + const struct timeval*, + const u_int8_t[], + const sfip_t* +)__attribute__((malloc,nonnull)); //create_host_entry_ifnew +static void del_host_entry( + struct IPv6_Hosts_head*, + struct IPv6_Host* +)__attribute__((nonnull)); +static void del_dad_entry( + struct IPv6_Hosts_head*, + struct IPv6_Host* +)__attribute__((nonnull)); + +RB_PROTOTYPE_STATIC(MAC_Entry_data, MAC_Entry, entries, mac_cmp); +RB_PROTOTYPE_STATIC(IPv6_Hosts_data, IPv6_Host, entries, host_cmp); + +#endif /* DATASTRUCTS_H */ diff -uNr src/dynamic-preprocessors/ipv6//spp_ipv6.h src/dynamic-preprocessors/ipv6//spp_ipv6.h --- src/dynamic-preprocessors/ipv6//spp_ipv6.h 1970-01-01 01:00:00.000000000 +0100 +++ src/dynamic-preprocessors/ipv6//spp_ipv6.h 2012-05-12 20:19:37.365274267 +0200 @@ -0,0 +1,188 @@ +/* + * spp_ipv6.h + * + * Copyright (C) 2011 Martin Schuette <info@mschuette.name> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _SPP_IPV6_H +#define _SPP_IPV6_H + +/********************************************************************** + ** Includes ** + **********************************************************************/ +#include "sf_types.h" +#include <time.h> +#include <sys/time.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <sys/queue.h> + +#ifdef __linux__ +#ifndef __unused +#define __unused __attribute__((__unused__)) +#endif /* __unused */ +#include "tree.h" +#else /* BSD */ +#include <sys/tree.h> +#endif /* __linux__ */ + +#include "preprocids.h" +#include "sf_snort_packet.h" +#include "sf_dynamic_preproc_lib.h" +#include "sf_dynamic_preprocessor.h" +#include "snort_debug.h" +#include "sfPolicy.h" +#include "sfPolicyUserData.h" +/* for ICMPv6 format */ +#include <netinet/icmp6.h> +#include <netinet/ip6.h> +#include <netinet/in.h> + +/* verify string contains a MAC address */ +#define IS_MAC(string) ((string) != NULL \ + && isxdigit((string)[ 0]) && isxdigit((string)[ 1]) && (string)[ 2] == ':' \ + && isxdigit((string)[ 3]) && isxdigit((string)[ 4]) && (string)[ 5] == ':' \ + && isxdigit((string)[ 6]) && isxdigit((string)[ 7]) && (string)[ 8] == ':' \ + && isxdigit((string)[ 9]) && isxdigit((string)[10]) && (string)[11] == ':' \ + && isxdigit((string)[12]) && isxdigit((string)[13]) && (string)[14] == ':' \ + && isxdigit((string)[15]) && isxdigit((string)[16]) && (string)[17] == '\0') + + +/********************************************************************** + ** Structures/Data Types ** + **********************************************************************/ + +/* + * Some simple statistics. + * TODO: only for data exploration; to be removed later on + */ +struct IPv6_Statistics { + uint32_t pkt_seen; + uint32_t pkt_invalid; + uint32_t pkt_icmpv6; + uint32_t pkt_other; + + uint32_t pkt_fragments; + + uint32_t pkt_ip6h; + + uint32_t pkt_icmp_rsol; + uint32_t pkt_icmp_radv; + uint32_t pkt_icmp_nsol; + uint32_t pkt_icmp_nadv; + + uint32_t pkt_icmp_mlquery; + uint32_t pkt_icmp_mlreport; + uint32_t pkt_icmp_unreach; + uint32_t pkt_icmp_other; +}; + +/* + * configuration and plugin state. + */ +struct IPv6_Config { + u_int32_t keep_state_duration; // in sec + u_int32_t expire_run_interval; // in sec + u_int32_t max_routers; + u_int32_t max_hosts; + u_int32_t max_unconfirmed; + bool track_ndp; + bool report_prefix_change; + bool report_new_routers; + bool report_new_hosts; + struct MAC_Entry_head *router_whitelist; + struct MAC_Entry_head *host_whitelist; + struct IP_List_head *prefix_whitelist; +} __attribute__((packed)); + +struct IPv6_State { + struct IPv6_Hosts_head *routers; // known routers + struct IPv6_Hosts_head *hosts; // established hosts + struct IPv6_Hosts_head *unconfirmed; // ongoing duplicate detections/solicitations + struct IPv6_Statistics *stat; + struct IPv6_Config *config; + time_t next_expire; +} __attribute__((packed)); + +/* + * for Rule Options + */ +enum IPv6_RuleOpt_Type { + IPV6_RULETYPE_IPV, + IPV6_RULETYPE_IP6EXTHDR, + IPV6_RULETYPE_IP6EXTCOUNT, + IPV6_RULETYPE_FLOWLABEL, + IPV6_RULETYPE_TRAFFICCLASS, + IPV6_RULETYPE_OPTION, + IPV6_RULETYPE_OPTION_EXT, + IPV6_RULETYPE_OPTVAL, + IPV6_RULETYPE_ND, + IPV6_RULETYPE_ND_OPTION, + IPV6_RULETYPE_RH, + IPV6_RULETYPE_EXT_ORDERED +}; + +enum cmp_op { + check_eq=0, check_neq, + check_lt, check_gt, + check_and, check_xor, check_nand +}; + +struct IPv6_RuleOpt_Data { +#ifdef DEBUG + char *debugname; + char *debugparam; +#endif /* DEBUG */ + enum IPv6_RuleOpt_Type type:4; + enum cmp_op op:4; + union { + u_int32_t number; + struct { // for ip6_optval + u_int8_t ext_type; + u_int8_t opt_type; + u_int16_t opt_value; + } exthdr; + } opt; +} __attribute__((packed)); + +/********************************************************************** + ** Function Prototypes ** + **********************************************************************/ + +static void IPv6_Init(char *); +static void IPv6_Process(void *, void *); +static void IPv6_Process_ICMPv6(const SFSnortPacket *, struct IPv6_State *); +static void IPv6_Process_ICMPv6_RA(const SFSnortPacket *, struct IPv6_State *); +static void IPv6_Process_ICMPv6_RA_stateless(const SFSnortPacket *); +static void IPv6_Process_ICMPv6_NA(const SFSnortPacket *, struct IPv6_State *); +static void IPv6_Process_ICMPv6_NS(const SFSnortPacket *, struct IPv6_State *); +inline static void IPv6_UpdateStats(const SFSnortPacket *, struct IPv6_Statistics *); +inline static void IPv6_Process_ND_Options(const SFSnortPacket *, struct IPv6_State *); +inline static void IPv6_Process_Extensions(const SFSnortPacket *, struct IPv6_State *); +static void IPv6_PrintStats(int); +static void IPv6_ResetStats(int, void *); +static void IPv6_Parse(char *, struct IPv6_Config *); + +static int IPv6_Rule_Init(char *, char *, void **); +static int IPv6_Rule_Eval(void *, const u_int8_t **, void *); +static u_int32_t IPv6_Rule_Hash(void *); +static int IPv6_Rule_KeyCompare(void *, void *); + +#endif /* _SPP_IPV6_H */ diff -uNr src/dynamic-preprocessors/ipv6//spp_ipv6_parse.c src/dynamic-preprocessors/ipv6//spp_ipv6_parse.c --- src/dynamic-preprocessors/ipv6//spp_ipv6_parse.c 1970-01-01 01:00:00.000000000 +0100 +++ src/dynamic-preprocessors/ipv6//spp_ipv6_parse.c 2012-05-12 20:19:37.373268196 +0200 @@ -0,0 +1,124 @@ +/* + * spp_ipv6_parse.c + * + * Copyright (C) 2011 Martin Schuette <info@mschuette.name> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Description: + * Function to parse configuration options in snort.conf + * + */ + +#include "spp_ipv6.h" +#include <errno.h> + +/** + * Parse the configuration options in snort.conf + * + * Currently supported options: router_mac, host_mac, net_prefix + */ +void set_default_config(struct IPv6_Config *config) +{ + config->track_ndp = true; + // for testing: 1h, later: 2-12h + config->keep_state_duration = 60*60; + config->expire_run_interval = 20*60; + // not sure if these are realistic, should be high enough + config->max_routers = 32; + config->max_hosts = 8192; + config->max_unconfirmed = 32768; + + return; +} + +#define BIN_OPTION(X, Y) if (!strcasecmp(X, arg)) { \ + (Y) = false; \ + _dpd.logMsg(" " X "\n"); \ + arg = strtok(NULL, " \t\n\r"); \ + } + +inline void read_num(char **arg, const char *param, u_int32_t *configptr) +{ + uint_fast32_t minutes; + *arg = strtok(NULL, " \t\n\r"); + minutes = (uint_fast32_t) strtoul(*arg, NULL, 10); + if (errno) { + _dpd.fatalMsg(" Invalid parameter to %s\n", param); + } + *configptr = 60 * minutes; + _dpd.logMsg(" %s = %u minutes = %u secs\n", + param, minutes, *configptr); + *arg = strtok(NULL, " \t\n\r"); +} + +void IPv6_Parse(char *args, struct IPv6_Config *config) +{ + char *arg; + char ismac; + sfip_t *prefix; + SFIP_RET rc; + + set_default_config(config); + _dpd.logMsg("IPv6 preprocessor config:\n"); + if (!args) { + _dpd.logMsg("\tno additional parameters\n"); + return; + } + + arg = strtok(args, " \t\n\r"); + while (arg) { + if(!strcasecmp("router_mac", arg)) { // and now a list of 0-n router MACs + config->report_new_routers = true; + while ((arg = strtok(NULL, ", \t\n\r")) && (ismac = IS_MAC(arg))) { + mac_add(config->router_whitelist, arg); + _dpd.logMsg(" default router MAC %s\n", arg); + } + } else if(!strcasecmp("host_mac", arg)) { // and now a list of 0-n host MACs + config->report_new_hosts = true; + while ((arg = strtok(NULL, ", \t\n\r")) && (ismac = IS_MAC(arg))) { + mac_add(config->host_whitelist, arg); + _dpd.logMsg(" default host MAC %s\n", arg); + } + } else if(!strcasecmp("net_prefix", arg)) { // and now a list of 0-n prefixes + config->report_prefix_change = true; + while ((arg = strtok(NULL, ", \t\n\r")) && strchr(arg, '/')) { // TODO remove /-check + prefix = sfip_alloc(arg, &rc); + if (rc == SFIP_SUCCESS) { + add_ip(config->prefix_whitelist, prefix); + _dpd.logMsg(" default net prefix %s/%d\n", + sfip_to_str(prefix), sfip_bits(prefix)); + } else { + _dpd.fatalMsg(" Invalid prefix %s\n", arg); + } + } + } else if(!strcasecmp("max_routers", arg)) { + read_num(&arg, "max_routers", &(config->max_routers)); + } else if(!strcasecmp("max_hosts", arg)) { + read_num(&arg, "max_hosts", &(config->max_hosts)); + } else if(!strcasecmp("max_unconfirmed", arg)) { + read_num(&arg, "max_unconfirmed", &(config->max_unconfirmed)); + } else if(!strcasecmp("keep_state", arg)) { + read_num(&arg, "keep_state", &(config->keep_state_duration)); + } else if(!strcasecmp("expire_run", arg)) { + read_num(&arg, "expire_run", &(config->expire_run_interval)); + } else BIN_OPTION("disable_tracking", config->track_ndp) + else { + _dpd.fatalMsg("IPv6: Invalid option %s\n", arg); + } + } +} + diff -uNr src/dynamic-preprocessors/ipv6//spp_ipv6_ruleopt.c src/dynamic-preprocessors/ipv6//spp_ipv6_ruleopt.c --- src/dynamic-preprocessors/ipv6//spp_ipv6_ruleopt.c 1970-01-01 01:00:00.000000000 +0100 +++ src/dynamic-preprocessors/ipv6//spp_ipv6_ruleopt.c 2012-05-12 20:19:37.384579512 +0200 @@ -0,0 +1,551 @@ +/* + * spp_ipv6_ruleopt.c + * + * Copyright (C) 2011 Martin Schuette <info@mschuette.name> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Description: + * IPv6 Preprocessor functions related to rule option handling. + * + */ + +#include <errno.h> +#include "spp_ipv6.h" +#include "spp_ipv6_constants.h" +#include "spp_ipv6_data_structs.h" +// for rule option: +#include "sf_snort_plugin_api.h" +// for IPv6_Rule_Hash +#include "sfhashfcn.h" + +/* reads whitespace and optional comparison operator; + * advances the char* and returns the operator to use + */ +static enum cmp_op get_op(char **param_ptr) +{ + enum cmp_op op = check_eq; + if (!*param_ptr) return op; // if called w/o params + + while (isblank(**param_ptr)) (*param_ptr)++; + switch(*param_ptr[0]) { + case '=': op = check_eq; (*param_ptr)++; break; + case '!': op = check_neq; (*param_ptr)++; break; + case '<': op = check_lt; (*param_ptr)++; break; + case '>': op = check_gt; (*param_ptr)++; break; + case '&': op = check_and; (*param_ptr)++; break; + case '^': op = check_xor; (*param_ptr)++; break; + case '|': op = check_nand;(*param_ptr)++; break; + } + while (isblank(**param_ptr)) (*param_ptr)++; + return op; +} + +/* reads a decimal or hexadecimal number. + * FatalMessage on error + */ +static u_int32_t get_num(char **param_ptr, const char *name) +{ + char *endptr; + u_int32_t parameter; + if (*param_ptr) { + while (isblank(**param_ptr)) (*param_ptr)++; + if (isdigit(*param_ptr[0])) { + if ((*param_ptr)[0] == '0' && (*param_ptr)[1] == 'x') { + (*param_ptr)++; (*param_ptr)++; + parameter = (u_int32_t) _dpd.SnortStrtoul(*param_ptr, &endptr, 16); + } else + parameter = (u_int32_t) _dpd.SnortStrtoul(*param_ptr, &endptr, 10); + + if (!errno && endptr && (*endptr == '\0')) + return parameter; + } + } + DynamicPreprocessorFatalMessage("%s(%d) => keyword %s with invalid number %s\n", + *(_dpd.config_file), *(_dpd.config_line), name, (*param_ptr ? *param_ptr : "")); +} + +/* copied from src/detection-plugins/sf_snort_plugin_hdropts.c */ +inline static bool checkField(enum cmp_op op, uint_fast32_t value1, uint_fast32_t value2) +{ + switch (op) + { + case check_eq: + if (value1 == value2) + return true; + break; + case check_neq: + if (value1 != value2) + return true; + break; + case check_lt: + if (value1 < value2) + return true; + break; + case check_gt: + if (value1 > value2) + return true; + break; + case check_and: + if (value1 & value2) + return true; + break; + case check_xor: + if (value1 ^ value2) + return true; + break; + case check_nand: + if (!(value1 & value2)) + return true; + break; + } + + return false; +} + +/* Check order of extension headers */ +static bool IPv6_Check_Ext_Order(SFSnortPacket *p) +{ + u_int8_t order[] = {IPPROTO_HOPOPTS, IPPROTO_ROUTING, IPPROTO_FRAGMENT, + IPPROTO_AH, IPPROTO_ESP, IPPROTO_DSTOPTS, IPPROTO_MOBILITY}; + u_short size = sizeof(order)/sizeof(u_int8_t); + u_short i = 0, c = 0; + + while (i < p->num_ip6_extensions) { + if (c >= size) + return false; // i.e. option_type not found, not good + else if (order[c] == IPPROTO_ROUTING + && p->ip6_extensions[i].option_type == IPPROTO_DSTOPTS + && p->ip6_extensions[i+1].option_type == IPPROTO_ROUTING) { + // special case: a 2nd dst opt hdr may be in front of a routing hdr, + c++; i++; i++; + } else if (p->ip6_extensions[i].option_type == order[c]) { // option_type good + c++; i++; + } else { // order[c] not present, check next + c++; + } + } + return true; +} + +/* Parsing for rule options */ +static int IPv6_Rule_Init(char *name, char *params, void **data) +{ + struct IPv6_RuleOpt_Data *sdata; + enum IPv6_RuleOpt_Type ruletype; +#ifdef DEBUG + char *orig_params = params ? strdup(params) : "" ; // to preserve full option in **data + char *orig_name = strdup(name); // to preserve full name in **data +#endif /* DEBUG */ + + DEBUG_WRAP(DebugMessage(DEBUG_RULES, "IPv6_rule_init(\"%s\", \"%s\", ...)\n", + orig_name, orig_params);); + + if (!strcmp(name, "ipv")) + ruletype = IPV6_RULETYPE_IPV; + else if (!strcmp(name, "ip6_tclass")) + ruletype = IPV6_RULETYPE_TRAFFICCLASS; + else if (!strcmp(name, "ip6_flow")) + ruletype = IPV6_RULETYPE_FLOWLABEL; + else if (!strcmp(name, "ip6_exthdr")) + ruletype = IPV6_RULETYPE_IP6EXTHDR; + else if (!strcmp(name, "ip6_extnum")) + ruletype = IPV6_RULETYPE_IP6EXTCOUNT; + else if (!strcmp(name, "ip6_option")) + ruletype = IPV6_RULETYPE_OPTION; + else if (!strcmp(name, "ip6_optval")) + ruletype = IPV6_RULETYPE_OPTVAL; + else if (!strcmp(name, "ip6_rh")) + ruletype = IPV6_RULETYPE_RH; + else if (!strcmp(name, "ip6_ext_ordered")) + ruletype = IPV6_RULETYPE_EXT_ORDERED; + else if (!strcmp(name, "icmp6_nd")) + ruletype = IPV6_RULETYPE_ND; + else if (!strcmp(name, "icmp6_nd_option")) + ruletype = IPV6_RULETYPE_ND_OPTION; + else + DynamicPreprocessorFatalMessage("%s(%d) => invalid keyword %s\n", + *(_dpd.config_file), *(_dpd.config_line), name); + + sdata = (struct IPv6_RuleOpt_Data *)calloc(1, sizeof(*sdata)); + if (sdata == NULL) + DynamicPreprocessorFatalMessage("Could not allocate memory for the " + "%s preprocessor rule option.\n", name); + sdata->type = ruletype; +#ifdef DEBUG + sdata->debugparam = strdup(orig_params); + sdata->debugname = strdup(orig_name); +#endif /* DEBUG */ + + if (!params + && ruletype != IPV6_RULETYPE_ND + && ruletype != IPV6_RULETYPE_EXT_ORDERED) { + DynamicPreprocessorFatalMessage("%s(%d) => keyword %s requires parameter\n", + *(_dpd.config_file), *(_dpd.config_line), name); + } + + switch(ruletype) { + case IPV6_RULETYPE_EXT_ORDERED: /* fallthrough */ + case IPV6_RULETYPE_ND: + sdata->op = get_op(¶ms); + // no numeric params, just ignore them if given + break; + case IPV6_RULETYPE_IPV: /* fallthrough */ + case IPV6_RULETYPE_IP6EXTCOUNT: /* fallthrough */ + case IPV6_RULETYPE_IP6EXTHDR: /* fallthrough */ + case IPV6_RULETYPE_FLOWLABEL: /* fallthrough */ + case IPV6_RULETYPE_TRAFFICCLASS: /* fallthrough */ + case IPV6_RULETYPE_ND_OPTION: /* fallthrough */ + case IPV6_RULETYPE_RH: + sdata->op = get_op(¶ms); + sdata->opt.number = get_num(¶ms, name); + break; + case IPV6_RULETYPE_OPTION: { + /* NB: the ip6_optval/IPV6_RULETYPE_OPTVAL was + * split off to clarify the user documentation. + * + * Internally the three syntax variants are mapped + * to three ruletypes, but the functional difference + * is small and they share most code in IPv6_Rule_Eval(). + */ + /* syntax: + * <op>Y --> normal, test options for <op>Y + * <op>X.Y --> test for <op>Y in ext./nd. type X + */ + char *split; + sdata->op = get_op(¶ms); // <op> + split = strchr(params, '.'); + if (!split) { + sdata->opt.exthdr.ext_type = 0; + sdata->opt.exthdr.opt_type = get_num(¶ms, name); // Y + } else { + sdata->type = IPV6_RULETYPE_OPTION_EXT; + *split++ = '\0'; + sdata->opt.exthdr.ext_type = get_num(¶ms, name); // X + sdata->opt.exthdr.opt_type = get_num(&split, name); // Y + } + break; + } + case IPV6_RULETYPE_OPTVAL: { + /* syntax: X.Y<op>Z --> test for value Z<op>Y in ext. type X */ + char *split; + char *op, *orig_op; + u_int32_t tmp; + + split = strchr(params, '.'); + *split++ = '\0'; + op = strpbrk(split, " =!<>^&"); + + sdata->opt.exthdr.ext_type = get_num(¶ms, name); // X + // have to remember the position for \0: + orig_op = op; + sdata->op = get_op(&op); // <op> + *orig_op = '\0'; + sdata->opt.exthdr.opt_type = get_num(&split, name); // Y + + // handle possibly larger Z values: + tmp = get_num(&op, name); + if (tmp > UINT16_MAX) { + free(sdata); + DynamicPreprocessorFatalMessage( + "%s(%d) => keyword %s only supports 16-bit values\n", + *(_dpd.config_file), *(_dpd.config_line), name, params); + } + sdata->opt.exthdr.opt_value = (u_int16_t) tmp; // Z + break; + } + default: + free(sdata); + DynamicPreprocessorFatalMessage("%s(%d) => invalid ruletype %s\n", + *(_dpd.config_file), *(_dpd.config_line), name); + } + + /* verify given modifiers */ + switch(ruletype) { + case IPV6_RULETYPE_IP6EXTHDR: /* fallthrough */ + case IPV6_RULETYPE_EXT_ORDERED: /* fallthrough */ + case IPV6_RULETYPE_RH: /* fallthrough */ + case IPV6_RULETYPE_ND: + if (sdata->op != check_eq && sdata->op != check_neq) + DynamicPreprocessorFatalMessage("%s(%d) => keyword %s " + "only allows the operators '=' or '!'\n", + *(_dpd.config_file), *(_dpd.config_line), name); + break; + case IPV6_RULETYPE_IP6EXTCOUNT: + if (sdata->op != check_eq + && sdata->op != check_neq + && sdata->op != check_gt + && sdata->op != check_lt) + DynamicPreprocessorFatalMessage("%s(%d) => keyword %s " + "only allows the operators '=', '!', '<', or '>'\n", + *(_dpd.config_file), *(_dpd.config_line), name); + break; + default: // all modifiers OK + break; + } + + *data = (void *)sdata; + DEBUG_WRAP(DebugMessage(DEBUG_RULES, " --> IPv6_rule_init returns data 0x%x\n", *data);); + return 1; +} + +static u_int32_t IPv6_Rule_Hash(void *d) +{ + u_int32_t a,b,c; + struct IPv6_RuleOpt_Data *sdata = (struct IPv6_RuleOpt_Data *)d; + + a = sdata->type; + b = sdata->opt.number; + c = sdata->op & PP_IPv6; + + final(a,b,c); + return c; +} + +static int IPv6_Rule_KeyCompare(void *l, void *r) +{ + struct IPv6_RuleOpt_Data *left = (struct IPv6_RuleOpt_Data *)l; + struct IPv6_RuleOpt_Data *right = (struct IPv6_RuleOpt_Data *)r; + + if (left && right + && left->type == right->type + && left->op == right->op + && left->opt.number == right->opt.number) { + DEBUG_WRAP(DebugMessage(DEBUG_RULES, "IPv6_Rule_KeyCompare(%x, %x) --> equal\n", l, r);); + return PREPROC_OPT_EQUAL; + } + + DEBUG_WRAP(DebugMessage(DEBUG_RULES, "IPv6_Rule_KeyCompare(%x, %x) --> not equal\n", l, r);); + return PREPROC_OPT_NOT_EQUAL; +} + +#define RETURN_MATCH do { \ + DEBUG_WRAP(DebugMessage(DEBUG_RULES, \ + "IPv6_rule_eval on raw_packet at 0x%x\n\twith option %s: %s " \ + "(internally: option %u, op %u, value 0x%04x)\n\tmatches\n", \ + raw_packet, sdata->debugname, sdata->debugparam, \ + sdata->type, sdata->op, sdata->opt.number);); \ + return RULE_MATCH; \ +} while (0) +#define RETURN_NOMATCH do { \ + DEBUG_WRAP(DebugMessage(DEBUG_RULES, \ + "IPv6_rule_eval on raw_packet at 0x%x\n\twith option %s: %s " \ + "(internally: option %u, op %u, value 0x%04x)\n\tdoes not match\n", \ + raw_packet, sdata->debugname, sdata->debugparam, \ + sdata->type, sdata->op, sdata->opt.number);); \ + return RULE_NOMATCH; \ +} while (0) + + +/* Rule option evaluation */ +static int IPv6_Rule_Eval(void *raw_packet, const u_int8_t **cursor, void *data) +{ + SFSnortPacket *p = (SFSnortPacket*) raw_packet; + struct IPv6_RuleOpt_Data *sdata = (struct IPv6_RuleOpt_Data *) data; + DEBUG_WRAP(DebugMessage(DEBUG_RULES, "IPv6_Rule_Eval()\n");); + + if (!p || !sdata) { + _dpd.errMsg("Error in IPv6_Rule_Eval(): missing packet or option data\n"); + return RULE_NOMATCH; + } + // small optimization: except ipv all rules match IPv6 only + if (!p->ip6h && (sdata->type != IPV6_RULETYPE_IPV)) { + return RULE_NOMATCH; + } + + switch (sdata->type) { + case IPV6_RULETYPE_IPV: { + uint_fast8_t ipv = GET_IPH_VER(p); + if (ipv != 6 && ipv != 4) { + _dpd.errMsg("buggy Snort version: ip6_ret_ver() uses wrong byte order\n"); + ipv = ntohl(p->ip6h->vcl) >> 28; + } + + if (checkField(sdata->op, ipv, sdata->opt.number)) + RETURN_MATCH; + else + RETURN_NOMATCH; + } + case IPV6_RULETYPE_IP6EXTCOUNT: + if (checkField(sdata->op, p->num_ip6_extensions, sdata->opt.number)) + RETURN_MATCH; + else + RETURN_NOMATCH; + case IPV6_RULETYPE_FLOWLABEL: + if (checkField(sdata->op, (ntohl(p->ip6h->vcl) & 0x000fffff), sdata->opt.number)) + RETURN_MATCH; + else + RETURN_NOMATCH; + case IPV6_RULETYPE_TRAFFICCLASS: { + uint_fast8_t tos; + tos = (ntohl(p->ip6h->vcl) & 0x0ff00000) >> 20; + if (checkField(sdata->op, tos, sdata->opt.number)) + RETURN_MATCH; + else + RETURN_NOMATCH; + } + case IPV6_RULETYPE_EXT_ORDERED: { + bool rc = IPv6_Check_Ext_Order(p); + if ((rc && sdata->op == check_eq) + || (!rc && sdata->op == check_neq)) + RETURN_MATCH; + else + RETURN_NOMATCH; + } + case IPV6_RULETYPE_IP6EXTHDR: { + uint_fast8_t i; + + for (i = 0; i < p->num_ip6_extensions; i++) + if (p->ip6_extensions[i].option_type == sdata->opt.number) { + if (sdata->op == check_eq) RETURN_MATCH; + if (sdata->op == check_neq) RETURN_NOMATCH; + } + if (sdata->op == check_neq) RETURN_MATCH; + break; + } + case IPV6_RULETYPE_RH: { + uint_fast8_t i; + + for (i = 0; i < p->num_ip6_extensions; i++) + if (p->ip6_extensions[i].option_type == IPPROTO_ROUTING) { + struct ip6_rthdr *rt_hdr = (struct ip6_rthdr *) + p->ip6_extensions[i].option_data; + if (rt_hdr->ip6r_type == sdata->opt.number) { + if (sdata->op == check_eq) RETURN_MATCH; + if (sdata->op == check_neq) RETURN_NOMATCH; + } + } + if (sdata->op == check_neq) RETURN_MATCH; + break; + } + case IPV6_RULETYPE_ND: + if (!p->icmp_header) { + // no ICMPv6 or a type that may not contain rule options + if (sdata->op == check_neq) RETURN_MATCH; + else RETURN_NOMATCH; + } + switch (p->icmp_header->type) { + case ICMP6_SOLICITATION: /* fallthrough */ + case ICMP6_ADVERTISEMENT: /* fallthrough */ + case ICMP6_N_SOLICITATION: /* fallthrough */ + case ICMP6_N_ADVERTISEMENT: /* fallthrough */ + case ICMP6_REDIRECT: /* fallthrough */ + case ICMP6_INV_SOLICITATION: /* fallthrough */ + case ICMP6_INV_ADVERTISEMENT: /* fallthrough */ + case ICMP6_HOME_AD_REQUEST: /* fallthrough */ + case ICMP6_HOME_AD_REPLY: /* fallthrough */ + case ICMP6_MOBILEPREFIX_SOL: /* fallthrough */ + case ICMP6_MOBILEPREFIX_ADV: /* fallthrough */ + case ICMP6_CRT_SOLICITATION: /* fallthrough */ + case ICMP6_CRT_ADVERTISEMENT: /* fallthrough */ + case ICMP6_MOBILE_FH: + if (sdata->op == check_eq) + RETURN_MATCH; + else + RETURN_NOMATCH; + default: + if (sdata->op == check_neq) + RETURN_MATCH; + else + RETURN_NOMATCH; + } + case IPV6_RULETYPE_ND_OPTION: { + if (!p->icmp_header || !ND_hdrlen[p->icmp_header->type]) { + // no ICMPv6 or a type that may not contain rule options + if (sdata->op == check_neq) RETURN_MATCH; + else RETURN_NOMATCH; + } else { + uint_fast8_t icmp_hdr_len = ND_hdrlen[p->icmp_header->type]; + uint_fast16_t len = p->ip_payload_size - icmp_hdr_len; + struct nd_opt_hdr *option = (struct nd_opt_hdr *) (p->ip_payload + icmp_hdr_len); + while (len) { + uint_fast8_t optlen = 8 * (option->nd_opt_len); + bool rc = checkField(sdata->op, option->nd_opt_type, sdata->opt.number); + if ( rc && sdata->op != check_neq) RETURN_MATCH; + if (!rc && sdata->op == check_neq) RETURN_NOMATCH; + if (optlen > len) { + _dpd.logMsg("IPv6 decoder problem. malformed ND option lenghts.\n"); + break; + } + len -= optlen; + option = (struct nd_opt_hdr *) ((char *)option + optlen); + } + if (sdata->op == check_neq) RETURN_MATCH; + else RETURN_NOMATCH; + } + } + case IPV6_RULETYPE_OPTVAL: /* fallthrough */ + case IPV6_RULETYPE_OPTION: /* fallthrough */ + case IPV6_RULETYPE_OPTION_EXT: { + uint_fast8_t i; + + for (i = 0; i < p->num_ip6_extensions; i++) { + if (p->ip6_extensions[i].option_type != IPPROTO_HOPOPTS + && p->ip6_extensions[i].option_type != IPPROTO_DSTOPTS) + continue; + if ((sdata->type == IPV6_RULETYPE_OPTION_EXT + || sdata->type == IPV6_RULETYPE_OPTVAL) + && p->ip6_extensions[i].option_type != sdata->opt.exthdr.ext_type) + continue; + + { + /* hbh_hdr is the Ext Hdr with type and length + * hbh_hdr+1 is the first option with type and length + * cursor iterates over the options + */ + struct ip6_hbh *hbh_hdr = (struct ip6_hbh*) p->ip6_extensions[i].option_data; + u_int16_t ext_len = (hbh_hdr->ip6h_len + 1) << 3; + u_int8_t *cursor = (u_int8_t *) (hbh_hdr+1); + u_int8_t *ext_end = ((u_int8_t *) hbh_hdr) + ext_len; + + while (cursor < ext_end) { + struct ip6_opt *opt = (struct ip6_opt*) cursor; + + DEBUG_WRAP(DebugMessage(DEBUG_RULES, + "checkOpt(type %u, len %u at 0x%04x)\n", + opt->ip6o_type, opt->ip6o_len, opt);); + + // NB: ext_type already checked in outer loop + if (sdata->type == IPV6_RULETYPE_OPTION + || sdata->type == IPV6_RULETYPE_OPTION_EXT) { + bool rc = checkField(sdata->op, opt->ip6o_type, sdata->opt.exthdr.opt_type); + DEBUG_WRAP(DebugMessage(DEBUG_RULES, + "checkField(%u, 0x%04x, 0x%04x) = %s\n", + sdata->op, opt->ip6o_type, sdata->opt.exthdr.opt_type, + (rc ? "match" : "fail"));); + if ( rc && sdata->op != check_neq) RETURN_MATCH; + if (!rc && sdata->op == check_neq) RETURN_NOMATCH; + } else if (sdata->type == IPV6_RULETYPE_OPTVAL + && opt->ip6o_type == sdata->opt.exthdr.opt_type) { + u_int16_t *ptr = (u_int16_t*) opt; + u_int16_t val = ntohs(*(ptr+1)); + bool rc = checkField(sdata->op, val, sdata->opt.exthdr.opt_value); + if ( rc && sdata->op != check_neq) RETURN_MATCH; + if (!rc && sdata->op == check_neq) RETURN_NOMATCH; + } + + cursor += (opt->ip6o_type == 0) ? 1 // Pad1 is special + : (2+opt->ip6o_len); + } + } + } + if (sdata->op == check_neq) RETURN_MATCH; + else RETURN_NOMATCH; + break; + } + } + RETURN_NOMATCH; +} --- configure.in.orig 2012-05-12 20:50:49.618568887 +0200 +++ configure.in 2012-05-12 20:51:12.457268063 +0200 @@ -1704,6 +1704,7 @@ src/dynamic-preprocessors/ssl/Makefile \ src/dynamic-preprocessors/modbus/Makefile \ src/dynamic-preprocessors/dnp3/Makefile \ +src/dynamic-preprocessors/ipv6/Makefile \ src/dynamic-preprocessors/rzb_saac/Makefile \ src/output-plugins/Makefile \ src/preprocessors/Makefile \