[Click] Element for inclusion...

Nicholas Weaver nweaver at ICSI.Berkeley.EDU
Thu Sep 28 13:14:59 EDT 2006


	Attached is a new element (MapTRW) which implements the TRW
algorithm for scan detection & blocking, and associated helpers for
the element.

	This can efficiently track and block all scanners going into a
place like LBL, while using a small amonut of memory (a few megabytes).


	It might be worth considering adding a new elements directory
(security) for NIDS related elements should someone want to use this
and to create other elements for this.

-- 
Nicholas C. Weaver                               nweaver at icsi.berkeley.edu
     This message has been ROT-13 encrypted twice for higher security.
-------------- next part --------------
// -*- c-basic-offset: 4 -*-
/*
 * map_trw.{cc,hh} -- An implementation of Usenix Security approximate TRW
 * Nicholas Weaver
 *
 * This element uses two inputs and two (or four) outputs.  It passively
 * maps the local network to determine which side a host is on, and
 * uses approximate TRW to track hosts and scanners.
 *
 *
 */

// This file is copyright 2005/2006 by the International Computer
// Science Institute.  It can be used for any purposes
// (Berkeley-liscence) as long as this notice remains intact.

// THERE IS NO WARANTEE ON THIS CODE!



#include <click/config.h>
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/straccum.hh>
#include <clicknet/icmp.h>
#include <clicknet/tcp.h>
#include <clicknet/udp.h>
#include "map_trw.hh"
#include "rc5.hh"
#include "trw_packet_utils.hh"
#include <clicknet/ether.h>
#include <click/etheraddress.hh>

#define FIND_SRC 0
#define FIND_DST 1


// This is for the map of the LOCAL area network.
struct map_record {

    // The IP address for this MAP record.
    IPAddress map_ip;

    // The ethernet discovered for this IP.
    // Not currently used (except for debugging)
    // but no reason not to record this.
    EtherAddress map_eth;

    // The last time this was checked as valid (seconds)
    // O is invalid.  > age is ignored.  Age - 60 is always
    // remapped.  This way, the difference between passive and
    // active can be recorded.
    unsigned last_valid;

    // which port the system is on
    int port;

    // Was this updated passively?
    bool passive_update;

    // Should this system be whitelisted from ARP scanning (the
    // gateway)
    bool arp_whitelist;

};

struct ip_record {
    uint16_t ip_tag;    // Uses the Usenix Security tag/value trick
    int8_t count;       // Count can only go +127/-127, but the floor
                        // is less anyway.  
                        // -128 has a special meaning: the 
                        // entry is invalid and not yet initialized.
    int8_t passive_count; // A count for when passive mapping only
                          // is used.

    uint8_t timestamp;  // Rather than the usenix security
                              // technique of scrubbing on a fixed
                              // timeschedule, instead each entry
                              // has a timestamp associated with it
                              // on a 1 minute granularity.
                              // 
                              // This does introduce a SLIGHT
                              // error, if an IP is idle for >255 minutes
                              // (~4 hours), when reexamined it will
                              // be as if the system was idle for
                              // only mod 256 minutes.
                              //
                              // In return, this saves a whopping 
                              // 50% in memory usage assuming structs
                              // are compiled to word aligned!
                              //
                              // I may add an incremental scrubber later
                              // which every 10 minutes does a scrub & update
};

// Also, unlike the Usenix paper, a common table is used for addresses on
// either side of the filter.  

// Rather, each record is looked up for "SRC" or "DST"
struct con_record {
    uint8_t status;   // Status bits & meaning
                      // Bit 0: Estabished out & allowed
                      // Bit 1: Response established & allowed
                      // Bit 2: Blocked but attempted.
    uint8_t timestamp;
};




CLICK_DECLS

MapTRW::MapTRW()
{
    // MOD_INC_USE_COUNT;
}

MapTRW::~MapTRW()
{
    
    // MOD_DEC_USE_COUNT;
}


