/*
 * arpquerier.{cc,hh} -- ARP resolver element
 * Robert Morris, Eddie Kohler
 *
 * Copyright (c) 1999-2000 Massachusetts Institute of Technology
 * Copyright (c) 2005 Regents of the University of California
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>
#include "arpquerier.hh"
#include <clicknet/ether.h>
#include <click/etheraddress.hh>
#include <click/ipaddress.hh>
#include <click/confparse.hh>
#include <click/bitvector.hh>
#include <click/router.hh>
#include <click/error.hh>
#include <click/glue.hh>
CLICK_DECLS

ARPQuerier::ARPQuerier()
    : _age_head(0), _age_tail(0), _expire_timer(expire_hook, this)
{
    // input 0: IP packets
    // input 1: ARP responses
    // output 0: ether/IP and ether/ARP queries
  for (int i = 0; i < NMAP; i++)
    _map[i] = 0;
}

ARPQuerier::~ARPQuerier()
{
}

int
ARPQuerier::configure(Vector<String> &conf, ErrorHandler *errh)
{
    _capacity = 2048;
    _bcast_addr = IPAddress();
    IPAddress bcast_mask;
    bool confirm_bcast = false;
    if (cp_va_parse_remove_keywords(conf, 1, this, errh,
				    "CAPACITY", cpUnsigned, "packet capacity", &_capacity,
				    cpConfirmKeywords,
				    "BROADCAST", cpIPAddress, "IP local broadcast address", &confirm_bcast, &_bcast_addr,
				    cpEnd) < 0)
	return -1;
    if (conf.size() == 1)
	conf.push_back(conf[0]);
    if (cp_va_parse(conf, this, errh,
		    cpIPAddressOrPrefix, "IP address", &_my_ip, &bcast_mask,
		    cpEthernetAddress, "Ethernet address", &_my_en,
		    cpEnd) < 0)
	return -1;
    if (!_bcast_addr)
	_bcast_addr = _my_ip | ~bcast_mask;
    if (_bcast_addr == _my_ip)
	_bcast_addr = 0xFFFFFFFFU;
    return 0;
}

int
ARPQuerier::live_reconfigure(Vector<String> &conf, ErrorHandler *errh)
{
    if (configure(conf, errh) < 0) {
	// if the configuration failed do nothing and return with a
	// failure indication
	return -1;
    }
  
    // if the new configuration succeeded then wipe out the old arp
    // table and reset the queries and pkts_killed counters
    clear_map();
    _arp_queries = 0;
    _drops = 0;
    _arp_responses = 0;
    return 0;
}

int
ARPQuerier::initialize(ErrorHandler *)
{
  _expire_timer.initialize(this);
  _expire_timer.schedule_after_msec(EXPIRE_TIMEOUT_MS);
  _arp_queries = 0;
  _drops = 0;
  _arp_responses = 0;
  _cache_size = 0;
  return 0;
}

void
ARPQuerier::cleanup(CleanupStage)
{
  clear_map();
}

void
ARPQuerier::clear_map()
{
  // Walk the arp cache table and free 
  // any stored packets and arp entries.
  for (int i = 0; i < NMAP; i++) {
    for (ARPEntry *ae = _map[i]; ae; ) {
      ARPEntry *n = ae->next;
      while (Packet *p = ae->head) {
	  ae->head = p->next();
	  p->kill();
	  _drops++;
      }
      delete ae;
      ae = n;
    }
    _map[i] = 0;
  }
  _cache_size = 0;
}

void
ARPQuerier::take_state(Element *e, ErrorHandler *errh)
{
    ARPQuerier *arpq = (ARPQuerier *)e->cast("ARPQuerier");
    if (!arpq || _my_ip != arpq->_my_ip || _my_en != arpq->_my_en)
	return;
    if (_arp_queries > 0) {
	errh->error("late take_state");
	return;
    }

    memcpy(_map, arpq->_map, sizeof(ARPEntry *) * NMAP);
    memset(arpq->_map, 0, sizeof(ARPEntry *) * NMAP);

    _age_head = arpq->_age_head;
    _age_tail = arpq->_age_tail;
    _cache_size = arpq->_cache_size;
    _arp_queries = arpq->_arp_queries;
    _drops = arpq->_drops;
    _arp_responses = arpq->_arp_responses;

    // Need to change some pprev entries.
    for (int i = 0; i < NMAP; i++)
	if (_map[i])
	    _map[i]->pprev = &_map[i];
    if (_age_head)
	_age_head->age_pprev = &_age_head;
    
    arpq->_age_head = arpq->_age_tail = 0;
    arpq->_cache_size = 0;
}


void
ARPQuerier::expire_hook(Timer *timer, void *thunk)
{
    // Expire any old entries, and make sure there's room for at least one
    // packet.
    ARPQuerier *arpq = (ARPQuerier *)thunk;
    arpq->_lock.acquire_write();
    int jiff = click_jiffies();
    ARPEntry *ae;

    // Delete old entries.
    while ((ae = arpq->_age_head) && (jiff - ae->last_response_jiffies) > 300*CLICK_HZ) {
	if ((*ae->pprev = ae->next))
	    ae->next->pprev = ae->pprev;

	if ((arpq->_age_head = ae->age_next))
	    arpq->_age_head->age_pprev = &arpq->_age_head;
	else
	    arpq->_age_tail = 0;
	
	while (Packet *p = ae->head) {
	    ae->head = p->next();
	    p->kill();
	    arpq->_cache_size--;
	    arpq->_drops++;
	}

	delete ae;
    }

    // Mark entries for polling, and delete packets to make space.
    while (ae) {
	// Only set polling on timer calls.
	if (jiff - ae->last_response_jiffies > 60*CLICK_HZ && timer)
	    ae->polling = 1;
	else if (arpq->_cache_size < arpq->_capacity)
	    break;
	while (arpq->_cache_size >= arpq->_capacity && ae->head) {
	    Packet *p = ae->head;
	    if (!(ae->head = p->next()))
		ae->tail = 0;
	    p->kill();
	    arpq->_cache_size--;
	    arpq->_drops++;
	}
	ae = ae->age_next;
    }

    if (timer)
	timer->schedule_after_msec(EXPIRE_TIMEOUT_MS);
    arpq->_lock.release_write();
}

void
ARPQuerier::send_query_for(IPAddress want_ip)
{
  WritablePacket *q = Packet::make(sizeof(click_ether) + sizeof(click_ether_arp));
  if (!q) {
    click_chatter("in arp querier: cannot make packet!");
    return;
  }
  memset(q->data(), '\0', q->length());
  
  click_ether *e = (click_ether *) q->data();
  q->set_ether_header(e);
  memcpy(e->ether_dhost, "\xff\xff\xff\xff\xff\xff", 6);
  memcpy(e->ether_shost, _my_en.data(), 6);
  e->ether_type = htons(ETHERTYPE_ARP);

  click_ether_arp *ea = (click_ether_arp *) (e + 1);
  ea->ea_hdr.ar_hrd = htons(ARPHRD_ETHER);
  ea->ea_hdr.ar_pro = htons(ETHERTYPE_IP);
  ea->ea_hdr.ar_hln = 6;
  ea->ea_hdr.ar_pln = 4;
  ea->ea_hdr.ar_op = htons(ARPOP_REQUEST);
  memcpy(ea->arp_tpa, want_ip.data(), 4);
  memcpy(ea->arp_sha, _my_en.data(), 6);
  memcpy(ea->arp_spa, _my_ip.data(), 4);

  _arp_queries++;
  output(noutputs()-1).push(q);
}

/*
 * If the packet's IP address is in the table, add an ethernet header
 * and push it out.
 * Otherwise push out a query packet.
 * May save the packet in the ARP table for later sending.
 * May call p->kill().
 */
