servlib/routing/DTLSRRouter.cc
changeset 0 2b3e5ec03512
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlib/routing/DTLSRRouter.cc	Thu Apr 21 14:57:45 2011 +0100
@@ -0,0 +1,973 @@
+/*
+ *    Copyright 2007 Intel Corporation
+ * 
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ * 
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <dtn-config.h>
+#endif
+
+#include <algorithm>
+#include <oasys/debug/DebugUtils.h>
+
+#include "DTLSRRouter.h"
+#include "bundling/BundleDaemon.h"
+#include "bundling/SDNV.h"
+#include "bundling/TempBundle.h"
+#include "contacts/ContactManager.h"
+#include "routing/RouteTable.h"
+#include "session/Session.h"
+
+namespace oasys {
+
+//----------------------------------------------------------------------
+template<>
+const char*
+InlineFormatter<dtn::DTLSRRouter::EdgeInfo>
+  ::format(const dtn::DTLSRRouter::EdgeInfo& ei)
+{
+    const char* state_str;
+    if (ei.params_.state_ == dtn::DTLSRRouter::LinkParams::LINK_UP) {
+         state_str = "UP";
+    } else if (ei.params_.state_ == dtn::DTLSRRouter::LinkParams::LINK_DOWN) {
+        state_str = "DOWN";
+    } else {
+        state_str = "__UNKNOWN__";
+    }
+
+    buf_.appendf("%s: state=%s cost=%u delay=%u bw=%u",
+                 ei.id_.c_str(), state_str,
+                 ei.params_.cost_, ei.params_.delay_, ei.params_.bw_);
+    return buf_.c_str();
+}
+
+} // namespace oasys
+
+namespace dtn {
+
+//----------------------------------------------------------------------
+class DTLSRRouter::CostWeightFn : public RoutingGraph::WeightFn {
+public:
+    u_int32_t operator()(const RoutingGraph::SearchInfo& info,
+                         const RoutingGraph::Edge* edge)
+    {
+        (void)info;
+        if (edge->info().params_.state_ == LinkParams::LINK_DOWN) {
+            return 0xffffffff;
+        }
+        return edge->info().params_.cost_;
+    }
+};
+
+//----------------------------------------------------------------------
+class DTLSRRouter::DelayWeightFn : public RoutingGraph::WeightFn {
+public:
+    u_int32_t operator()(const RoutingGraph::SearchInfo& info,
+                         const RoutingGraph::Edge* edge)
+    {
+        (void)info;
+
+        u_int32_t downtime = (info.now_ - edge->info().last_update_).sec_;
+        if (DTLSRConfig::instance()->lsa_interval_ != 0 &&
+            downtime > (2 * DTLSRConfig::instance()->lsa_interval_))
+        {
+            // don't know anything implies the link is "down"
+            return 0xffffffff;
+        }
+            
+        if (edge->info().params_.state_ == LinkParams::LINK_DOWN) {
+            return 0xffffffff;
+        }
+        return edge->info().params_.delay_;
+    }
+};
+
+//----------------------------------------------------------------------
+class DTLSRRouter::EstimatedDelayWeightFn : public RoutingGraph::WeightFn {
+public:
+    EstimatedDelayWeightFn(DTLSRRouter* rtr)
+        : router_(rtr) {}
+    
+    u_int32_t operator()(const RoutingGraph::SearchInfo& info,
+                         const RoutingGraph::Edge* edge)
+    {
+        if (edge->info().params_.state_ == LinkParams::LINK_DOWN) {
+
+            // adjust the delay based on the uptime / downtime
+            u_int32_t downtime = (info.now_ - edge->info().last_update_).sec_;
+            
+            // XXX/demmer for now make the bogus silly assumption that
+            // it will take just as long to come back up as it's been
+            // down for, plus a constant factor of 5 seconds to
+            // establish the link, capped at 1 day
+            //
+            // really, we need a good way of succinctly characterizing
+            // delay in a way that can be decayed but also take into
+            // account historical data, i.e. a number that
+            // grows/shrinks as the link gets worse but where a router
+            // can take into account that some outages are expected
+            u_int32_t ret = (downtime + 5) >> DTLSRConfig::instance()->weight_shift_;
+
+            if (ret < 24*60*60) {
+                log_debug_p("/dtn/route/graph",
+                            "weight of edge %s=%u (downtime %u + 5)",
+                            edge->info().id_.c_str(), ret, downtime);
+            } else {
+                ret = 24*60*60;
+                log_warn_p("/dtn/route/graph",
+                           "weight of edge %s=%u: (downtime %u exceeded max)",
+                           edge->info().id_.c_str(), ret, downtime);
+            }
+
+            return ret;
+        }
+
+        u_int32_t qlen, qsize;
+        // XXX/demmer use the link queue length if it's a local link
+        qlen  = edge->info().params_.qcount_;
+        qsize = edge->info().params_.qsize_;
+        
+        // based on the queue length and link estimates, calculate the
+        // forwarding delay
+        u_int32_t qdelay = (qlen + 1) * edge->info().params_.delay_ / 1000;
+        u_int32_t bwdelay = 0;
+        if (edge->info().params_.bw_ != 0) {
+            // XXX/demmer is bw bits or bytes?
+            bwdelay = qsize / edge->info().params_.bw_;
+        }
+        
+        u_int32_t delay = qdelay + bwdelay;
+        log_debug_p("/dtn/route/graph", "weight of edge %s=%u "
+                    "(qlen %u delay %u qdelay %u qsize %u bw %u bwdelay %u)",
+                    edge->info().id_.c_str(), delay,
+                    qlen, edge->info().params_.delay_, qdelay,
+                    qsize, edge->info().params_.bw_, bwdelay);
+        
+        // XXX/demmer we might want to have an estimator for bundles
+        // that we've sent out en route to the destination along with
+        // where and when those bundles will be at different routers.
+        // then when LSAs come in, we adjust our estimates accordingly
+
+        return delay;
+    }
+
+    DTLSRRouter* router_;
+};
+
+//----------------------------------------------------------------------
+DTLSRRouter::DTLSRRouter()
+    : TableBasedRouter("DTLSRRouter", "dtlsr"),
+      announce_tag_("dtlsr"),
+      announce_eid_("dtn://*/dtlsr?*"),
+      current_lsas_("DTLSRRouter::current_lsas"),
+      periodic_lsa_timer_(this),
+      delayed_lsa_timer_(this)
+{
+    // override add_nexthop_routes since it just confuses things
+    config_.add_nexthop_routes_ = false;
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::initialize()
+{
+    TableBasedRouter::initialize();
+    
+    const EndpointID& local_eid = BundleDaemon::instance()->local_eid();
+
+    if (local_eid.scheme_str() != "dtn") {
+        log_crit("cannot use DTLSR with a local eid not in the 'dtn' scheme");
+        exit(1);
+    }
+    
+    local_node_ = graph_.add_node(local_eid.str(), NodeInfo());
+
+    EndpointIDPattern admin_eid = local_eid;
+    admin_eid.append_service_tag(announce_tag_);
+    reg_ = new Reg(this, admin_eid);
+    
+    log_info("initializing: local_eid %s weight_fn %s",
+             local_eid.c_str(),
+             DTLSRConfig::weight_fn_to_str(config()->weight_fn_));
+             
+    switch (config()->weight_fn_) {
+    case DTLSRConfig::COST:
+        weight_fn_ = new CostWeightFn();
+        break;
+        
+    case DTLSRConfig::DELAY:
+        weight_fn_ = new DelayWeightFn();
+        break;
+
+    case DTLSRConfig::ESTIMATED_DELAY:
+        weight_fn_ = new EstimatedDelayWeightFn(this);
+        break;
+    }
+    
+    if (config()->lsa_interval_ != 0) {
+        periodic_lsa_timer_.set_interval(config()->lsa_interval_ * 1000);
+        periodic_lsa_timer_.schedule_in(config()->lsa_interval_ * 1000);
+    }
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::get_routing_state(oasys::StringBuffer* buf)
+{
+//     invalidate_routes();
+//     recompute_routes();
+    
+    buf->appendf("DTLSR Routing Graph:\n*%p", &graph_);
+    buf->appendf("Current routing table:\n");
+    TableBasedRouter::get_routing_state(buf);
+}
+
+//----------------------------------------------------------------------
+bool
+DTLSRRouter::can_delete_bundle(const BundleRef& bundle)
+{
+    if (! TableBasedRouter::can_delete_bundle(bundle)) {
+        return false;
+    }
+
+    if (current_lsas_.contains(bundle)) {
+        log_debug("can_delete_bundle(%u): current lsa", bundle->bundleid());
+        return false;
+    }
+
+    return true;
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::delete_bundle(const BundleRef& bundle)
+{
+    TableBasedRouter::delete_bundle(bundle);
+
+    if (current_lsas_.contains(bundle))
+    {
+        log_crit("deleting bundle id %u whilea still on current lsas list",
+                 bundle->bundleid());
+        current_lsas_.erase(bundle);
+    }
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::handle_link_created(LinkCreatedEvent* e)
+{
+    // XXX/demmer TODO: add the edge and initialize downtime_pct based
+    // on the link type.
+
+    invalidate_routes();
+    recompute_routes();
+    TableBasedRouter::handle_link_created(e);
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::handle_bundle_received(BundleReceivedEvent* e)
+{
+//     if (time_to_age_routes()) {
+//         recompute_routes();
+//     }
+
+    TableBasedRouter::handle_bundle_received(e);
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::handle_bundle_expired(BundleExpiredEvent* e)
+{
+    Bundle* bundle = e->bundleref_.object();
+
+    // check if this is one of the current LSAs if so, we
+    // need to drop our retention constraint and let it expire
+    //
+    // XXX/demmer perhaps we should do something more drastic like
+    // remove the node from the routing graph?
+    oasys::ScopeLock l(current_lsas_.lock(),
+                       "DTLSRRouter::handle_bundle_expired");
+
+    if (current_lsas_.contains(bundle))
+    {
+        log_notice("current lsa for %s expired... kicking links",
+                   bundle->source().c_str());
+        handle_lsa_expired(bundle);
+        current_lsas_.erase(bundle);
+    }
+    
+    TableBasedRouter::handle_bundle_expired(e);
+}
+    
+//----------------------------------------------------------------------
+void
+DTLSRRouter::handle_contact_up(ContactUpEvent* e)
+{
+    const LinkRef& link = e->contact_->link();
+    if (link->remote_eid() == EndpointID::NULL_EID()) {
+        log_warn("can't handle link %s: no remote endpoint id",
+                 link->name());
+        return;
+    }
+
+    EdgeInfo ei(link->name());
+    // XXX/demmer what about bw/delay/capacity?
+    ei.params_.cost_ = link->params().cost_;
+    
+    RoutingGraph::Node* remote_node =
+        graph_.find_node(link->remote_eid().str());
+
+    if (remote_node == NULL) {
+        remote_node = graph_.add_node(link->remote_eid().str(), NodeInfo());
+    }
+
+    RoutingGraph::Edge* edge = graph_.find_edge(local_node_, remote_node, ei);
+    if (edge == NULL) {
+        edge = graph_.add_edge(local_node_, remote_node, ei);
+    } else {
+        edge->mutable_info().params_.state_ = LinkParams::LINK_UP;
+    }
+
+    // always calculate the new routing graph now
+    invalidate_routes();
+    recompute_routes();
+    
+    // schedule a new lsa to include the new link
+    schedule_lsa();
+
+    // now let the superclass have a turn at the event
+    TableBasedRouter::handle_contact_up(e);
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::handle_contact_down(ContactDownEvent* e)
+{
+    const LinkRef& link = e->contact_->link();
+
+    RoutingGraph::Node* remote_node =
+        graph_.find_node(link->remote_eid().str());
+
+    if (remote_node == NULL) {
+        log_err("contact down event for link %s: node not in routing graph",
+                link->name());
+        return;
+    }
+
+    RoutingGraph::Edge* edge = NULL;
+    RoutingGraph::EdgeVector::const_iterator iter;
+    for (iter = local_node_->out_edges().begin();
+         iter != local_node_->out_edges().end(); ++iter)
+    {
+        if ((*iter)->info().id_ == link->name_str()) {
+            edge = *iter;
+            break;
+        }
+    }
+    
+    if (edge == NULL) {
+        log_err("handle_contact_down: can't find link *%p", link.object());
+        return;
+    }
+        
+    edge->mutable_info().params_.state_ = LinkParams::LINK_DOWN;
+
+    // calculate the new routing graph
+    invalidate_routes();
+    recompute_routes();
+
+    // inform peers
+    schedule_lsa();
+    
+    if (! config()->keep_down_links_) {
+        remove_edge(edge);
+    }
+    
+    TableBasedRouter::handle_contact_down(e);
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::handle_link_deleted(LinkDeletedEvent* e)
+{
+    TableBasedRouter::handle_link_deleted(e);
+    NOTREACHED;
+    
+    // XXX/demmer fixme
+    
+    RoutingGraph::EdgeVector::const_iterator iter;
+    for (iter = local_node_->out_edges().begin();
+         iter != local_node_->out_edges().end(); ++iter)
+    {
+        if ((*iter)->info().id_ == e->link_->name()) {
+            remove_edge(*iter);
+            return;
+        }
+    }
+
+    log_err("handle_link_deleted: can't find link *%p", e->link_.object());
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::handle_registration_added(RegistrationAddedEvent* event)
+{
+    Registration* reg = event->registration_;
+    const EndpointID& local_eid = BundleDaemon::instance()->local_eid();
+    
+    TableBasedRouter::handle_registration_added(event);
+
+    // to handle registration for endpoint identifiers that aren't
+    // covered by our local_eid pattern, we add a node to the graph
+    // with an infinite-bandwidth edge from our local node to it.
+    //
+    // session registrations don't get announced, except for custody
+    // ones which get the 'dtn-session:' prefix added on
+    if (reg->endpoint().subsume(local_eid)) {
+        return; // nothing to do
+    }
+
+    std::string eid;
+    if (reg->session_flags() == 0)
+    {
+        eid = reg->endpoint().str();
+    }
+    else if (reg->session_flags() & Session::CUSTODY)
+    {
+        eid = std::string("dtn-session:") + reg->endpoint().str();
+    }
+    else
+    {
+        return; // ignore non-custody registrations
+    }
+    
+    RoutingGraph::Node* node = graph_.find_node(eid);
+    if (node == NULL) {
+        log_debug("handle_registration added: adding new graph node for %s",
+                  eid.c_str());
+        node = graph_.add_node(eid, NodeInfo());
+    }
+
+    EdgeInfo ei(std::string("reg-") + eid);
+    ei.is_registration_ = true;
+    ei.params_.bw_      = 0xffffffff;
+    
+    RoutingGraph::Edge* edge = graph_.find_edge(local_node_, node, ei);
+    if (edge == NULL) {
+        log_debug("handle_registration added: adding new edge node for %s",
+                  ei.id_.c_str());
+        edge = graph_.add_edge(local_node_, node, ei);
+        
+        // send a new lsa to announce the new edge
+        schedule_lsa();
+    }
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::remove_edge(RoutingGraph::Edge* edge)
+{
+    log_debug("remove_edge %s", edge->info().id_.c_str());
+    bool ok = graph_.del_edge(local_node_, edge);
+    ASSERT(ok);
+}
+
+//----------------------------------------------------------------------
+bool
+DTLSRRouter::time_to_age_routes()
+{
+    u_int32_t elapsed = last_update_.elapsed_ms() / 1000;
+    if (elapsed > config()->aging_interval_) {
+        return true;
+    }
+    return false;
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::invalidate_routes()
+{
+    log_debug("invalidating routes");
+    last_update_.sec_ = 0;
+}
+
+//----------------------------------------------------------------------
+bool
+DTLSRRouter::is_dynamic_route(RouteEntry* entry)
+{
+    if (entry->info() == NULL) {
+        return false;
+    }
+    
+    RouteInfo* info = dynamic_cast<RouteInfo*>(entry->info());
+    if (info != NULL) {
+        return true;
+    }
+    
+    return false;
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::recompute_routes()
+{
+//     // XXX/demmer make this a parameter
+//     u_int32_t elapsed = last_update_.elapsed_ms() / 1000;
+//     if (elapsed < config()->recompute_delay_) {
+//         log_debug("not recomputing routes since %u ms elapsed since last update",
+//                   elapsed);
+//         return;
+//     }
+
+    log_debug("recomputing all routes");
+    last_update_.get_time();
+
+    route_table_->del_matching_entries(is_dynamic_route);
+
+    // loop through all the nodes in the graph, finding the right
+    // route and re-adding it
+    RoutingGraph::NodeVector::const_iterator iter;
+    for (iter = graph_.nodes().begin(); iter != graph_.nodes().end(); ++iter) {
+        const RoutingGraph::Node* dest = *iter;
+
+        if (dest == local_node_) {
+            continue;
+        }
+
+        EndpointIDPattern dest_eid(dest->id());
+        dest_eid.append_service_tag("*");
+        ASSERT(dest_eid.valid());
+
+        // XXX/demmer this should include more criteria for
+        // classification, i.e. the priority class, perhaps the size
+        // limit, etc
+        RoutingGraph::Edge* edge = graph_.best_next_hop(local_node_, dest,
+                                                        weight_fn_);
+        if (edge == NULL) {
+//            log_warn("no route to destination %s", dest->id().c_str());
+            continue;
+        }
+
+        ContactManager* cm = BundleDaemon::instance()->contactmgr();
+        LinkRef link = cm->find_link(edge->info().id_.c_str());
+        if (link == NULL) {
+
+            // it's possible that this is a local registration link,
+            // so just ignore it
+            log_debug("link %s not in local link table... ignoring it",
+                      edge->info().id_.c_str());
+            continue;
+        }
+
+        log_debug("recompute_routes: adding route for %s to link %s",
+                  dest_eid.c_str(), link->name());
+
+        // XXX/demmer shouldn't this call check_next_hop??
+        RouteEntry* e = new RouteEntry(dest_eid, link);
+        e->set_info(new RouteInfo(edge));
+        route_table_->add_entry(e);
+    }
+
+    // go through all bundles and re-route them
+    handle_changed_routes();
+}
+
+//----------------------------------------------------------------------
+DTLSRRouter::Reg::Reg(DTLSRRouter* router,
+                      const EndpointIDPattern& eid)
+    : Registration(DTLSR_REGID, eid, Registration::DEFER,
+                   /* XXX/demmer session_flags */ 0, 0),
+                   
+      router_(router)
+{
+    logpathf("%s/reg", router->logpath()),
+    BundleDaemon::post(new RegistrationAddedEvent(this, EVENTSRC_ADMIN));
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::Reg::deliver_bundle(Bundle* bundle)
+{
+    // XXX/demmer yuck
+    if (bundle->source() == BundleDaemon::instance()->local_eid()) {
+        log_info("ignoring bundle sent by self");
+        return;
+    }
+
+    u_char type;
+    bundle->payload().read_data(0, 1, &type);
+
+    if (type == DTLSR::MSG_LSA) {
+        log_info("got LSA bundle *%p",bundle);
+        
+        LSA lsa;
+        if (!DTLSR::parse_lsa_bundle(bundle, &lsa)) {
+            log_warn("error parsing LSA");
+            goto bail;
+        }
+        
+        router_->handle_lsa(bundle, &lsa);
+    } else {
+        log_err("unknown message type %d", type);
+    }
+
+bail:
+    BundleDaemon::post_at_head(new BundleDeliveredEvent(bundle, this));
+}
+
+//----------------------------------------------------------------------
+bool
+DTLSRRouter::update_current_lsa(RoutingGraph::Node* node,
+                                Bundle* bundle, u_int64_t seqno)
+{
+    bool found_stale_lsa = false;
+    if (seqno <= node->info().last_lsa_seqno_ &&
+        bundle->creation_ts().seconds_ < node->info().last_lsa_creation_ts_)
+    {
+        log_info("update_current_lsa: "
+                 "ignoring stale LSA (seqno %llu <= last %llu, "
+                 "creation_ts %llu <= last %llu)",
+                 seqno, node->info().last_lsa_seqno_,
+                 bundle->creation_ts().seconds_,
+                 node->info().last_lsa_creation_ts_);
+
+        // suppress forwarding of the stale LSA
+        bundle->fwdlog()->add_entry(EndpointIDPattern::WILDCARD_EID(),
+                                    ForwardingInfo::FORWARD_ACTION,
+                                    ForwardingInfo::SUPPRESSED);
+        
+        return false;
+    }
+    else
+    {
+        log_info("update_current_lsa: "
+                 "got new LSA (seqno %llu > last %llu || "
+                 "creation_ts %llu > last %llu)",
+                 seqno, node->info().last_lsa_seqno_,
+                 bundle->creation_ts().seconds_,
+                 node->info().last_lsa_creation_ts_);
+    }
+
+    oasys::ScopeLock l(current_lsas_.lock(),
+                       "DTLSRRouter::update_current_lsa");
+    for (BundleList::iterator iter = current_lsas_.begin();
+         iter != current_lsas_.end(); ++iter)
+    {
+        if ((*iter)->source() == node->id()) {
+            // be careful not to let the bundle reference count drop
+            // to zero by keeping a local reference until we can make
+            // sure it's at least in the NotNeededEvent
+            BundleRef stale_lsa("DTLSRRouter::update_current_lsa");
+            stale_lsa = *iter;
+
+            current_lsas_.erase(iter);
+
+            log_debug("cancelling pending transmissions for *%p",
+                      stale_lsa.object());
+
+            stale_lsa->fwdlog()->add_entry(EndpointIDPattern::WILDCARD_EID(),
+                                           ForwardingInfo::FORWARD_ACTION,
+                                           ForwardingInfo::SUPPRESSED);
+            
+            BundleDaemon::post_at_head(
+                new BundleDeleteRequest(stale_lsa.object(),
+                                        BundleProtocol::REASON_NO_ADDTL_INFO));
+            found_stale_lsa = true;
+            
+//             oasys::StringBuffer buf("Stale LSA: ");
+//             bundle->format_verbose(&buf);
+//             log_multiline(oasys::LOG_INFO, buf.c_str());
+            
+            if (node->info().last_lsa_seqno_ == 0) {
+                NOTREACHED;
+            }
+            break;
+        }
+    }
+
+    if (node->info().last_lsa_seqno_ == 0) {
+        ASSERT(!found_stale_lsa);
+        log_info("update_current_lsa: "
+                 "first LSA from %s (seqno %llu)",
+                 node->id().c_str(), seqno);
+    } else {
+        log_info("update_current_lsa: "
+                 "replaced %s LSA from %s (seqno %llu) with latest (%llu)",
+                 found_stale_lsa ? "stale" : "expired",
+                 node->id().c_str(), node->info().last_lsa_seqno_, seqno);
+    }
+
+    node->mutable_info().last_lsa_seqno_ = seqno;
+    node->mutable_info().last_lsa_creation_ts_ = bundle->creation_ts().seconds_;
+    current_lsas_.push_back(bundle);
+    return true;
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::handle_lsa(Bundle* bundle, LSA* lsa)
+{
+    // First check the LSA bundle destination to see if the sender is
+    // in a different area
+    std::string lsa_area = bundle->dest().uri().query_value("area");
+    if (config()->area_ != lsa_area) {
+        log_debug("handle_lsa: ignoring LSA since area %s != local area %s",
+                  lsa_area.c_str(), config()->area_.c_str());
+
+        bundle->fwdlog()->add_entry(EndpointIDPattern::WILDCARD_EID(),
+                                    ForwardingInfo::FORWARD_ACTION,
+                                    ForwardingInfo::SUPPRESSED);
+        return;
+    }
+    
+    const EndpointID& source = bundle->source();
+    
+    RoutingGraph::Node* a = graph_.find_node(source.str());
+    if (a == NULL) {
+        log_debug("handle_lsa: adding new source node %s",
+                  source.c_str());
+        a = graph_.add_node(source.str(), NodeInfo());
+    }
+
+    if (! update_current_lsa(a, bundle, lsa->seqno_)) {
+        return; // stale lsa
+    }
+
+    // don't send the LSA back where we got it from
+    ForwardingInfo info;
+    if (bundle->fwdlog()->get_latest_entry(ForwardingInfo::RECEIVED, &info))
+    {
+        log_debug("handle_lsa: suppressing transmission back to %s",
+                  info.remote_eid().c_str());
+        bundle->fwdlog()->add_entry(info.remote_eid(),
+                                    ForwardingInfo::FORWARD_ACTION,
+                                    ForwardingInfo::SUPPRESSED);
+    }
+    
+    // XXX/demmer here we should drop all the links that aren't
+    // present in the LSA...
+
+    // Handle all the link announcements
+    for (LinkStateVec::iterator iter = lsa->links_.begin();
+         iter != lsa->links_.end(); ++iter)
+    {
+        LinkState* ls = &(*iter);
+
+        // check for the case where we're re-receiving an LSA that was
+        // sent previously, but our link configuration has changed so
+        // we no longer have the link
+        if (a == local_node_) {
+            LinkRef link("DTLSRRouter::handle_lsa");
+            link = BundleDaemon::instance()->contactmgr()->
+                   find_link(ls->id_.c_str());
+            if (link == NULL) {
+                log_warn("local link %s in LSA but no longer exists, ignoring...",
+                         ls->id_.c_str());
+                continue;
+            }
+        }
+
+        // find or add the node
+        RoutingGraph::Node* b = graph_.find_node(ls->dest_.str());
+        if (!b) {
+            log_debug("handle_lsa: %s adding new dest node %s",
+                      source.c_str(), ls->dest_.c_str());
+            b = graph_.add_node(ls->dest_.str(), NodeInfo());
+        }
+
+        // try to find and update the edge in the graph, otherwise add it
+        RoutingGraph::Edge *e = NULL;
+        RoutingGraph::EdgeVector::const_iterator ei;
+        for (ei = a->out_edges().begin(); ei != a->out_edges().end(); ++ei) {
+            if ((*ei)->info().id_ == ls->id_) {
+                e = *ei;
+                break;
+            }
+        }
+
+        if (e == NULL) {
+            e = graph_.add_edge(a, b, EdgeInfo(ls->id_, ls->params_));
+            
+            log_debug("handle_lsa: added new edge %s from %s -> %s",
+                      oasys::InlineFormatter<EdgeInfo>().format(e->info()),
+                      a->id().c_str(), b->id().c_str());
+        } else {
+            e->mutable_info().params_ = ls->params_;
+            
+            log_debug("handle_lsa: updated edge %s from %s -> %s",
+                      oasys::InlineFormatter<EdgeInfo>().format(e->info()),
+                      a->id().c_str(), b->id().c_str());
+        }
+
+        // XXX/demmer fold this into a parameter update method
+        e->mutable_info().last_update_.get_time();
+    }
+
+    recompute_routes();
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::handle_lsa_expired(Bundle* bundle)
+{
+    // XXX/demmer this is bogus -- we don't want to drop all routes
+    // just b/c we lost an LSA in all cases, only when we're using the
+    // long-lived LSA bundles
+    (void)bundle;
+    
+    /*
+    const EndpointID& source = bundle->source_;
+    
+    RoutingGraph::Node* a = graph_.find_node(source.str());
+    ASSERT(a != NULL);
+
+    RoutingGraph::EdgeVector::const_iterator ei;
+    for (ei = a->out_edges().begin(); ei != a->out_edges().end(); ++ei) {
+        (*ei)->mutable_info().params_.state_ = LinkParams::LINK_DOWN;
+    }
+
+    recompute_routes();
+    */
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::generate_link_state(LinkState* ls,
+                                 RoutingGraph::Edge* edge,
+                                 const LinkRef& link)
+{
+    ls->dest_.assign(edge->dest()->id());
+    ls->id_.assign(edge->info().id_);
+    ls->params_ = edge->info().params_;
+
+    // XXX/demmer maybe the edge info should be tied to the link
+    // itself somehow?
+    if (link != NULL) {
+        ls->params_.qcount_ = link->bundles_queued();
+        ls->params_.qsize_  = link->bytes_queued();
+    }
+    
+    if (edge->info().last_update_.sec_ != 0) {
+        ls->elapsed_ = edge->info().last_update_.elapsed_ms();
+    } else {
+        ls->elapsed_ = 0;
+    }
+    edge->mutable_info().last_update_.get_time();
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::TransmitLSATimer::timeout(const struct timeval& now)
+{
+    (void)now;
+    router_->send_lsa();
+    if (interval_ != 0) {
+        schedule_in(interval_);
+    }
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::schedule_lsa()
+{
+    if (delayed_lsa_timer_.pending()) {
+        log_debug("schedule_lsa: delayed LSA transmission already pending");
+        return;
+    }
+
+    u_int elapsed = (oasys::Time::now() - last_lsa_transmit_).sec_;
+    if (elapsed > config()->min_lsa_interval_)
+    {
+        log_debug("schedule_lsa: %u seconds since last LSA transmission, "
+                  "sending one immediately", elapsed);
+        send_lsa();
+    }
+    else
+    {
+        u_int delay = std::min(config()->min_lsa_interval_,
+                               config()->min_lsa_interval_ - elapsed);
+        log_debug("schedule_lsa: %u seconds since last LSA transmission, "
+                  "min interval %u, delaying LSA for %u seconds",
+                  elapsed, config()->min_lsa_interval_, delay);
+        delayed_lsa_timer_.schedule_in(delay * 1000);
+    }
+}
+
+//----------------------------------------------------------------------
+void
+DTLSRRouter::send_lsa()
+{
+    char tmp[64]; (void)tmp;
+    LSA lsa;
+
+    // the contents of last_lsa_seqno get updated in the call to
+    // update_current_lsa below
+    lsa.seqno_ = local_node_->info().last_lsa_seqno_ + 1;
+    
+    RoutingGraph::EdgeVector::iterator ei;
+    for (ei = local_node_->out_edges().begin();
+         ei != local_node_->out_edges().end();
+         ++ei)
+    {
+        LinkState ls;
+        LinkRef link("DTLSRRouter::send_lsa");
+
+        // if the edge is a local registration, there's no link, we
+        // just pretend there's a zero-delay, infinite-bandwidth link
+        // to the other endpoint
+        if (! (*ei)->info().is_registration_) {
+            link = BundleDaemon::instance()->contactmgr()->
+                   find_link((*ei)->info().id_.c_str());
+            ASSERT(link != NULL);
+        }
+        
+        generate_link_state(&ls, *ei, link);
+        lsa.links_.push_back(ls);
+    }
+
+    log_debug("send_lsa: generated %zu link states for local node",
+              lsa.links_.size());
+
+    Bundle* bundle = new TempBundle();
+
+    if (config()->area_ != "") {
+        snprintf(tmp, sizeof(tmp), "dtn://*/%s?area=%s;lsa_seqno=%llu",
+                 announce_tag_, config()->area_.c_str(), lsa.seqno_);
+    } else {
+        snprintf(tmp, sizeof(tmp), "dtn://*/%s?lsa_seqno=%llu",
+                 announce_tag_, lsa.seqno_);
+    }
+    bundle->mutable_dest()->assign(tmp);
+    
+    bundle->mutable_source()->assign(BundleDaemon::instance()->local_eid());
+    bundle->mutable_replyto()->assign(EndpointID::NULL_EID());
+    bundle->mutable_custodian()->assign(EndpointID::NULL_EID());
+    bundle->set_expiration(config()->lsa_lifetime_);
+    bundle->set_singleton_dest(false);
+    DTLSR::format_lsa_bundle(bundle, &lsa);
+
+    update_current_lsa(local_node_, bundle, lsa.seqno_);
+    
+    log_debug("send_lsa: formatted LSA bundle *%p", bundle);
+
+    // XXX/demmer this would really be better done with
+    // BundleActions::inject_bundle
+    BundleDaemon::post_at_head(new BundleReceivedEvent(bundle, EVENTSRC_ROUTER));
+
+    last_lsa_transmit_.get_time();
+}
+
+} // namespace dtn
+