void 
MapTRW::handle_arp(int port, Packet *p){
    click_ether *e = (click_ether *) p->data();
    click_ether_arp *ea = (click_ether_arp *) (e + 1);
    unsigned int tpa, spa;
    memcpy(&tpa, ea->arp_tpa, 4);
    memcpy(&spa, ea->arp_spa, 4);
    IPAddress dst = IPAddress(tpa);
    IPAddress src = IPAddress(spa);
    passive_update_map_arp(port,p);


    // Ignore ARPs two/from the gateway system.
    // Currently assumed to be .1 on the subnet
    // (but should change to specify gateway systems)
    if( (ntohl(src) ^ ntohl(_my_ip)) < map_size &&
	(ntohl(src) & ~ntohl(_my_mask)) == 1){

    } else if( (ntohl(dst) ^ ntohl(_my_ip)) < map_size &&
	       (ntohl(dst) & ~ntohl(_my_mask)) == 1){

    } else if (p->length() >= sizeof(*e) + sizeof(click_ether_arp) &&
	       ntohs(e->ether_type) == ETHERTYPE_ARP &&
	       ntohs(ea->ea_hdr.ar_hrd) == ARPHRD_ETHER &&
	       ntohs(ea->ea_hdr.ar_pro) == ETHERTYPE_IP &&
	       ntohs(ea->ea_hdr.ar_op) == ARPOP_REQUEST) {



	if(supress_broadcast(port,p)){
	    output(port).push(p);
	    return;
	}

	uint32_t src_hash = rc5_encrypt((uint32_t) src, rc5_key);
	uint32_t dst_hash = rc5_encrypt((uint32_t) dst, rc5_key);
	struct ip_record *src_record = find_ip(src_hash);
	struct con_record *src_con = find_con(NULL,src_hash,
					      dst_hash,FIND_SRC);

	if(src_con->status & 0x1){
	    // arp already seen, ignoring.
	} else if(src_record->count >= ip_table_block_count) {
	    // Policy is IF over count, ALL arps are killed
	    if(src_con->status & 0x4){
		// already seen it.  just drop and quit
	    } else {
		// record this attempt and kill
		src_con->status = src_con->status | 0x4;
		src_record->count += 1;
	    }
	    click_chatter("Dropping ARP scan attempt\n");
	    if(noutputs() == 4){
		output(port + 2).push(p);
	    } else {
		p->kill();
	    }
	    return;
	} else {
	    src_con->status = src_con->status | 1;
	    src_record->count += 1;
	}
    } else if(p->length() >= sizeof(*e) + sizeof(click_ether_arp) &&
	      ntohs(e->ether_type) == ETHERTYPE_ARP &&
	      ntohs(ea->ea_hdr.ar_hrd) == ARPHRD_ETHER &&
	      ntohs(ea->ea_hdr.ar_pro) == ETHERTYPE_IP &&
	      ntohs(ea->ea_hdr.ar_op) == ARPOP_REPLY) {
	uint32_t src_hash = rc5_encrypt((uint32_t) src, rc5_key);
	uint32_t dst_hash = rc5_encrypt((uint32_t) dst, rc5_key);
	struct ip_record *dst_record = find_ip(dst_hash);
	struct con_record *dst_con = find_con(NULL,src_hash,
					      dst_hash,FIND_DST);
	if(dst_con->status & 0x2){

	} else {
	    dst_con->status = dst_con->status | 0x2;
	    dst_record->count = dst_record->count - 1;
	}
    }

    else {
	click_chatter("Ignoring non request/response ARP packet");
    }


    output(port).push(p);
}

void 
MapTRW::update_map(){
    // Currently doing passive-only updating.
}

void 
MapTRW::passive_update_map_ip(int port, Packet *p){
    click_ether *e = (click_ether *) p->data();
    const click_ip *iph = p->ip_header();
    const Timestamp ts = p->timestamp_anno();
    IPAddress src(iph->ip_src.s_addr);
    EtherAddress shost(e->ether_shost);

    if( (ntohl(src) ^ ntohl(_my_ip)) < map_size){
	int index = (ntohl(src) & ~ntohl(_my_mask));
	if(arp_map[index].port != port ||
	   arp_map[index].map_eth != shost){
	    StringAccum sa;
	    sa << "WARNING!  Host " << src << " / "
	       << shost << " has moved or changed identity" << '\0';
	    if(arp_map[index].last_valid != 0) 
		click_chatter("%s", sa.data());
	    arp_map[index].port = port;
	    arp_map[index].map_ip = src;
	    arp_map[index].map_eth = shost;
	}
	arp_map[index].last_valid = (unsigned) ts.sec();
    } else {

    }

}

void 
MapTRW::passive_update_map_arp(int port, Packet *p){
    click_ether *e = (click_ether *) p->data();
    click_ether_arp *ea = (click_ether_arp *) (e + 1);
    const Timestamp ts = p->timestamp_anno();
    unsigned int spa;
    memcpy(&spa, ea->arp_spa, 4);
    IPAddress src = IPAddress(spa);
    EtherAddress shost(e->ether_shost);
    if( (ntohl(src) ^ ntohl(_my_ip)) < map_size){
	int index = (ntohl(src) & ~ntohl(_my_mask));
	if(arp_map[index].port != port ||
	   arp_map[index].map_eth != shost){
	    StringAccum sa;
	    sa << "WARNING!  Host " << src << " / "
	       << shost << " has moved or changed identity" << '\0';
	    if(arp_map[index].last_valid != 0) 
		click_chatter("%s", sa.data());
	    arp_map[index].port = port;
	    arp_map[index].map_ip = src;
	    arp_map[index].map_eth = shost;
	}
	arp_map[index].last_valid = (unsigned) ts.sec();
    } else {

    }
}



// The rules for supressing broadcast...

// IF packet is broadcast, don't supress.

// IF packet is destined for the local LAN, supress analysis
// IF destination system is on the same side as src system.