void
ARPQuerier::handle_ip(Packet *p)
{
    // delete packet if we are not configured
    if (!_my_ip) {
	p->kill();
	_drops++;
	return;
    }

    IPAddress ipa = p->dst_ip_anno();
    int bucket = ip_bucket(ipa);
    ARPEntry *ae;

    // Easy case: requires only read lock
  retry_read_lock:
    _lock.acquire_read();
    ae = _map[bucket];
    while (ae && ae->ip != ipa)
	ae = ae->next;
    if (ae && ae->ok) {
	int was_polling = ae->polling;
	ae->polling = 0;
	if (WritablePacket *q = p->push_mac_header(sizeof(click_ether))) {
	    click_ether *e = q->ether_header();
	    memcpy(e->ether_shost, _my_en.data(), 6);
	    memcpy(e->ether_dhost, ae->en.data(), 6);
	    e->ether_type = htons(ETHERTYPE_IP);
	    _lock.release_read();
	    output(0).push(q);
	} else {
	    _drops++;
	    _lock.release_read();
	}
	if (was_polling)
	    send_query_for(ipa);
	return;
    }
    _lock.release_read();

    // Check special IP addresses
    if (!ipa) {
	static bool zero_warned = false;  
	if (!zero_warned) {
	    click_chatter("%s: would query for 0.0.0.0; missing dest IP addr annotation?", declaration().c_str());
	    zero_warned = true;
	}
	_drops++;
	p->kill();
	return;
    } else if (ipa.addr() == 0xFFFFFFFFU || ipa == _bcast_addr) {
	if (WritablePacket *q = p->push_mac_header(sizeof(click_ether))) {
	    click_ether *e = q->ether_header();
	    memcpy(e->ether_shost, _my_en.data(), 6);
	    memset(e->ether_dhost, 0xFF, 6);
	    e->ether_type = htons(ETHERTYPE_IP);
	    output(0).push(q);
	} else
	    _drops++;
	return;
    }

    // Hard case: requires write lock
    // 18.May.2005 -- must expire BEFORE we grab the ae pointer!!
    // because expiring might in fact DELETE the ae pointer.
    if (_cache_size >= _capacity)	// get some space if necessary
	expire_hook(0, this);
    
    _lock.acquire_write();
    ae = _map[bucket];
    while (ae && ae->ip != ipa)
	ae = ae->next;
    if (ae && ae->ok) {
	_lock.release_write();
	goto retry_read_lock;
    } else if (ae) {
	if (ae->tail)
	    ae->tail->set_next(p);
	else
	    ae->head = p;
	ae->tail = p;
	p->set_next(0);
	_cache_size++;
    } else if ((ae = new ARPEntry)) {
	ae->ip = ipa;
	ae->ok = ae->polling = 0;
	ae->last_response_jiffies = click_jiffies() - CLICK_HZ;
	
	ae->head = ae->tail = p;
	p->set_next(0);
	
	ae->pprev = &_map[bucket];
	if ((ae->next = _map[bucket]))
	    ae->next->pprev = &ae->next;
	_map[bucket] = ae;
	
	if (_age_tail)
	    ae->age_pprev = &_age_tail->age_next;
	else
	    ae->age_pprev = &_age_head;
	_age_tail = *ae->age_pprev = ae;
	ae->age_next = 0;
	
	_cache_size++;
    } else {
	p->kill();
	_drops++;
	_lock.release_write();
	return;
    }
    
    // Send a query for any given address at most 10 times a second.
    int jiff = click_jiffies();
    if ((int) (jiff - ae->last_response_jiffies) >= CLICK_HZ / 10) {
	ae->last_response_jiffies = jiff;
	_lock.release_write();
	send_query_for(ipa);
    } else
	_lock.release_write();
}

