servlib/routing/TcaRouter.cc
changeset 0 2b3e5ec03512
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlib/routing/TcaRouter.cc	Thu Apr 21 14:57:45 2011 +0100
@@ -0,0 +1,977 @@
+/*
+ *    Copyright 2005-2006 University of Waterloo
+ * 
+ *    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 "conv_layers/ConvergenceLayer.h"
+#include "bundling/BundleActions.h"
+#include "contacts/ContactManager.h"
+#include "bundling/BundleDaemon.h"
+#include "routing/RouteTable.h"
+#include  <oasys/io/NetUtils.h>
+#include "contacts/InterfaceTable.h"
+#include "conv_layers/TCPConvergenceLayer.h"
+
+#include "TcaRouter.h"
+
+namespace dtn {
+
+
+
+// Consts
+
+static const std::string BL = "tca://localhost/bundlelayer";
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+// get_payload_str is a convenient one-call function to get the payload
+// of a simple bundle (must not contain any nulls)
+static std::string
+get_payload_str(const Bundle* b)
+{
+    size_t len = b->payload().length();
+    u_char data[len+1];
+    const u_char* p = b->payload().read_data(0, len, data);
+    return (const char*)p;
+}
+
+
+// debug: check for expected number of args in control bundle, w/ message
+static bool
+check_nargs(const TcaControlBundle& cb, uint n_expected)
+{
+    if (cb.args_.size() != n_expected)
+    {
+        log_err_p("dtn/tca", "TcaRouter: bundle '%s' contains wrong number "
+                  "of args. %d expected.", cb.str().c_str(), n_expected);
+        return false;
+    }
+    return true;
+}
+
+
+// debug: log bundle wrapper (source -> dest) and optionally payload too
+static void
+log_bundle(const std::string& comment, const Bundle* b, bool include_payload)
+{
+    (void)comment;
+    (void)b;
+    
+    if (include_payload)
+        log_debug_p("/dtn/tca", "%s [%s] -> [%s] : '%s'", comment.c_str(),
+                    b->source().str().c_str(), b->dest().c_str(),
+                    get_payload_str(b).c_str());
+    else
+        log_debug_p("/dtn/tca", "%s [%s] -> [%s]", comment.c_str(),
+                    b->source().str().c_str(), b->dest().c_str());
+} 
+    
+
+static void
+log_controlbundle(const TcaControlBundle& cb)
+{
+    log_debug_p("/dtn/tca", "    code='%s', args=%d",
+                cb.code_.c_str(), (u_int)cb.args_.size());
+    for (unsigned int i=0; i<cb.args_.size(); ++i)
+    {
+        log_debug_p("/dtn/tca", "        '%s'", cb.args_[i].c_str());
+    }
+}
+
+
+/*
+// get_tcp_interface gets the "tcp0" Interface object.
+// No longer neeed.
+static Interface* get_tcp_interface()
+{
+    InterfaceTable* p_iftab = InterfaceTable::instance();
+    return p_iftab->find("tcp0");
+}
+*/
+
+///////////////////////////////////////////////////////////////////////////////
+// class TcaRouter
+
+TcaRouter::TcaRouter(Role role)
+    : TableBasedRouter("TcaRouter", "TcaRouter")
+{
+    role_ = role;
+
+    // Construct the eid of the local admin app.
+    admin_app_ = BundleDaemon::instance()->local_eid();
+    admin_app_.set_app("admin");
+
+    logpathf("/dtn/tca");
+
+    log_info("TcaRouter started: role='%s', admin_app='%s'",
+             get_role_str().c_str(), admin_app_.c_str());
+}
+
+
+std::string
+TcaRouter::get_role_str() const
+{
+    switch (role_)
+    {
+        case TCA_MOBILE: return "mobile";
+        case TCA_ROUTER: return "router";
+        case TCA_GATEWAY: return "gateway";
+        default: return "null";
+    }
+
+}
+
+
+void
+TcaRouter::handle_bundle_received(BundleReceivedEvent* event)
+{           
+    Bundle* bundle = event->bundleref_.object();
+    // log_debug("TcaRouter: handle bundle received: *%p", bundle);
+
+    // Special handling for certain TCA bundles
+    // Control bundles are identified by their dest eids.
+    // Unbound data bundles need special handling as
+    // TCA control bundles are the following:
+    // Check for control bundles here (REG, COA, ASK, ADV)
+
+    const EndpointID& dest = bundle->dest();
+
+    if (dest.scheme_str() == "tca")
+    {
+        log_bundle("TcaRouter: tca bundle received", bundle, true);
+
+        TcaEndpointID tca_dest(dest);
+  
+        if (tca_dest.ssp() == "//registry")
+        {
+            handle_register(bundle);
+        }
+
+        else if (tca_dest.app() == "admin.coa")
+        {
+            handle_coa(bundle);
+        }
+
+        else if (tca_dest.ssp().substr(0,11) == "//anonymous")
+        {
+            handle_anonymous_bundle(bundle);
+        }
+
+        else if (tca_dest.ssp() == "//localhost/bundlelayer")
+        {
+            handle_bl_control_bundle(bundle);
+        }
+            
+        else if (tca_dest.host() != admin_app_.host())
+        {
+            // What we're really checking for is whether the bundle is home.
+            // ie. addressed to some app on the local host.
+            // If not, then it's a late-bound data bundle:
+            handle_tca_unbound_bundle(bundle);
+        }
+    }
+    
+    else
+    {
+        // Not a tca control or data bundle. Just forward the bundle.
+        fwd_to_matching(bundle);
+    }
+}
+
+
+void
+TcaRouter::handle_bundle_transmitted(BundleTransmittedEvent* event)
+{
+    Bundle* b = event->bundleref_.object();
+    log_debug("TcaRouter: handle bundle transmitted: *%p", b);
+
+    const EndpointID& dest = b->dest();
+
+    if (dest.scheme_str() == "tca")
+    {
+        // TODO: These control codes appended to app (eg. admin.coa) were
+        // a bad idea and should be eliminated. I did this initially so that
+        // we could switch on control code without inspecting bundle payload.
+        // However, if the bundle is addressed to an admin app, then it's
+        // fair to assume it is a control bundle and inspect the payload.
+        // The "adv" case below uses the preferred method. ask and coa should
+        // follow suit.
+        TcaEndpointID tca_dest(dest);
+        if (tca_dest.app() == "admin.coa")
+        {
+            TcaControlBundle cb(get_payload_str(b));
+            on_coa_transmitted(b, cb);
+        }
+        else if (tca_dest.app() == "admin.ask")
+        {
+            TcaControlBundle cb(get_payload_str(b));
+            on_ask_transmitted(b, cb);
+        }
+        else if (tca_dest.app() == "admin")
+        {
+            TcaControlBundle cb(get_payload_str(b));
+            if (cb.code_ == "adv")
+            {
+                on_adv_transmitted(b, cb);
+            }
+        }
+    }
+}   
+ 
+
+// Note: ContactUp and ContactDown are generated by a sending node when a
+// connection is opened.  As such, they're probably not relevant to TCA.
+
+void
+TcaRouter::handle_contact_up(ContactUpEvent* event)
+{
+    ASSERT(event->contact_->link() != NULL);
+    ASSERT(!event->contact_->link()->isdeleted());
+
+    // Note: *must* call the base class handler so that existing bundles
+    // can be checked against the new contact.
+    TableBasedRouter::handle_contact_up(event);
+    log_debug("TcaRouter::contact up");
+    post_bundle(BL, admin_app_, "contact_up");
+}
+    
+
+void
+TcaRouter::handle_contact_down(ContactDownEvent* event)
+{
+    (void)event;
+    log_debug("TcaRouter::contact down");
+    post_bundle(BL, admin_app_, "contact_down");
+
+}
+
+
+void
+TcaRouter::handle_link_available(LinkAvailableEvent* event)
+{
+    ASSERT(event->link_ != NULL);
+    ASSERT(!event->link_->isdeleted());
+
+    // Note: *must* call the base class handler so that existing bundles
+    // can be checked against the new link.
+    TableBasedRouter::handle_link_available(event);
+    log_debug("TcaRouter::link available");
+    post_bundle(BL, admin_app_, "link_available");
+}
+
+
+void
+TcaRouter::handle_link_unavailable(LinkUnavailableEvent* event)
+{
+    (void)event;
+    log_debug("TcaRouter::link unavailable");
+    post_bundle(BL, admin_app_, "link_unavailable");
+}
+
+
+void
+TcaRouter::handle_shutdown_request(ShutdownRequest* event)
+{
+    (void)event;
+    log_debug("TcaRouter::daemon shutdown");
+    post_bundle(BL, admin_app_, "daemon_shutdown");
+}
+        
+
+// fwd_to_all "broadcasts" a bundle to all tca routes (ie. "tca://*")
+// This is dead code at the moment.
+
+int
+TcaRouter::fwd_to_all(Bundle* bundle)
+{
+    RouteEntryVec matches;
+    RouteEntryVec::iterator iter;
+
+    std::string pattern = "tca://*";
+    EndpointID tca_all = pattern;
+
+    route_table_->get_matching(tca_all, &matches);
+
+    int count = 0;
+    for (iter = matches.begin(); iter != matches.end(); ++iter)
+    {
+        log_debug("TcaRouter::fwd_to_all: %s",
+                  (*iter)->dest_pattern().str().c_str());
+        fwd_to_nexthop(bundle, *iter);
+        ++count;
+    }
+
+    log_debug("TcaRouter::fwd_to_all dest='%s': %d matches",
+              bundle->dest().c_str(), count);
+    return count;
+}
+
+
+// fwd_to_matching function overridden from TableBasedRouter
+// This is necessary to filter outgoing bundles. fwd_to_matching is
+// called via TableBasedRouter::check_next_hop on a variety of events
+// including link_available and on contact_up.
+
+int
+TcaRouter::fwd_to_matching(Bundle* bundle, const LinkRef& next_hop)
+{
+    ForwardingRule fwd_rule = get_forwarding_rule(bundle);
+    return fwd_to_matching_r(bundle, next_hop, fwd_rule);
+}
+
+
+// Specialized fwd_to_matching function with selective default-route handling
+// You can foward to the default route either never, always, or "if necessary"
+// where "if necessary" means iff there are no non-default routes.
+
+int
+TcaRouter::fwd_to_matching_r(Bundle* bundle, const LinkRef& next_hop,
+                             ForwardingRule fwd_rule)
+{
+    log_debug("TcaRouter::fwd_to_matching_r: owner='%s'",
+              bundle->owner().c_str());
+    log_debug("TcaRouter::fwd_to_matching_r: fwd_rule=%d", fwd_rule);
+
+    if (fwd_rule == FWD_NEVER) return 0;    // no matches
+
+    RouteEntryVec matches;
+    RouteEntryVec::iterator iter;
+
+    route_table_->get_matching(bundle->dest(), &matches);
+
+    // First step - split out the default route
+    RouteEntry*     default_route = NULL;   
+    RouteEntryVec   hard_matches;       // non-default matches
+    for (iter = matches.begin(); iter != matches.end(); ++iter)
+    {
+        if ((*iter)->dest_pattern().str() == "tca://*")
+        {
+            default_route = *iter;
+        }
+        else
+        {
+            hard_matches.push_back(*iter);
+        }
+    }
+
+    if (fwd_rule == FWD_UDR_EXCLUSIVELY)
+    {
+        if (default_route != NULL)
+        {
+            fwd_to_nexthop(bundle, default_route);
+            return 1;   // 1 match
+        }
+    }
+
+    int count = 0;
+
+    // handle default route...
+    if (fwd_rule == FWD_UDR_ALWAYS || 
+            (fwd_rule == FWD_UDR_IFNECESSARY && hard_matches.size() == 0))
+    {
+        if (default_route != NULL)
+        {
+            fwd_to_nexthop(bundle, default_route);
+            ++ count;
+        }
+    }
+
+    // handle hard matches...
+    for (iter = hard_matches.begin(); iter != hard_matches.end(); ++iter)
+    {
+        if (next_hop == NULL || (next_hop == (*iter)->link()))
+        {
+            fwd_to_nexthop(bundle, *iter);
+            ++count;
+        }
+        else
+        {
+            log_debug("fwd_to_matching_r dest='%s': "
+                      "ignoring match %s since next_hop link %s set",
+                      bundle->dest().c_str(), (*iter)->link()->name(),
+                      next_hop->name());
+        }
+    }
+
+    log_debug("fwd_to_matching_r dest='%s': %d matches",
+              bundle->dest().c_str(), count);
+
+    return count;
+}
+
+
+bool
+TcaRouter::on_coa_transmitted(Bundle* b, const TcaControlBundle& cb)
+{   
+    log_debug("TcaRouter: COA bundle transmitted");
+            
+    TcaWrappedBundle wb(cb);
+    
+    log_debug("    coa: source=%s, dest=%s",
+                b->source().c_str(),
+                b->dest().c_str());
+    
+    // todo: use a WrappedBundle here
+    std::string coa_sent_payload = "coa_sent:";
+    coa_sent_payload += b->source().str();
+    coa_sent_payload += "\t";
+    coa_sent_payload += b->dest().str();
+    coa_sent_payload += "\t";
+    coa_sent_payload += cb.args_[0]; // link_addr of mobile, from body of coa
+
+    log_debug("    coa_sent, payload='%s'", coa_sent_payload.c_str());
+    post_bundle(BL, admin_app_, coa_sent_payload);
+
+    return true;
+}
+
+
+bool
+TcaRouter::on_ask_transmitted(Bundle* b, const TcaControlBundle& cb)
+{
+    log_debug("TcaRouter: ASK bundle transmitted");
+
+    if (!check_nargs(cb, 1)) return false;
+
+    // todo: use a WrappedBundle here
+    std::string ask_sent= "ask_sent:";
+    ask_sent += b->source().str();
+    ask_sent += "\t";
+    ask_sent += b->dest().str();
+    ask_sent += "\t";
+    ask_sent += cb.args_[0];
+
+    log_debug("    ask sent, payload='%s'", ask_sent.c_str());
+    post_bundle(BL, admin_app_, ask_sent);
+
+    return false;
+}
+
+
+bool
+TcaRouter::on_adv_transmitted(Bundle* b, const TcaControlBundle& cb)
+{
+    log_debug("TcaRouter: ADV bundle transmitted");
+
+    if (!check_nargs(cb, 2)) return false;
+
+    // todo: use a WrappedBundle here
+    std::string adv_sent= "adv_sent:";
+    adv_sent += b->source().str();
+    adv_sent += "\t";
+    adv_sent += b->dest().str();
+    adv_sent += "\t";
+    adv_sent += cb.args_[0];
+    adv_sent += "\t";
+    adv_sent += cb.args_[1];
+
+    log_debug("    adv_sent, payload='%s'", adv_sent.c_str());
+    post_bundle(BL, admin_app_, adv_sent);
+
+    return false;
+}
+
+
+bool
+TcaRouter::handle_register(Bundle* b)
+{
+    // DK: New stuff - experimental
+    // Register bundles come either from
+    // 1) A client application (zero args, source eid important), or
+    // 2) An admin app (2 args)
+    
+    if (b->source().str() == admin_app_.str())
+    {
+        // The local admin app sent this bundle. Just forward it to the
+        // default route:
+        LinkRef link("TcaRouter::handle_register: fwd_to_matching null");
+        fwd_to_matching_r(b, link, FWD_UDR_EXCLUSIVELY);
+    }
+    else
+    {
+        // The bundle came from either a local app (non-admin) or
+        // else another node. In either case, deliver it to local
+        // admin app.
+        TcaControlBundle cb(get_payload_str(b));
+
+        TcaWrappedBundle reg_received("reg_received",
+                b->source().str(), b->dest().str());
+
+        log_debug("TcaRouter::handle_register:");
+        log_controlbundle(cb);
+
+        if (cb.args_.size() == 2)
+        {
+            // This bundle came from another node, with mobile_eid
+            // and last_hop fields filled in.
+            reg_received.append_arg(cb.args_[0]);
+            reg_received.append_arg(cb.args_[1]);
+        }
+        else
+        {
+            // This bundle came from an app on the local node
+            // so fill in mobile_eid and NULL last_hop
+            reg_received.append_arg(b->source().str());
+            reg_received.append_arg("NULL");
+        }
+        
+        post_bundle(BL, admin_app_, reg_received.str());
+    }
+
+    return true;
+}
+
+
+bool
+TcaRouter::handle_coa(Bundle* b)
+{
+    log_debug("TcaRouter: COA bundle received");
+
+    // Propagate it along the reverse path, ignoring default route:
+    LinkRef link("TcaRouter::handle_coa: fwd_to_matching null");
+    fwd_to_matching_r(b, link, FWD_UDR_NEVER);
+
+    // The old route is deleted after the coa bundle has been
+    // transmitted, from on_coa_transmitted
+    return true;
+}
+
+
+bool
+TcaRouter::handle_anonymous_bundle(Bundle* b)
+{
+    // these are bundles addressed to a destination eid that begins
+    // with tca://anonymous
+    // so far, the "ask" bundle is the only one of its kind
+
+    TcaEndpointID dest(b->dest());
+
+    TcaControlBundle cb(get_payload_str(b));
+
+    if (cb.code_ == "ask")
+    {
+        return handle_ask(b, cb);
+    }
+    else
+    {
+        log_debug("TcaRouter:: unrecognized anonymous bundle code '%s'",
+                cb.code_.c_str());
+        return false;
+    }
+}
+
+
+bool
+TcaRouter::handle_ask(Bundle* b, const TcaControlBundle& cb)
+{   
+    if (is_local_source(b))
+    {
+        // if the ask originated at this node, just forward it to
+        // matching, omitting the default route.
+        LinkRef link("TcaRouter::handle_ask: fwd_to_matching null");
+        fwd_to_matching_r(b, link, FWD_UDR_NEVER);
+    }
+    else
+    {
+        if (!check_nargs(cb, 1)) return false;
+    
+        // generate ask_received for local admin app
+        std::string payload = "ask_received:";
+        payload += b->source().str();
+        payload += "\t";
+        payload += b->dest().str();
+        payload += "\t";
+        payload += cb.args_[0];
+
+        post_bundle(BL, admin_app_, payload);
+    }
+
+    return true;
+}
+
+
+
+// handle_tca_control_bundle handles bundles addressed to
+// "localhost/bundlelayer" from the local control application
+
+bool
+TcaRouter::handle_bl_control_bundle(Bundle* b)
+{
+    TcaControlBundle cb(get_payload_str(b));
+
+    // handle ctl bundles:
+    if (cb.code_ == "ask")
+    {
+        return handle_ask(b, cb);
+    }
+    else if (cb.code_ == "get_routes")
+    {
+        return handle_get_routes(b, cb);
+    }
+    else if (cb.code_ == "add_route")
+    {
+        return handle_add_route(b, cb);
+    }
+    else if (cb.code_ == "del_route")
+    {
+        return handle_del_route(b, cb);
+    }
+
+    log_debug("TcaRouter: unknown control bundle type '%s'", cb.code_.c_str());
+    return false;
+    
+}
+
+
+bool
+TcaRouter::handle_bl_ask(Bundle* b, const TcaControlBundle& cb)
+{
+    (void)cb;
+    // Note: We should never get here! asks should be addressed to the
+    // control app, not bundle layer.
+    return post_bundle(BL, b->source(),
+        "adv:Don\'t ASK me. You should probably ASK the Control App,"
+        " not the Bundle Layer.");
+}
+
+
+bool
+TcaRouter::handle_get_routes(Bundle* b, const TcaControlBundle& cb)
+{   
+    if (!check_nargs(cb, 1)) return false;
+
+    log_debug("TcaRouter:: get_routes bundle received. body = '%s'",
+           cb.args_[0].c_str());
+
+    RouteEntryVec matches;
+    RouteEntryVec::iterator iter;
+
+    EndpointIDPattern pattern(cb.args_[0]);
+    route_table_->get_matching(pattern, &matches);
+
+    std::string response = "routes:";
+    for (iter = matches.begin(); iter != matches.end(); ++iter)
+    {
+        response += (*iter)->dest_pattern().str().c_str();
+        response += "\t";
+    }
+
+    post_bundle(BL, b->source(), response);
+
+    return true;
+}
+
+
+bool
+TcaRouter::handle_add_route(Bundle* b, const TcaControlBundle& cb)
+{
+    (void)b;
+    
+    if (!check_nargs(cb, 2)) return false;
+
+    const std::string& pattern = cb.args_[0];
+    const std::string& link = cb.args_[1];
+
+    log_debug("TcaRouter:: add_route bundle received. "
+              "pattern='%s', link='%s'\n", pattern.c_str(), link.c_str());
+
+    if (pattern.length() == 0 || link.length() == 0) return false;
+
+    // TODO: Some syntax-checking would be a very good idea. Right now,
+    // just blast away:
+    return create_route(pattern, link);
+}
+
+
+bool
+TcaRouter::handle_del_route(Bundle* b, const TcaControlBundle& cb)
+{
+    (void)b;
+    
+    if (!check_nargs(cb, 1)) return false;
+
+    log_debug("TcaRouter:: del_route bundle received. body = '%s'",
+           cb.args_[0].c_str());
+
+    // TODO: Some syntax-checking would be a very good idea. Right now,
+    // just blast away:
+    route_table_->del_entries(cb.args_[0]);
+    return true;
+}
+
+
+// handle_tca_unbound_bundle handles routing of regular late-bound tca data
+// bundles.
+// This function assumes that this router is not the dest endpoint.
+// for the bundle. The logic goes like this:
+// First, forward the bundle to any existing route to dest, using the default
+// route iff there are no other matches.
+// If there are no matches at all, and if the local node is a gateway,
+// then push a "unb" bundle up to the control app for special handling.
+
+bool
+TcaRouter::handle_tca_unbound_bundle(Bundle* bundle)
+{
+    log_debug("TcaRouter::handle_tca_unbound_bundle...");
+
+    LinkRef link("TcaRouter::handle_tca_unbound_bundle: fwd_to_matching null");
+    int n_matches = fwd_to_matching_r(bundle, link, FWD_UDR_IFNECESSARY);
+
+    if (n_matches == 0)
+    {
+        if (role_ == TCA_ROUTER)
+        {
+            // If I'm a router, this is an error! All tca bundles must have
+            // a default route up to a gateway.
+            log_err("TcaRouter: Error. TCA_ROUTER has no route to dest %s",
+                    bundle->dest().c_str());
+            return false;
+        }
+        else if (role_ == TCA_GATEWAY)
+        {
+            // If I'm a gateway, try late-binding...
+            // Leave the unbound bundle alone, but push a control bundle up
+            // to the control app, which will get registration and create a
+            // route if possible.
+            std::string payload = "unb:";
+            payload += bundle->dest().str();
+            post_bundle(BL, admin_app_, payload);
+        }
+    }
+    return true;
+}
+
+
+
+bool
+TcaRouter::is_local_source(Bundle* b)
+{
+    TcaEndpointID src(b->source());
+    return src.get_hostid() == admin_app_.get_hostid();
+}
+
+
+TcaRouter::ForwardingRule
+TcaRouter::get_forwarding_rule(Bundle* b)
+{
+    // all non-tca bundles should always be forwarded to all routes
+    if (b->dest().scheme_str() != "tca") return FWD_UDR_ALWAYS;
+    
+    TcaEndpointID dest(b->dest());
+
+    if (dest.ssp() == "//registry")
+    {
+        // forward to default route (exclusively) iff sent by the local
+        // admin app
+        if (b->source().str() == admin_app_.str()) return FWD_UDR_EXCLUSIVELY;
+        else return FWD_NEVER;
+    }
+
+    else if (dest.app() == "admin.coa")
+    {
+        // Never us the default route for the coa bundles. They are on their
+        // way "down" the tree to a mobile and we don't want them getting
+        // forwarded back up to a gateway too.
+        return FWD_UDR_NEVER;
+    }
+
+    else if (dest.ssp().substr(0,11) == "//anonymous")
+    {
+        // Assume this is an ask bundle. (WARNING: If there are ever any
+        // anonymous bundles other than ASK, this should be changed!)
+        // For ask, forward only if originating at local node, and never
+        // to the default route.
+        if (is_local_source(b)) return FWD_UDR_NEVER;
+        else return FWD_NEVER;
+    }
+
+    else if (dest.ssp() == "//localhost/bundlelayer")
+    {
+        // Never forward bundles sent to the local bundle layer
+        return FWD_NEVER;
+    }
+
+    // These are all the special control dest cases. What remains are any
+    // ordinarily addressed tca bundles
+    else
+    {
+        if (dest.host() == admin_app_.host())
+        {
+            // If addressed to local admin app, do not forward. This bundle
+            // is home.
+            return FWD_NEVER;
+        }
+        else
+        {
+            // Treat this as an unbound tca bundle. Forward to matches, using
+            // default route iff no other matches.
+            return FWD_UDR_IFNECESSARY;
+        }
+    }
+}
+
+
+// create_link creates a link for the given link_addr iff it doesn't already
+// exist.
+// link_addr must be in the form "tcp://host:port"
+
+LinkRef
+TcaRouter::create_link(const std::string& link_addr)
+{
+    // Note that deleting the old one and re-creating it is the wrong
+    // thing to do, because when the link is re-created all pending
+    // bundles (inlcuding the register bundle that initiated this) will
+    // be matched and forwarded to the new link.
+
+    EndpointID link_eid(link_addr);
+    std::string clayer_name = link_eid.scheme_str();
+    const std::string& ssp = link_eid.ssp();
+    std::string host = ssp.substr(2, ssp.length()); // chop off the "//"
+
+    ContactManager* p_man = BundleDaemon::instance()->contactmgr();
+
+    // Check if there's an existing link of the same name.
+    LinkRef p_link("TcaRouter::create_link: return value");
+           
+    p_link = p_man->find_link(host.c_str());
+    if (p_link != NULL) return p_link;
+
+    ConvergenceLayer* cl = ConvergenceLayer::find_clayer(clayer_name.c_str());
+    if (!cl) {
+        log_err("TcaRouter: create_link failed: invalid convergence layer"
+                  " '%s'", clayer_name.c_str());
+        return p_link;
+    }
+ 
+    p_link = Link::create_link(host, Link::ONDEMAND, cl, host.c_str(), 0, NULL);
+    if (p_link == NULL) return p_link;
+        
+    // Add the link to contact manager's table, which posts a
+    // LinkCreatedEvent to the daemon
+    if (!p_man->add_new_link(p_link)) {
+        p_link->delete_link();
+        log_err("TcaRouter::create_link: "
+                "failed to add new link %s", p_link->name());
+        p_link = NULL;
+        return p_link;
+    }
+
+    return p_link;
+}
+
+
+RouteEntry*
+TcaRouter::create_route(const std::string& pattern, const LinkRef& p_link)
+{
+
+    log_debug("TcaRouter::create_route: pattern=%s, p_link=%p",
+            pattern.c_str(), p_link.object());
+
+    RouteEntry* p_entry = new RouteEntry(pattern, p_link);
+    p_entry->set_action(ForwardingInfo::COPY_ACTION);
+
+    route_table_->add_entry(p_entry);
+
+    return p_entry;
+}
+
+
+bool
+TcaRouter::create_route(const std::string& pattern,
+                        const std::string& link_addr)
+{
+    // First find the right link, or create a new one if necesary
+    LinkRef p_link = create_link(link_addr);
+    if (p_link == NULL)
+    {
+        log_err("TcaRouter::create_route: create_link failed");
+        return false;
+    }
+
+    // Now create the new route entry
+    if (!create_route(pattern, p_link))
+    {
+        log_err("TcaRouter::create_route: create_route failed");
+        return false;
+    }
+
+    return true;
+}
+
+
+bool
+TcaRouter::post_bundle(const EndpointID& src, const EndpointID& dest,
+                       const std::string& payload)
+{
+
+    log_debug("TcaRouter::post_bundle: [%s] -> [%s] : '%s'\n",
+                src.c_str(), dest.c_str(), payload.c_str());
+
+    // Construct bundle
+    Bundle* b = new Bundle();
+
+    // if source is unspecified, use bundlelayer
+    if (src.length() == 0)
+        b->mutable_source()->assign("tca://localhost/bundlelayer");
+    else
+        b->mutable_source()->assign(src);
+
+    b->mutable_dest()->assign(dest);
+    b->mutable_custodian()->assign(BundleDaemon::instance()->local_eid());
+    b->mutable_replyto()->assign(BundleDaemon::instance()->local_eid());
+
+    b->mutable_payload()->set_data(payload);
+
+    // We need to set non-zero expiration or else the bundle
+    // expires as soon as it arrives.
+    b->set_expiration(3600);
+
+    // The default values are ok for the rest of the bundle fields.
+
+    // Post the bundle by generating a BundleReceivedEvent
+    BundleReceivedEvent* p_event = new BundleReceivedEvent(b, EVENTSRC_ADMIN);
+    BundleDaemon::instance()->post(p_event);
+
+    return true;
+}
+
+
+// dead code at the moment:
+bool
+TcaRouter::push_wrapped_bundle(const std::string& code,
+                               const EndpointID& src,
+                               const EndpointID& dest,
+                               const std::string& bsp)
+{
+    std::string payload = code;
+    payload += ":";
+    payload += src.str();
+    payload += "\t";
+    payload += dest.str();
+    payload += "\t";
+    payload += bsp;
+    return post_bundle(BL, admin_app_, payload);
+}
+
+
+
+
+} // namespace dtn