// IF packet is NOT destined for the local LAN, supress analysis
// IF src is on the same side as the gateway (assumed to be 
// the [1] index.
bool
MapTRW::supress_broadcast(int port, Packet *p){
    click_ether *e = (click_ether *) p->data();
    click_ether_arp *ea = (click_ether_arp *) (e + 1);
    const click_ip *iph = p->ip_header();
    IPAddress dst;
    if (p->length() >= sizeof(*e) + sizeof(click_ether_arp) &&
        ntohs(e->ether_type) == ETHERTYPE_ARP &&
        ntohs(ea->ea_hdr.ar_hrd) == ARPHRD_ETHER &&
        ntohs(ea->ea_hdr.ar_pro) == ETHERTYPE_IP){
	click_ether_arp *ea = (click_ether_arp *) (e + 1);
	unsigned int tpa;
	memcpy(&tpa, ea->arp_tpa, 4);
	dst = IPAddress(tpa);
    } else {
	dst = IPAddress(iph->ip_dst.s_addr);
    }
    if( (ntohl(dst) ^ ntohl(_my_ip)) < map_size){
	unsigned index = (ntohl(dst) & ~ntohl(_my_mask));
	// Don't supress broadcasts from analysis.
	if(index == (map_size - 1)) return false;

	if(arp_map[index].last_valid != 0 &&
	   port == arp_map[index].port){
	    return true;
	}
	return false;
    } else {
	if(arp_map[1].last_valid != 0 &&
	   port == arp_map[1].port){
	    return true;
	}
	return false;
    }
}


void 
MapTRW::push(int port, Packet *p)
{
    const click_ip *iph = p->ip_header();
    const Timestamp ts = p->timestamp_anno();
    click_ether *e = (click_ether *) p->data();
    click_ether_arp *ea = (click_ether_arp *) (e + 1);
    if (last_time == 0 || last_time < (((unsigned) ts.sec()) / 60)){
	last_time = (((unsigned) ts.sec()) / 60);
    }

    // For if the map is updated actively.
    if (last_map == 0 || last_map + 60 < ((unsigned) ts.sec())){
	update_map();	
	last_map = ts.sec();
    }

    if (p->length() >= sizeof(*e) + sizeof(click_ether_arp) &&
        ntohs(e->ether_type) == ETHERTYPE_ARP &&
        ntohs(ea->ea_hdr.ar_hrd) == ARPHRD_ETHER &&
        ntohs(ea->ea_hdr.ar_pro) == ETHERTYPE_IP){
	handle_arp(port, p);
	return;
    }

    if (!iph) {
	click_chatter("Not an IP packet.  Dropping\n");
        if(noutputs() == 4){
            output(port + 2).push(p);
        } else {
            p->kill();
        }
        return;
    }

    // Update the passive network map.
    passive_update_map_ip(port, p);
    IPAddress src(iph->ip_src.s_addr);
    IPAddress dst(iph->ip_dst.s_addr);

    if(supress_broadcast(port, p)){
	StringAccum sa;
	sa << "Ignored broadcast from " << src << " to " << dst
	   << " from port " << port << '\0';
	click_chatter("%s", sa.data());
	output(port).push(p);
	return;
    }



    uint32_t src_hash = rc5_encrypt((uint32_t) src, rc5_key);
    uint32_t dst_hash = rc5_encrypt((uint32_t) dst, rc5_key);
    struct ip_record *src_record = find_ip(src_hash);
    struct ip_record *dst_record = find_ip(dst_hash);

    struct con_record *src_con = find_con(p,src_hash,dst_hash,FIND_SRC);
    struct con_record *dst_con = find_con(p,src_hash,dst_hash,FIND_DST);

    bool drop = false;
    // Already allowed packet in this direction
    if(src_con->status & 0x1){
	// However, we don't allow it if its a TCP SYN or UDP
	// if its over the count.
	if( src_record->count >= ip_table_block_count
	    && block_policy(p)){
	    drop = true;
	} 
	else if(dst_con->status & 0x1){
	    // dst already established as well.
	    if(!(dst_con->status & 0x2)){
		dst_con->status = dst_con->status & 0x2;
	    } // just in case of collisions & also multiple
	      // connections
	} else {
	    if(!(dst_con->status & 0x2)){
		dst_con->status = dst_con->status & 0x2;
	    }
	    // Do nothing.  Just pass the packet, another allowed send
	}
    } else {
	if(dst_con->status & 0x1){
	    if(valid_ack(p)){
		// This is an ack packet.  So lower DST's count
		// DST's count only gets recorded if DST hasn't
		// had it ACKEd before
		if(!(dst_con->status & 0x2)){
		    // Need to check the 0x2 flag, because
		    // we don't want to double count acks from
		    // different connections to the same port
		    dst_record->count = dst_record->count - 2;
		    if(dst_record->count < ip_table_min_count){
			dst_record->count = ip_table_min_count;
		    }
		    if(dst_record->count < ip_table_block_count &&
		       !(dst_record->count + 2 < ip_table_block_count)){
			StringAccum sa;
			sa << dst << '\0';
			click_chatter("Now Unblocking IP %s (count)",
				      sa.data());
		    }
		    dst_record->timestamp = (uint8_t) last_time;

		    if(((uint32_t) dst) == 0x3aba96c0 && tomato_chatter) 
			click_chatter("Count decreased to %i for %x",
				      dst_record->count,
				      (uint32_t) dst);
		}
		src_con->status = src_con->status | 0x1;
		dst_con->status = dst_con->status | 0x2;

	    } // Otherwise just pass it as a nonack with no
	      // change in any status
	} else {
	    if(dst_con->status & 0x4){ 
		// Reply to something "dropped" but not dropped
		// due to outline testing.
	    }
	    // This is a NEW connection.
	    else if(src_record->count >= ip_table_block_count){
		// IP already being blocked.
		drop = true;
		if(!(src_con->status & 0x4)){
		    // Not a new attempt, so count it as another failure
		    src_record->count = src_record->count + 1;
		    if(src_record->count > ip_table_max_count){
			src_record->count = ip_table_max_count;
		    }
		    src_record->timestamp = (uint8_t) last_time;
		    src_con->status = src_con->status | 0x4;
		    if(((uint32_t) src) == 0x3aba96c0 && tomato_chatter) 
			click_chatter("Count increased to %i for %x",
				      src_record->count,
				      (uint32_t) src);
		}

	    } else {
		// IP not being blocked, so this is OK, but INCR the count
		src_record->count = src_record->count + 1;
		if(src_record->count == ip_table_block_count){
		    StringAccum sa;
		    sa << src << '\0';
		    click_chatter("Now Blocking IP %s\n",
				  sa.data());
		}
		if(src_record->count > ip_table_max_count){
		    src_record->count = ip_table_max_count;
		}
		src_record->timestamp = (uint8_t) last_time;
		src_con->status = src_con->status | 0x1;
		dst_con->status = dst_con->status | 0x2;
		if(((uint32_t) src) == 0x3aba96c0 && tomato_chatter) 
		    click_chatter("Count increased to %i for %x",
				  src_record->count,
				  (uint32_t) src);
			      
	    }
	}
    }

    if(drop){
	click_chatter("Dropping packet");
	if(noutputs() == 4){
	    output(port + 2).push(p);
	} else{
	    p->kill();
	}
    } else {
	output(port).push(p);
    }
}