/*
 * Got an ARP response.
 * Update our ARP table.
 * If there was a packet waiting to be sent, return it.
 */
void
ARPQuerier::handle_response(Packet *p)
{
  if (p->length() < sizeof(click_ether) + sizeof(click_ether_arp))
    return;

  _arp_responses++;
  
  click_ether *ethh = (click_ether *) p->data();
  click_ether_arp *arph = (click_ether_arp *) (ethh + 1);
  IPAddress ipa = IPAddress(arph->arp_spa);
  EtherAddress ena = EtherAddress(arph->arp_sha);
  if (ntohs(ethh->ether_type) == ETHERTYPE_ARP
      && ntohs(arph->ea_hdr.ar_hrd) == ARPHRD_ETHER
      && ntohs(arph->ea_hdr.ar_pro) == ETHERTYPE_IP
      && ntohs(arph->ea_hdr.ar_op) == ARPOP_REPLY
      && !ena.is_group()) {
    int bucket = ip_bucket(ipa);

    _lock.acquire_write();
    ARPEntry *ae = _map[bucket];
    while (ae && ae->ip != ipa)
      ae = ae->next;
    if (!ae) {
	// XXX would be nice to store an entry for this preemptive response
	_lock.release_write();
	return;
    }
    
    if (ae->ok && ae->en != ena)
	click_chatter("ARPQuerier overwriting an entry");
    ae->en = ena;
    ae->ok = 1;
    ae->polling = 0;
    ae->last_response_jiffies = click_jiffies();
    Packet *cached_packet = ae->head;
    ae->head = ae->tail = 0;
    if (_age_tail != ae) {
	*ae->age_pprev = ae->age_next;
	ae->age_next->age_pprev = ae->age_pprev;
	ae->age_pprev = &_age_tail->age_next;
	ae->age_next = 0;
	_age_tail = *ae->age_pprev = ae;
    }
    _lock.release_write();

    // Send out packets in the order in which they arrived
    while (cached_packet) {
	Packet *next = cached_packet->next();
	handle_ip(cached_packet);
	cached_packet = next;
	_cache_size--;
    }
  }
}

void
ARPQuerier::push(int port, Packet *p)
{
  if (port == 0)
    handle_ip(p);
  else {
    handle_response(p);
    p->kill();
  }
}

String
ARPQuerier::read_table(Element *e, void *)
{
  ARPQuerier *q = (ARPQuerier *)e;
  String s;
  for (int i = 0; i < NMAP; i++)
    for (ARPEntry *e = q->_map[i]; e; e = e->next) {
      s += e->ip.s() + " " + (e->ok ? "1" : "0") + " " + e->en.s() + "\n";
    }
  return s;
}

String
ARPQuerier::read_stats(Element *e, void *thunk)
{
  ARPQuerier *q = (ARPQuerier *)e;

  switch ((uintptr_t) thunk) {
    case 0:
      return
        String(q->_drops.value()) + " packets killed\n" +
        String(q->_arp_queries.value()) + " ARP queries sent\n";
    case 1:
      return String(q->_arp_queries.value());
    case 2:
      return String(q->_arp_responses.value());
    case 3:
      return String(q->_drops.value());
    default:
      return String();
  }
}

void
ARPQuerier::add_handlers()
{
    add_read_handler("table", read_table, (void *)0);
    add_read_handler("stats", read_stats, (void *)0);
    add_read_handler("queries", read_stats, (void *)1);
    add_read_handler("responses", read_stats, (void *)2);
    add_read_handler("drops", read_stats, (void *)3);
}

CLICK_ENDDECLS
EXPORT_ELEMENT(ARPQuerier)
ELEMENT_MT_SAFE(ARPQuerier)


syntax highlighted by Code2HTML, v. 0.9.1