/* * 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 #include "arpquerier.hh" #include #include #include #include #include #include #include #include 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 &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 &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)