// looking up the connection record.  The port is ignored for
// UDP but specified for TCP.
struct con_record *MapTRW::find_con(Packet *p,
				       uint32_t src_hash, 
				       uint32_t dst_hash,
				       int direction){
    uint32_t proto_hash;
    if(p == NULL){
	proto_hash = rc5_encrypt(3,rc5_key);
    } else{
	const click_ip *iph = p->ip_header();
	
	if(iph->ip_p == IP_PROTO_TCP){
	    const click_tcp *tcph = p->tcp_header();
	    uint16_t srcp = ntohs(tcph->th_sport);
	    uint16_t dstp = ntohs(tcph->th_dport);
	    if(direction == FIND_SRC){
		// Note, SRCs are keyed by the DST port!
		proto_hash = rc5_encrypt((uint32_t) dstp, 
					 rc5_key);
	    } else {
		proto_hash = rc5_encrypt((uint32_t) srcp,
					 rc5_key);
	    }
	} else if(iph->ip_p == IP_PROTO_UDP){
	    proto_hash = rc5_encrypt(1,rc5_key);
	} else {
	    proto_hash = rc5_encrypt(2,rc5_key);
	}
    }
    int index;
    if(direction == FIND_SRC){
	index = ((src_hash << 2) ^
		 (src_hash >> 30) ^ 
		 dst_hash ^ proto_hash) % con_table_size;
    }
    else {
	index = (src_hash ^ 
		 (dst_hash << 2) ^
		 (dst_hash >> 30) ^ proto_hash) % con_table_size;
    }

    if( ((uint8_t) last_time) - con_table[index].timestamp 
	>= ((uint8_t) con_table_maxage)){  
	if(con_table[index].status) {
	    // click_chatter("Table aged.  Clearing status\n");
	}
	con_table[index].status = 0;
    }
    con_table[index].timestamp = (uint8_t) last_time;
    return &(con_table[index]);
}


// Performs the lookup for the IP in the ip table.  Note
// that because of the use of encrypted indexing for the lookup,
// host or byte order DOES NOT MATTER as long as it is consistant
// across all lookups.
struct ip_record *MapTRW::find_ip(uint32_t ip_encrypted){
    uint32_t ip_index = ip_encrypted & ip_addr_index_mask; 
    uint16_t ip_tag   = (uint16_t) (ip_encrypted >> ip_addr_tag_shift);
    int i;
    // uint32_t ip = rc5_decrypt(ip_encrypted,rc5_key);
    // click_chatter("IP is %8x, encrypted %8x, index %8x, tag %4x\n",
    // ip, ip_encrypted, ip_index, (uint32_t) ip_tag);
    for(i = 0; i < (int) ip_table_assoc; ++i){
	const int at_index = ip_index * ip_table_assoc + i;
	if(ip_table[at_index].ip_tag == ip_tag){
	    if(ip_table[at_index].count == -128){
		ip_table[at_index].count = 0;
		ip_table[at_index].timestamp = (uint8_t) last_time;
	    }
	    // click_chatter("Found IP %x", 
	    // rc5_decrypt(ip_encrypted, rc5_key));
	    if(ip_table[at_index].count < 0){
		if(((uint8_t) last_time) - ip_table[at_index].timestamp
		   > (uint8_t) ip_table_incr_age){
		    ip_table[at_index].timestamp += 
			ip_table_incr_age;
		    ip_table[at_index].count += 1;
		    // click_chatter("Incrementing count for aging\n");
		    // Cheat and handle multiple agings by doing
		    // a recursive call.
		    return find_ip(ip_encrypted);
		}
	    } else if(ip_table[at_index].count > 0){
		if(((uint8_t) last_time) - ip_table[at_index].timestamp
		   > (uint8_t) ip_table_decr_age){
		    ip_table[at_index].timestamp += 
			ip_table_incr_age;
		    ip_table[at_index].count += -1;
                    if(ip_table[at_index].count + 1 == ip_table_block_count){
                        click_chatter("Now Unblocking IP (age)");
		    }

		    // click_chatter("Decrementing count for aging\n");
		    // Cheat and handle multiple agings by doing
		    // a recursive call.
		    return find_ip(ip_encrypted);
		}
	    }
	    return &(ip_table[at_index]);
	}
    }
    for(i = 0; i < (int) ip_table_assoc; ++i){
	const int at_index = ip_index * ip_table_assoc + i;
	if(ip_table[at_index].count == -128){
	    ip_table[at_index].count = 0;
	    ip_table[at_index].ip_tag = ip_tag;
	    ip_table[at_index].timestamp = 
		(uint8_t) last_time;
	    // click_chatter("Allocated new IP %x", 
	    // rc5_decrypt(ip_encrypted, rc5_key));
	    return &(ip_table[at_index]);
	}
    }
    int min = 127;
    int min_index = 0;
    for(i = 0; i < (int) ip_table_assoc; ++i){
	if(ip_table[ip_index * ip_table_assoc + i].count < min){
	    min = ip_table[ip_index * ip_table_assoc + i].count;
	    min_index = i;
	}
    }
    int evict_index = ip_index * ip_table_assoc + min_index;
    //    click_chatter("Evicting entry for IP %x, count %i, index %i",
    // rc5_decrypt((((uint32_t) 
    // ip_table[evict_index].ip_tag) 
    // << ip_addr_tag_shift)
    // | ip_index, rc5_key),
    // (int) 
    // ip_table[evict_index].count,
    // min_index);
    ip_table[evict_index].count = 0;
    ip_table[evict_index].ip_tag = ip_tag;
    ip_table[evict_index].timestamp =
	(uint8_t) last_time;
    return &(ip_table[evict_index]);
}

int
MapTRW::configure(Vector<String> &conf, ErrorHandler *errh){
    // Setting defaults
    ip_table_size = 262144; // 2^18
    ip_table_assoc = 4;
    con_table_size = 262144; // 2^18
    last_time = 0;
    last_map = 0;
    con_table_maxage = 10; 
    // Default of 10 minutes to remove
    // idle connections

    ip_table_decr_age = 2;     // Every 2 minutes count can go down by 1 if >0
    ip_table_incr_age = 10;    // every 10 minutes count goes up by 1 if < 0
    ip_table_block_count = 10; // Block after 10 scans
    ip_table_max_count = 20;   // count shal not exceed
    ip_table_min_count = -20;  // both positive and negative
    tomato_chatter = false;

    rc5_seed = 0xCAFEBABE;
    click_chatter("Parsing Arguments\n");

    if( cp_va_parse(conf, this, errh,
		    cpIPAddress, "IP address", &_my_ip,
		    cpEthernetAddress, "Ethernet address", &_my_en,
		    cpIPAddress, "IP address", &_my_mask,

		    cpKeywords,

		    "TOMATO_CHATTER", cpBool,
		    "Whether to chatter Tomato", &tomato_chatter,

		    "IP_TABLE_SIZE", cpUnsigned,
		    "Number of entries in IP Table", &ip_table_size
		    ,
		    "IP_TABLE_ASSOC", cpUnsigned,
		    "Associativity of IP Cache", &ip_table_assoc,
		    
		    "IP_TABLE_MAX_COUNT", cpInteger,
		    "Maximum count for IP table entries", &ip_table_max_count,
		    
		    "IP_TABLE_MIN_COUNT", cpInteger,
		    "Minimum count for IP table entries", &ip_table_min_count,

		    "IP_TABLE_BLOCK_COUNT", cpInteger,
		    "Block IPs when count exceeds X", &ip_table_block_count,
		    
		    "IP_TABLE_DECR_AGE", cpUnsigned,
		    "Decrement positive entries every X minutes", 
		    &ip_table_decr_age,

		    "IP_TABLE_INCR_AGE", cpUnsigned,
		    "Increment negative entries every X minutes", 
		    &ip_table_incr_age,
		    
		    "CON_TABLE_SIZE", cpUnsigned,
		    "Size of the connection table", &con_table_size,
		    
		    "CON_TABLE_AGE", cpUnsigned,
		    "Connections are removed after X minutes of idleness",
		    &con_table_maxage, cpEnd
		    ) < 0
	
       ){
	click_chatter("Arguments Parse Failure\n");
	return -1;
    } 

    map_size = 1 << (32 - _my_mask.mask_to_prefix_len());
    arp_map = (struct map_record *) 
	malloc(sizeof(struct map_record) * map_size);

    {
	StringAccum sa;
	sa << _my_ip.unparse_with_mask(_my_mask) << " / " << _my_en 
	   << '\0';
	click_chatter("Arguments Parsed\n");
	click_chatter("My IP/MAC address/mask is %s\n", sa.data());
	click_chatter("# of elements in map is %i\n", map_size);
    }


    for(unsigned i = 0; i < map_size; ++i){
	arp_map[i].last_valid = 0;
	arp_map[i].port = 0;
	arp_map[i].passive_update = false;
	arp_map[i].map_ip = (uint32_t (_my_ip & _my_mask)) + htonl(i);
	if(i == 1){
	    arp_map[i].arp_whitelist = true;
	} else {
	    arp_map[i].arp_whitelist = false;
	}
    }


    if(ip_table_max_count <= 0 || ip_table_max_count > 120
       || ip_table_min_count >= 0 || ip_table_min_count < -120
       || ip_table_block_count > ip_table_max_count 
       || ip_table_block_count <= 0 
       || ip_table_incr_age <= 0 || ip_table_incr_age > 120
       || ip_table_decr_age <= 0 || ip_table_decr_age > 120
       || con_table_maxage <= 0 || con_table_maxage > 120){
	return errh->error("0 < block_count < max_count < 120\n" \
			   "-120 < min_count < 0\n" \
			   "0 < (any ageing) < 120\n");
			   
    }
       

    if(noutputs() != 2 && noutputs() != 4){
	return errh->error("There can only be 2 or 4 outputs for MapTRW");
    }
    if(ninputs() != 2){
	return errh->error("There can only be 2 inputs for MapTRW");
    }

    rc5_key = rc5_keygen(rc5_seed);

    click_chatter("RC5 key is %x\n", rc5_seed);
    click_chatter("RC5 encrypt of 0xFEEDFACE is %x\n", 
		  rc5_encrypt(0xFEEDFACE, rc5_key));
    click_chatter("RC5 D(E(x)) of 0xFEEDFACE is %x\n", 
		  rc5_decrypt(rc5_encrypt(0xFEEDFACE, rc5_key),
			      rc5_key));

    click_chatter("Allocating space for %i entry IP table: %i bytes\n",
		  ip_table_size, sizeof(struct ip_record) * ip_table_size);
    ip_table = (struct ip_record *) 
	malloc(sizeof(struct ip_record) * ip_table_size);
    for(int i = 0; i < (int) ip_table_size; ++i){
	ip_table[i].count = -128;
    }
    
    click_chatter("Allocating space for %i entry connection table: %i bytes\n",
		  con_table_size,
		  sizeof(struct con_record) * con_table_size);
    con_table = (struct con_record *)
	malloc(sizeof(struct con_record) * con_table_size);
    for(int i = 0; i < (int) con_table_size; ++i){
	con_table[i].status = 0;
	// Don't need to set the timestamp, as status gets properly
	// zeroed out anyway.
    }

    // The masks remove the need for mod calculations and recalculation
    // when finding the index and tag of an IP address
    ip_addr_index_mask = (ip_table_size / ip_table_assoc) - 1;
    ip_addr_tag_shift = 32;
    for(unsigned i = 1; i < (ip_table_size / ip_table_assoc); i = i * 2){
	ip_addr_tag_shift = ip_addr_tag_shift - 1;
    }
    for(unsigned i = 1; i <= ip_table_assoc; i = i * 2){
	if(ip_table_assoc % i != 0 || ip_table_assoc < 1)
	    return 
		errh->error("Table Size & Associativity must be a power of 2");
    }
    for(unsigned i = 1; i <= ip_table_size; i = i * 2){
	if(ip_table_size % i != 0 || ip_table_size < 1)
	    return 
		errh->error("Table Size & Associativity must be a power of 2");
    }
    if((ip_table_size / ip_table_assoc) < 65536 || ip_addr_tag_shift < 16)
	return 
	    errh->error("Table Size / assoc must be >= 2^16. Was %i",
			(ip_table_size / ip_table_assoc));

    click_chatter("IP index mask is %x\n", ip_addr_index_mask);
    click_chatter("IP tag shift is %i\n",  ip_addr_tag_shift);
    
    click_chatter("IP table associativity is %i\n", ip_table_assoc);

    return 0;
}

void MapTRW::chatter_map(struct map_record &rec){
    StringAccum sa;
    sa << "Map Record for " << rec.map_ip << '\0';
    click_chatter("%s", sa.data());
}

CLICK_ENDDECLS
ELEMENT_REQUIRES(rc5)
EXPORT_ELEMENT(MapTRW)

-------------- next part --------------
// -*- c-basic-offset: 4 -*-
#ifndef NW_MAP_TRW_HH
#define NW_MAP_TRW_HH
#include <click/element.hh>
#include <click/string.hh>
#include <click/etheraddress.hh>
#include <click/ipaddress.hh>
CLICK_DECLS

// This file is copyright 2005/2006 by the International Computer
// Science Institute.  It can be used for any purposes
// (Berkeley-liscence) as long as this notice remains intact.

// THERE IS NO WARANTEE ON THIS CODE!


/*
 * =c
 * MapTRW(IP, Enet_MAC, subnet_mask, args)
 * =s Packet processing for security
 * This is a packet processor for approximate TRW
 * =d
 * This module implements approximate TRW scan detection.  It is designed
 * to be a push-only module.
 *
 * It takes two input streams and has four output streams.  The first
 * two output streams correspond to the two inputs for normal passing of
 * packets.  The second two output streams are for "dropped" packets,
 * which allows some other module to possibly process and reinject
 * (such as for notification of dropping) 
 *
 * The options are for the IP table size and the connection table size.
 * 
 * Unlike the usenix description, fields will contain a timestamp
 * with updates performed based on that timestamp.  This is because
 * the usenix experience was that a lot of the range was unused,
 * so rather than housekeeping the table eagerly, more memory will be
 * used to enable lazy housekeeping. 
 *
 * IP address is used to determine this instance's IP if active mapping
 * (currently not implemneted) is desired and the local subnet.
 *
 * Enet_mac is a mac to use for active mapping (not implemented)
 *
 * Subnet mask is used to specify the subnet mask.  Combined with the IP
 * Address, this is used to determine whether an IP is local to this LAN
 * or remote (no ARPing needed).
 *
 * =a 
 */

class MapTRW : public Element { public:
  
    MapTRW();
    ~MapTRW();
  
    const char *class_name() const	{ return "MapTRW"; }
    const char *processing() const	{ return PUSH; }

    int configure(Vector<String> &conf, ErrorHandler *errh);

    const char * port_count () const {return "2/4";}
    
    void push(int port, Packet *p);
  
private:
    struct ip_record *find_ip(uint32_t ip_hash);
    struct con_record *find_con(Packet *p, uint32_t src_hash,
				uint32_t dst_hash,
				int direction);

    struct ip_record *ip_table;
    struct con_record *con_table;


    struct map_record *arp_map;

    uint16_t *rc5_key;
    uint32_t rc5_seed;

    // Both these are the size and associativity for the
    // two tables.  They will be rounded DOWN to the nearest power of
    // 2.  the ip_table_size must be at least 2^16 * assocativity,
    // 
    // Default values are associativity of 4, table size of 2^18, thus
    // requiring 1 MB by default, and able to store 256k entries
    unsigned ip_table_size;
    unsigned ip_table_assoc;

    unsigned ip_table_decr_age;
    unsigned ip_table_incr_age;
    
    int ip_table_block_count;
    int ip_table_max_count;
    int ip_table_min_count;


    // The size of the connection table.  Default is 2^18 entries
    // which requires 1 MB.
    unsigned con_table_size;

    // The number of idle minutes before a connection table record is aged
    unsigned con_table_maxage;

    // The last time this was accessed, in MINUTES
    unsigned last_time;

    // The last time the table was updated, in SECONDS
    unsigned last_map;

    // The number of entries in the 
    unsigned map_size;

    // Used to get the and index quickly
    uint32_t ip_addr_index_mask;
    uint32_t ip_addr_tag_shift;

    // Controls whether to chatter for tomato
    bool tomato_chatter;


    // The ethernet and IP addresses
    EtherAddress _my_en;
    IPAddress _my_ip;
    IPAddress _my_mask;

    void chatter_map(struct map_record &mp);

    void update_map();
    void handle_arp(int port, Packet *p);

    void passive_update_map_ip(int port, Packet *p);
    void passive_update_map_arp(int port, Packet *p);

    bool supress_broadcast(int port, Packet *p);

};

CLICK_ENDDECLS
#endif
-------------- next part --------------
#include <click/config.h>
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/straccum.hh>
#include <clicknet/icmp.h>
#include <clicknet/tcp.h>
#include <clicknet/udp.h>
#include "trw_packet_utils.hh"

// This file is copyright 2005/2006 by the International Computer
// Science Institute.  It can be used for any purposes
// (Berkeley-liscence) as long as this notice remains intact.

// THERE IS NO WARANTEE ON THIS CODE!

// Does this get dropped if we are overcount, even though
// the connection is established?
// Currently, its UDP, TCP SYN (and no ACK),
bool block_policy(Packet *p){
  const click_ip *iph = p->ip_header();
  if(iph->ip_p == IP_PROTO_TCP){
    const click_tcp *tcph = p->tcp_header();
    if( (tcph->th_flags & TH_SYN) &&
	!(tcph->th_flags & TH_ACK)){
      return true;
    }
  } else if(iph->ip_p == IP_PROTO_UDP){
    return true;
  } else {
  }
  return false;
}

// Is this a valid acknowledgement packet
bool valid_ack(Packet *p){
  const click_ip *iph = p->ip_header();
  if(iph->ip_p == IP_PROTO_TCP){
    const click_tcp *tcph = p->tcp_header();
    if( (tcph->th_flags & TH_FIN) ||
	(tcph->th_flags & TH_RST)){
      // TCP FIN & RST are not valid ack, but
      // normal
      return false;
    }
    return true;
  } else if(iph->ip_p == IP_PROTO_UDP){
    return true;
  } else if(iph->ip_p == IP_PROTO_ICMP){
    const click_icmp *icmph = p->icmp_header();
    if (icmph->icmp_type == ICMP_ECHOREPLY) {
      return true;
    }
    return false;
  } else {
    return true;
  }
}


CLICK_ENDDECLS
ELEMENT_PROVIDES(trw_packet_utils)

-------------- next part --------------
#ifndef NW_PUTILS_HH
#define NW_PUTILS_HH
#include <click/element.hh>

// This file is copyright 2005/2006 by the International Computer
// Science Institute.  It can be used for any purposes
// (Berkeley-liscence) as long as this notice remains intact.

// THERE IS NO WARANTEE ON THIS CODE!


// On TRW, does this packet get blocked if a system is being blocked?
bool block_policy(Packet *p);

// Is this packet really an acknowledgement?
bool valid_ack(Packet *p);

#endif
-------------- next part --------------
#include <click/config.h>
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/straccum.hh>
#include <clicknet/icmp.h>
#include <clicknet/tcp.h>
#include <clicknet/udp.h>
#include "rc5.hh"

// This file is copyright 2005/2006 by the International Computer
// Science Institute.  It can be used for any purposes
// (Berkeley-liscence) as long as this notice remains intact.

// THERE IS NO WARANTEE ON THIS CODE!



// Implementation for RC5, 32 bit, 3 rounds, 32 bit key (RC5/32/3/32)
// which is what's used as the random permutation for the address
// table, and for the pRNG for the random dropper.  This is a WEAK
// cypher, but as the attacker doesn't really have insight into the
// table state, AND since blowing out the table really doesn't buy
// much unless the attacker has tons of IPs, this isn't a problem.

// Modified from applied crypto, and my simple sim

#define RC5_ROUNDS 3

#define ROTR16(x,c) ((uint16_t) (((x)>>((c) & 0xf))|((x)<<(16-((c) & 0xf)))))
#define ROTL16(x,c) ((uint16_t) (((x)<<((c) & 0xf))|((x)>>(16-((c) & 0xf)))))
CLICK_DECLS
uint16_t *rc5_keygen(uint64_t key){
    uint16_t *keyArray;
    uint16_t lArray[4];
    int i = 0;
    int j = 0;
    int k = 0;
    uint16_t A = 0;
    uint16_t B = 0;
    lArray[0] = key;
    lArray[1] = key >> 16;
    lArray[2] = key >> 32;
    lArray[3] = key >> 48;
    keyArray = (uint16_t *) malloc(sizeof(uint16_t) * 2 * (RC5_ROUNDS + 1));
    keyArray[0] = 0xb7e5;
    for(i = 1; i < (2 * (RC5_ROUNDS + 1)); ++i){
	keyArray[i] = (keyArray[i-1] + 0x9e37);
    }
    i = 0; j = 0;
    for(k = 0; k < 6 * (RC5_ROUNDS + 1); ++k){
	A = ROTL16( keyArray[i] + A + B, 3);
	keyArray[i] = A;
	B = ROTL16( lArray[j] + A + B, A + B);
	lArray[j] = B;
	i++;
	j++;
	i = i % (2 * (RC5_ROUNDS + 1));
	j = j % 4;
    }
    /*   click_chatter("Key array for initial key %i is\n", key);
	 for(i = 0; i < (2 * (numRounds + 1)); ++i){
	 click_chatter("%2i   0x%4x\n",i,keyArray[i]);
    } */
    return keyArray;
}

uint32_t rc5_encrypt(uint32_t data, uint16_t *key){
    uint16_t a, b;
    uint32_t result;
    int i;
    a = (data & 0xffff);
    b = ((data >> 16) & 0xffff);
    a += key[0];
    b += key[1];
    for(i = 1; i <= RC5_ROUNDS; ++i){
	a = a ^ b;
	a = ROTL16(a,b);
	a = a + key[2 * i];
	b = ROTL16((b ^ a), a);
	b = b + key[2 * i + 1];
    }
    result = b;
    result = result << 16;
    result = result | a;
    return result;
}

uint32_t rc5_decrypt(uint32_t data, uint16_t *key){
    uint16_t a, b;
    uint32_t result;
    int i;
    a = (data & 0xffff);
    b = ((data >> 16) & 0xffff);
    for(i = RC5_ROUNDS; i >= 1; --i){
	b = b - key [2 * i + 1];
	b = ROTR16(b,a);
	b = b ^ a;

	a = a - key [2 * i];
	a = ROTR16(a,b);
	a = a ^ b;
    }
    b = b - key[1];
    a = a - key[0];

    result = b;
    result = result << 16;
    result = result | a;
    return result;
}
CLICK_ENDDECLS
ELEMENT_PROVIDES(rc5)

-------------- next part --------------
#ifndef NW_RC5_HH
#define NW_RC5_HH
#include <click/element.hh>

// This file is copyright 2005/2006 by the International Computer
// Science Institute.  It can be used for any purposes
// (Berkeley-liscence) as long as this notice remains intact.

// THERE IS NO WARANTEE ON THIS CODE!

uint16_t * rc5_keygen(uint64_t key);
uint32_t rc5_encrypt(uint32_t data, uint16_t *key);
uint32_t rc5_decrypt(uint32_t data, uint16_t *key);

#endif
-------------- next part --------------

// Classifies into IP, ARP, and other
only_ip0 :: Classifier(12/0800, 12/0806, -);
only_ip1 :: Classifier(12/0800, 12/0806, -);


toEth0 :: Queue;
toEth1 :: Queue;

trw :: MapTRW(10.10.1.254, CA:FE:BA:BE:00:01, 255.255.255.0, IP_TABLE_MIN_COUNT -15, IP_TABLE_MAX_COUNT 20,
        IP_TABLE_BLOCK_COUNT 5);


FromDevice(eth2, PROMISC true) -> only_ip0[0] -> MarkIPHeader(14) -> 
	[0]trw[0] -> toEth1 -> ToDevice(eth1);

only_ip0[1] -> [0]trw;
only_ip0[2] -> toEth1;

FromDevice(eth1, PROMISC true) -> only_ip1[0] -> MarkIPHeader(14) -> 
	[1]trw[1]-> toEth0 ->
	ToDevice(eth2);

only_ip1[1] -> [1]trw;
only_ip1[2] -> toEth0;


More information about the click mailing list