[Xorp-hackers] [RFC 3/3] static-mcast: Add support for static multicast routes.
greearb at candelatech.com
greearb at candelatech.com
Mon Jul 9 12:04:18 PDT 2012
From: Ben Greear <greearb at candelatech.com>
This adds the ability to configure static multicast routes
using the static-route module.
Example config file looks like:
interfaces {
interface "p3p1" {
disable: false
default-system-config
}
interface "wlan0" {
disable: false
default-system-config
}
}
fea {
unicast-forwarding4 {
disable: false
}
}
protocols {
static {
route 10.2.46.91/16 {
next-hop: 10.2.46.20
metric: 1
}
mrib-route 10.2.46.0/16 {
next-hop: 10.2.46.30
metric: 1
}
mcast-route 226.0.0.1 {
input_if: "p3p1"
input_ip: 192.168.9.11
output_ifs: "wlan0"
distance: 2
}
}
}
plumbing {
mfea4 {
disable: false
interface "p3p1" {
vif "p3p1" {
disable: false
}
}
interface "wlan0" {
vif "wlan0" {
disable: false
}
}
interface "register_vif" {
vif "register_vif" {
disable: false
}
}
} /* mfea4 */
} /* plumbing */
Signed-off-by: Ben Greear <greearb at candelatech.com>
---
xorp/etc/templates/static_routes.tp | 39 +++-
xorp/static_routes/SConscript | 1 +
xorp/static_routes/static_routes_node.cc | 185 +++++++++++
xorp/static_routes/static_routes_node.hh | 214 ++++++++-----
xorp/static_routes/xorp_static_routes.cc | 5 +-
xorp/static_routes/xrl_static_routes_node.cc | 459 +++++++++++++++++++++++++-
xorp/static_routes/xrl_static_routes_node.hh | 131 +++++++-
xorp/xrl/interfaces/static_routes.xif | 8 +-
xorp/xrl/targets/static_routes.tgt | 3 +-
9 files changed, 956 insertions(+), 89 deletions(-)
diff --git a/xorp/etc/templates/static_routes.tp b/xorp/etc/templates/static_routes.tp
index f8359d0..9f69c54 100644
--- a/xorp/etc/templates/static_routes.tp
+++ b/xorp/etc/templates/static_routes.tp
@@ -1,4 +1,3 @@
-/* $XORP: xorp/etc/templates/static_routes.tp,v 1.43 2008/08/06 08:23:24 abittau Exp $ */
protocols {
static {
@@ -26,6 +25,13 @@ protocols {
}
}
+ mcast-route @: ipv4 {
+ input_if: txt;
+ input_ip: ipv4;
+ output_ifs: txt;
+ distance: u32 = 0;
+ }
+
route4 @: ipv4net { /* %deprecated */
next-hop: ipv4;
nexthop: ipv4; /* %deprecated */
@@ -284,6 +290,37 @@ protocols {
}
}
+ mcast-route @: ipv4 {
+ %help: short "Configure a static Multicast route";
+ %mandatory: $(@.input_if), $(@.input_ip), $(@.output_ifs);
+
+ %create: xrl "$(static.targetname)/static_routes/0.1/add_mcast_route4?mcast_addr:ipv4=$(@)&input_if:txt=$(@.input_if)&input_ip:ipv4=$(@.input_ip)&output_ifs:txt=$(@.output_ifs)&distance:u32=$(@.distance)";
+ %update: xrl "$(static.targetname)/static_routes/0.1/replace_mcast_route4?mcast_addr:ipv4=$(@)&input_if:txt=$(@.input_if)&input_ip:ipv4=$(@.input_ip)&output_ifs:txt=$(@.output_ifs))&distance:u32=$(@.distance)";
+ %delete: xrl "$(static.targetname)/static_routes/0.1/delete_mcast_route4?mcast_addr:ipv4=$(@)&input_ip:ipv4=$(@.input_ip)";
+
+ input_if {
+ %help: short "Configure the input interface";
+ %set:;
+ }
+
+ input_ip {
+ %help: short "Configure the input IP address";
+ %set:;
+ }
+
+ output_ifs {
+ %help: short "Configure the input output interface(s)";
+ %set:;
+ }
+
+ distance {
+ %help: short "Configure the routing distance. Lower value wins";
+ %allow-range: $(@) "0" "7" %help: "The routing distance";
+ %set:;
+ }
+ }
+
+
route4 @: ipv4net {
%deprecated: "Statement 'route4' is replaced with 'route'";
%help: short "Configure an IPv4 static route";
diff --git a/xorp/static_routes/SConscript b/xorp/static_routes/SConscript
index 583432a..5b69ef9 100644
--- a/xorp/static_routes/SConscript
+++ b/xorp/static_routes/SConscript
@@ -51,6 +51,7 @@ env.AppendUnique(LIBS = [
'xst_fea_ifmgr_mirror',
'xst_static_routes',
'xif_rib',
+ 'xif_mfea',
'xif_finder_event_notifier',
'xorp_policy_backend',
'xorp_policy_common',
diff --git a/xorp/static_routes/static_routes_node.cc b/xorp/static_routes/static_routes_node.cc
index 06a8a9f..f8934ad 100644
--- a/xorp/static_routes/static_routes_node.cc
+++ b/xorp/static_routes/static_routes_node.cc
@@ -121,6 +121,9 @@ StaticRoutesNode::shutdown()
//
rib_register_shutdown();
+ // De-register with the MFEA
+ rib_register_shutdown();
+
//
// De-register with the FEA
//
@@ -306,6 +309,7 @@ StaticRoutesNode::updates_made()
StaticRoutesNode::Table::iterator route_iter;
list<StaticRoute *> add_routes, replace_routes, delete_routes;
list<StaticRoute *>::iterator pending_iter;
+ list<McastRoute *>::iterator mpending_iter;
for (route_iter = _static_routes.begin();
route_iter != _static_routes.end();
@@ -395,6 +399,80 @@ StaticRoutesNode::updates_made()
}
}
+ // Deal with mcast-routes
+ list<McastRoute *> add_mroutes, replace_mroutes, delete_mroutes;
+ map<IPvX, McastRoute>::iterator mroute_iter;
+ for (mroute_iter = _mcast_routes.begin();
+ mroute_iter != _mcast_routes.end();
+ ++mroute_iter) {
+ McastRoute& static_route = mroute_iter->second;
+ bool is_old_up = false;
+ bool is_new_up = false;
+ string old_ifname, old_vifname, new_ifname, new_vifname;
+
+ //
+ // Calculate whether the interface was UP before and now.
+ //
+ const IfMgrIfAtom* if_atom;
+ const IfMgrVifAtom* vif_atom;
+
+ if_atom = _iftree.find_interface(static_route.ifname());
+ vif_atom = _iftree.find_vif(static_route.ifname(),
+ static_route.vifname());
+ if ((if_atom != NULL) && (if_atom->enabled())
+ && (! if_atom->no_carrier())
+ && (vif_atom != NULL) && (vif_atom->enabled())) {
+ is_old_up = true;
+ }
+
+ if_atom = ifmgr_iftree().find_interface(static_route.ifname());
+ vif_atom = ifmgr_iftree().find_vif(static_route.ifname(),
+ static_route.vifname());
+ if ((if_atom != NULL) && (if_atom->enabled())
+ && (! if_atom->no_carrier())
+ && (vif_atom != NULL) && (vif_atom->enabled())) {
+ is_new_up = true;
+ }
+
+ if ((is_old_up == is_new_up)
+ && (old_ifname == new_ifname)
+ && (old_vifname == new_vifname)) {
+ continue; // Nothing changed
+ }
+
+ if ((! is_old_up) && (! is_new_up)) {
+ //
+ // The interface is still down, so nothing to do
+ //
+ continue;
+ }
+ if ((! is_old_up) && (is_new_up)) {
+ //
+ // The interface is now up, hence add the route
+ //
+ add_mroutes.push_back(&static_route);
+ continue;
+ }
+ if ((is_old_up) && (! is_new_up)) {
+ //
+ // The interface went down, hence cancel all pending requests,
+ // and withdraw the route.
+ //
+ delete_mroutes.push_back(&static_route);
+ continue;
+ }
+ if (is_old_up && is_new_up) {
+ //
+ // The interface remains up, hence probably the interface or
+ // the vif name has changed.
+ // Delete the route and then add it again so the information
+ // in the RIB will be updated.
+ //
+ replace_mroutes.push_back(&static_route);
+ continue;
+ }
+ }
+
//
// Update the local copy of the interface tree
//
@@ -444,6 +522,44 @@ StaticRoutesNode::updates_made()
copy_route.set_delete_route();
inform_rib(copy_route);
}
+
+
+ //
+ // Process all pending "add mroute" requests
+ //
+ for (mpending_iter = add_mroutes.begin();
+ mpending_iter != add_mroutes.end();
+ ++pending_iter) {
+ McastRoute& orig_route = *(*mpending_iter);
+ McastRoute copy_route = orig_route;
+ copy_route.set_add_route();
+ inform_mfea(copy_route);
+ }
+
+ //
+ // Process all pending "replace mroute" requests
+ //
+ for (mpending_iter = replace_mroutes.begin();
+ mpending_iter != replace_mroutes.end();
+ ++mpending_iter) {
+ McastRoute& orig_route = *(*mpending_iter);
+ McastRoute copy_route = orig_route;
+ copy_route.set_replace_route();
+ inform_mfea(copy_route);
+ }
+
+ //
+ // Process all pending "delete mroute" requests
+ //
+ for (mpending_iter = delete_mroutes.begin();
+ mpending_iter != delete_mroutes.end();
+ ++mpending_iter) {
+ McastRoute& orig_route = *(*mpending_iter);
+ cancel_mfea_mfc_change(orig_route);
+ McastRoute copy_route = orig_route;
+ copy_route.set_delete_route();
+ inform_mfea(copy_route);
+ }
}
/**
@@ -670,6 +786,65 @@ StaticRoutesNode::delete_route6(bool unicast, bool multicast,
return (delete_route(static_route, error_msg));
}
+int StaticRoutesNode::add_mcast_route4(const IPv4& mcast_addr, const string& input_if,
+ const IPv4& input_ip, const string& output_ifs,
+ uint32_t distance, string& error_msg) {
+ map<IPvX, McastRoute>::const_iterator iter = _mcast_routes.find(mcast_addr);
+ if (iter == _mcast_routes.end()) {
+ McastRoute mr(mcast_addr, input_if, input_ip, output_ifs, distance);
+ _mcast_routes[mcast_addr] = mr;
+ McastRoute copy_route = mr;
+ copy_route.set_add_route();
+ inform_mfea(copy_route);
+ }
+ else {
+ error_msg.append("Mcast-Route: " + mcast_addr.str() + " already exists!\n");
+ return XORP_ERROR;
+ }
+ return XORP_OK;
+}
+
+int StaticRoutesNode::replace_mcast_route4(const IPv4& mcast_addr, const string& input_if,
+ const IPv4& input_ip, const string& output_ifs,
+ uint32_t distance, string& error_msg) {
+ UNUSED(error_msg);
+
+ McastRoute mr(mcast_addr, input_if, input_ip, output_ifs, distance);
+ map<IPvX, McastRoute>::const_iterator iter = _mcast_routes.find(mcast_addr);
+ if (iter == _mcast_routes.end()) {
+ if (iter->second == mr) {
+ // no changes
+ return XORP_OK;
+ }
+ }
+ _mcast_routes.erase(mcast_addr);
+ _mcast_routes[mcast_addr] = mr;
+
+ McastRoute copy_route = mr;
+ copy_route.set_replace_route();
+ inform_mfea(copy_route);
+
+ return XORP_OK;
+}
+
+
+int StaticRoutesNode::delete_mcast_route4(const IPv4& mcast_addr, const IPv4& input_ip,
+ string& error_msg) {
+ UNUSED(error_msg);
+
+ map<IPvX, McastRoute>::const_iterator iter = _mcast_routes.find(mcast_addr);
+ if (iter != _mcast_routes.end()) {
+ _mcast_routes.erase(mcast_addr);
+
+ McastRoute mr(mcast_addr, input_ip);
+ mr.set_delete_route();
+ inform_mfea(mr);
+ }
+
+ return XORP_OK;
+}
+
+
/**
* Find a route from the routing table.
*
@@ -1296,6 +1471,16 @@ StaticRoutesNode::inform_rib(const StaticRoute& route)
inform_rib_route_change(modified_route);
}
+
+void
+StaticRoutesNode::inform_mfea(const McastRoute& route)
+{
+ if (! is_enabled())
+ return;
+
+ inform_mfea_mfc_change(route);
+}
+
/**
* Update a route received from the user configuration.
*
diff --git a/xorp/static_routes/static_routes_node.hh b/xorp/static_routes/static_routes_node.hh
index 907dbeb..53106be 100644
--- a/xorp/static_routes/static_routes_node.hh
+++ b/xorp/static_routes/static_routes_node.hh
@@ -18,8 +18,6 @@
// XORP Inc, 2953 Bunker Hill Lane, Suite 204, Santa Clara, CA 95054, USA;
// http://xorp.net
-// $XORP: xorp/static_routes/static_routes_node.hh,v 1.32 2008/10/02 21:58:29 bms Exp $
-
#ifndef __STATIC_ROUTES_STATIC_ROUTES_NODE_HH__
#define __STATIC_ROUTES_STATIC_ROUTES_NODE_HH__
@@ -29,23 +27,91 @@
//
-
#include "libxorp/service.hh"
#include "libxorp/status_codes.h"
-
#include "libfeaclient/ifmgr_xrl_mirror.hh"
-
#include "policy/backend/policytags.hh"
#include "policy/backend/policy_filters.hh"
class EventLoop;
+
+class StaticRouteBase {
+protected:
+ enum RouteType { IDLE_ROUTE, ADD_ROUTE, REPLACE_ROUTE, DELETE_ROUTE };
+ RouteType _route_type;
+ bool _is_ignored; // True if the route is to be ignored
+
+public:
+
+ StaticRouteBase() : _route_type(IDLE_ROUTE), _is_ignored(false) { }
+ virtual ~StaticRouteBase() { }
+
+ /**
+ * Test if this is a route to add.
+ *
+ * @return true if this is a route to add, otherwise false.
+ */
+ bool is_add_route() const { return (_route_type == ADD_ROUTE); }
+
+ /**
+ * Test if this is a replacement route.
+ *
+ * @return true if this is a replacement route, otherwise false.
+ */
+ bool is_replace_route() const { return (_route_type == REPLACE_ROUTE); }
+
+ /**
+ * Test if this is a route to delete.
+ *
+ * @return true if this is a route to delete, otherwise false.
+ */
+ bool is_delete_route() const { return (_route_type == DELETE_ROUTE); }
+
+ /**
+ * Set the type of this route to "a route to add".
+ */
+ void set_add_route() { _route_type = ADD_ROUTE; }
+
+ /**
+ * Set the type of this route to "a replacement route".
+ */
+ void set_replace_route() { _route_type = REPLACE_ROUTE; }
+
+ /**
+ * Set the type of this route to "a route to delete".
+ */
+ void set_delete_route() { _route_type = DELETE_ROUTE; }
+
+
+ /**
+ * Test if the route is to be ignored.
+ *
+ * This method is used only for internal purpose when passing the route
+ * around.
+ *
+ * @return true if the route is to be ignored, otherwise false.
+ */
+ bool is_ignored() const { return _is_ignored; }
+
+ /**
+ * Set whether the route is to be ignored.
+ *
+ * This method is used only for internal purpose when passing the route
+ * around.
+ *
+ * @param v true if the route is to be ignored, otherwise false.
+ */
+ void set_ignored(bool v) { _is_ignored = v; }
+
+};
+
/**
* @short A StaticRoute helper class.
*
* This class is used to store a routing entry.
*/
-class StaticRoute {
+class StaticRoute : public StaticRouteBase {
public:
/**
* Constructor for a given IPv4 static route.
@@ -68,12 +134,11 @@ public:
const IPv4Net& network, const IPv4& nexthop,
const string& ifname, const string& vifname,
uint32_t metric, bool is_backup_route)
- : _unicast(unicast), _multicast(multicast),
- _network(network), _nexthop(nexthop),
- _ifname(ifname), _vifname(vifname),
- _metric(metric), _is_backup_route(is_backup_route),
- _route_type(IDLE_ROUTE), _is_ignored(false),
- _is_filtered(false), _is_accepted_by_nexthop(false) {}
+ : _unicast(unicast), _multicast(multicast),
+ _network(network), _nexthop(nexthop),
+ _ifname(ifname), _vifname(vifname),
+ _metric(metric), _is_backup_route(is_backup_route),
+ _is_filtered(false), _is_accepted_by_nexthop(false) {}
#ifdef XORP_USE_USTL
StaticRoute() { }
@@ -100,12 +165,11 @@ public:
const IPv6Net& network, const IPv6& nexthop,
const string& ifname, const string& vifname,
uint32_t metric, bool is_backup_route)
- : _unicast(unicast), _multicast(multicast),
- _network(network), _nexthop(nexthop),
- _ifname(ifname), _vifname(vifname),
- _metric(metric), _is_backup_route(is_backup_route),
- _route_type(IDLE_ROUTE), _is_ignored(false),
- _is_filtered(false), _is_accepted_by_nexthop(false) {}
+ : _unicast(unicast), _multicast(multicast),
+ _network(network), _nexthop(nexthop),
+ _ifname(ifname), _vifname(vifname),
+ _metric(metric), _is_backup_route(is_backup_route),
+ _is_filtered(false), _is_accepted_by_nexthop(false) {}
/**
* Equality Operator
@@ -231,42 +295,6 @@ public:
bool is_backup_route() const { return _is_backup_route; }
/**
- * Test if this is a route to add.
- *
- * @return true if this is a route to add, otherwise false.
- */
- bool is_add_route() const { return (_route_type == ADD_ROUTE); }
-
- /**
- * Test if this is a replacement route.
- *
- * @return true if this is a replacement route, otherwise false.
- */
- bool is_replace_route() const { return (_route_type == REPLACE_ROUTE); }
-
- /**
- * Test if this is a route to delete.
- *
- * @return true if this is a route to delete, otherwise false.
- */
- bool is_delete_route() const { return (_route_type == DELETE_ROUTE); }
-
- /**
- * Set the type of this route to "a route to add".
- */
- void set_add_route() { _route_type = ADD_ROUTE; }
-
- /**
- * Set the type of this route to "a replacement route".
- */
- void set_replace_route() { _route_type = REPLACE_ROUTE; }
-
- /**
- * Set the type of this route to "a route to delete".
- */
- void set_delete_route() { _route_type = DELETE_ROUTE; }
-
- /**
* Test if the route is interface-specific (e.g., if the interface
* is explicitly specified).
*
@@ -284,26 +312,6 @@ public:
bool is_valid_entry(string& error_msg) const;
/**
- * Test if the route is to be ignored.
- *
- * This method is used only for internal purpose when passing the route
- * around.
- *
- * @return true if the route is to be ignored, otherwise false.
- */
- bool is_ignored() const { return _is_ignored; }
-
- /**
- * Set whether the route is to be ignored.
- *
- * This method is used only for internal purpose when passing the route
- * around.
- *
- * @param v true if the route is to be ignored, otherwise false.
- */
- void set_ignored(bool v) { _is_ignored = v; }
-
- /**
* @return policy-tags for this route.
*/
PolicyTags& policytags() { return _policytags; }
@@ -359,15 +367,48 @@ private:
string _vifname;
uint32_t _metric;
bool _is_backup_route;
- enum RouteType { IDLE_ROUTE, ADD_ROUTE, REPLACE_ROUTE, DELETE_ROUTE };
- RouteType _route_type;
- bool _is_ignored; // True if the route is to be ignored
bool _is_filtered; // True if rejected by a policy filter
bool _is_accepted_by_nexthop; // True if the route is accepted based on its next-hop information
PolicyTags _policytags;
};
+class McastRoute : public StaticRouteBase {
+protected:
+ IPvX _mcast_addr;
+ string _ifname; // assume vifname == ifname
+ IPvX _input_ip;
+ string _output_ifs; // assume vifname == ifname
+ uint32_t _distance;
+
+public:
+ McastRoute() { };
+ McastRoute(const IPvX& addr, const string& ifname, const IPvX& input_ip,
+ const string& output_ifs, uint32_t distance) :
+ _mcast_addr(addr), _ifname(ifname), _input_ip(input_ip),
+ _output_ifs(output_ifs), _distance(distance) { }
+
+ McastRoute(const IPvX& addr, const IPvX& input_ip) :
+ _mcast_addr(addr), _input_ip(input_ip) { }
+
+ bool operator==(const McastRoute& other) const {
+ if (this == &other)
+ return true;
+ return (_mcast_addr == other._mcast_addr &&
+ _ifname == other._ifname &&
+ _input_ip == other._input_ip &&
+ _output_ifs == other._output_ifs &&
+ _distance == other._distance);
+ }
+
+ const IPvX& mcast_addr() const { return _mcast_addr; }
+ const string& ifname() const { return _ifname; }
+ const string& vifname() const { return ifname(); }
+ const IPvX& input_ip() const { return _input_ip; }
+ const string& output_ifs() const { return _output_ifs; }
+ const uint32_t& distance() const { return _distance; }
+};
+
/**
* @short The StaticRoutes node class.
*
@@ -597,6 +638,19 @@ public:
const string& vifname, bool is_backup_route,
string& error_msg);
+
+ int add_mcast_route4(const IPv4& mcast_addr, const string& input_if,
+ const IPv4& input_ip, const string& output_ifs,
+ uint32_t distance, string& error_msg);
+
+ int replace_mcast_route4(const IPv4& mcast_addr, const string& input_if,
+ const IPv4& input_ip, const string& output_ifs,
+ uint32_t distance, string& error_msg);
+
+ int delete_mcast_route4(const IPv4& mcast_addr, const IPv4& input_ip,
+ string& error_msg);
+
+
/**
* Find a route from the routing table.
*
@@ -814,6 +868,10 @@ private:
*/
virtual void cancel_rib_route_change(const StaticRoute& static_route) = 0;
+ virtual void inform_mfea_mfc_change(const McastRoute& static_route) = 0;
+ virtual void cancel_mfea_mfc_change(const McastRoute& static_route) = 0;
+ void inform_mfea(const McastRoute& route);
+
/**
* Update a route received from the user configuration.
*
@@ -873,6 +931,8 @@ private:
map<IPvXNet, StaticRoute> _winning_routes_unicast;
map<IPvXNet, StaticRoute> _winning_routes_multicast;
+ map<IPvX, McastRoute> _mcast_routes;
+
//
// Status-related state
//
diff --git a/xorp/static_routes/xorp_static_routes.cc b/xorp/static_routes/xorp_static_routes.cc
index dcf3b67..cce5ee4 100644
--- a/xorp/static_routes/xorp_static_routes.cc
+++ b/xorp/static_routes/xorp_static_routes.cc
@@ -31,7 +31,7 @@
#include "libxorp/callback.hh"
#include "libxorp/eventloop.hh"
#include "libxorp/exceptions.hh"
-
+#include "libproto/proto_unit.hh"
#include "xrl_static_routes_node.hh"
#ifdef HAVE_GETOPT_H
@@ -106,7 +106,8 @@ static_routes_main(const string& finder_hostname, uint16_t finder_port) {
finder_port,
"finder",
"fea",
- "rib");
+ "rib",
+ xorp_module_name(AF_INET, XORP_MODULE_MFEA));
wait_until_xrl_router_is_ready(eventloop,
xrl_static_routes_node.xrl_router());
diff --git a/xorp/static_routes/xrl_static_routes_node.cc b/xorp/static_routes/xrl_static_routes_node.cc
index a3a4079..c9551a7 100644
--- a/xorp/static_routes/xrl_static_routes_node.cc
+++ b/xorp/static_routes/xrl_static_routes_node.cc
@@ -39,16 +39,19 @@ XrlStaticRoutesNode::XrlStaticRoutesNode(EventLoop& eventloop,
uint16_t finder_port,
const string& finder_target,
const string& fea_target,
- const string& rib_target)
+ const string& rib_target,
+ const string& mfea_target)
: StaticRoutesNode(eventloop),
XrlStdRouter(eventloop, class_name.c_str(), finder_hostname.c_str(),
finder_port),
XrlStaticRoutesTargetBase(&xrl_router()),
_eventloop(eventloop),
_xrl_rib_client(&xrl_router()),
+ _xrl_mfea_client(&xrl_router()),
_finder_target(finder_target),
_fea_target(fea_target),
_rib_target(rib_target),
+ _mfea_target(mfea_target),
_ifmgr(eventloop, fea_target.c_str(), xrl_router().finder_address(),
xrl_router().finder_port()),
_xrl_finder_client(&xrl_router()),
@@ -61,7 +64,9 @@ XrlStaticRoutesNode::XrlStaticRoutesNode(EventLoop& eventloop,
_is_rib_registered(false),
_is_rib_registering(false),
_is_rib_deregistering(false),
- _is_rib_igp_table4_registered(false)
+ _is_rib_igp_table4_registered(false),
+ _is_mfea_alive(false),
+ _is_mfea_registered(false)
#ifdef HAVE_IPV6
, _is_rib_igp_table6_registered(false)
#endif
@@ -222,6 +227,208 @@ XrlStaticRoutesNode::finder_register_interest_fea_cb(const XrlError& xrl_error)
}
}
+
+//
+// Register with the MFEA
+//
+void
+XrlStaticRoutesNode::mfea_register_startup()
+{
+ bool success;
+
+ _mfea_register_startup_timer.unschedule();
+ _mfea_register_shutdown_timer.unschedule();
+
+ if (! _is_finder_alive)
+ return; // The Finder is dead
+
+ if (_is_mfea_registered)
+ return; // Already registered
+
+ _is_fea_registering = true;
+
+ //
+ // Register interest in the FEA with the Finder
+ //
+ success = _xrl_finder_client.send_register_class_event_interest(
+ _finder_target.c_str(), xrl_router().instance_name(), _mfea_target,
+ callback(this, &XrlStaticRoutesNode::finder_register_interest_mfea_cb));
+
+ if (! success) {
+ //
+ // If an error, then start a timer to try again.
+ //
+ _mfea_register_startup_timer = _eventloop.new_oneoff_after(
+ RETRY_TIMEVAL,
+ callback(this, &XrlStaticRoutesNode::mfea_register_startup));
+ return;
+ }
+}
+
+void
+XrlStaticRoutesNode::finder_register_interest_mfea_cb(const XrlError& xrl_error)
+{
+ switch (xrl_error.error_code()) {
+ case OKAY:
+ _is_mfea_registering = false;
+ _is_mfea_registered = true;
+ break;
+
+ case COMMAND_FAILED:
+ //
+ // If a command failed because the other side rejected it, this is
+ // fatal.
+ //
+ XLOG_FATAL("Cannot register interest in Finder events: %s",
+ xrl_error.str().c_str());
+ break;
+
+ case NO_FINDER:
+ case RESOLVE_FAILED:
+ case SEND_FAILED:
+ //
+ // A communication error that should have been caught elsewhere
+ // (e.g., by tracking the status of the Finder and the other targets).
+ // Probably we caught it here because of event reordering.
+ // In some cases we print an error. In other cases our job is done.
+ //
+ XLOG_ERROR("XRL communication error: %s", xrl_error.str().c_str());
+ break;
+
+ case BAD_ARGS:
+ case NO_SUCH_METHOD:
+ case INTERNAL_ERROR:
+ //
+ // An error that should happen only if there is something unusual:
+ // e.g., there is XRL mismatch, no enough internal resources, etc.
+ // We don't try to recover from such errors, hence this is fatal.
+ //
+ XLOG_FATAL("Fatal XRL error: %s", xrl_error.str().c_str());
+ break;
+
+ case REPLY_TIMED_OUT:
+ case SEND_FAILED_TRANSIENT:
+ //
+ // If a transient error, then start a timer to try again
+ // (unless the timer is already running).
+ //
+ if (! _mfea_register_startup_timer.scheduled()) {
+ XLOG_ERROR("Failed to register interest in Finder events: %s. "
+ "Will try again.",
+ xrl_error.str().c_str());
+ _mfea_register_startup_timer = _eventloop.new_oneoff_after(
+ RETRY_TIMEVAL,
+ callback(this, &XrlStaticRoutesNode::mfea_register_startup));
+ }
+ break;
+ }
+}
+
+//
+// De-register with the RIB
+//
+void
+XrlStaticRoutesNode::mfea_register_shutdown()
+{
+ bool success;
+
+ _mfea_register_startup_timer.unschedule();
+ _mfea_register_shutdown_timer.unschedule();
+
+ if (! _is_finder_alive)
+ return; // The Finder is dead
+
+ if (! _is_mfea_alive)
+ return; // The MFEA is not there anymore
+
+ if (! _is_mfea_registered)
+ return; // Not registered
+
+ if (! _is_mfea_deregistering) {
+ StaticRoutesNode::incr_shutdown_requests_n();
+ _is_mfea_deregistering = true;
+ }
+
+ success = _xrl_finder_client.send_deregister_class_event_interest(
+ _finder_target.c_str(), xrl_router().instance_name(), _mfea_target,
+ callback(this, &XrlStaticRoutesNode::finder_deregister_interest_mfea_cb));
+
+ if (! success) {
+ //
+ // If an error, then start a timer to try again.
+ //
+ _mfea_register_shutdown_timer = _eventloop.new_oneoff_after(
+ RETRY_TIMEVAL,
+ callback(this, &XrlStaticRoutesNode::mfea_register_shutdown));
+ return;
+ }
+}
+
+void
+XrlStaticRoutesNode::finder_deregister_interest_mfea_cb(
+ const XrlError& xrl_error)
+{
+ switch (xrl_error.error_code()) {
+ case OKAY:
+ //
+ // If success, then we are done
+ //
+ _is_mfea_deregistering = false;
+ _is_mfea_registered = false;
+ break;
+
+ case COMMAND_FAILED:
+ //
+ // If a command failed because the other side rejected it, this is
+ // fatal.
+ //
+ XLOG_FATAL("Cannot deregister interest in Finder events: %s",
+ xrl_error.str().c_str());
+ break;
+
+ case NO_FINDER:
+ case RESOLVE_FAILED:
+ case SEND_FAILED:
+ //
+ // A communication error that should have been caught elsewhere
+ // (e.g., by tracking the status of the Finder and the other targets).
+ // Probably we caught it here because of event reordering.
+ // In some cases we print an error. In other cases our job is done.
+ //
+ _is_mfea_deregistering = false;
+ _is_mfea_registered = false;
+ break;
+
+ case BAD_ARGS:
+ case NO_SUCH_METHOD:
+ case INTERNAL_ERROR:
+ //
+ // An error that should happen only if there is something unusual:
+ // e.g., there is XRL mismatch, no enough internal resources, etc.
+ // We don't try to recover from such errors, hence this is fatal.
+ //
+ XLOG_FATAL("Fatal XRL error: %s", xrl_error.str().c_str());
+ break;
+
+ case REPLY_TIMED_OUT:
+ case SEND_FAILED_TRANSIENT:
+ //
+ // If a transient error, then start a timer to try again
+ // (unless the timer is already running).
+ //
+ if (! _mfea_register_shutdown_timer.scheduled()) {
+ XLOG_ERROR("Failed to deregister interest in Finder events: %s. "
+ "Will try again.",
+ xrl_error.str().c_str());
+ _mfea_register_shutdown_timer = _eventloop.new_oneoff_after(
+ RETRY_TIMEVAL,
+ callback(this, &XrlStaticRoutesNode::mfea_register_shutdown));
+ }
+ break;
+ }
+}
+
+
//
// De-register with the FEA
//
@@ -445,6 +652,7 @@ XrlStaticRoutesNode::finder_register_interest_rib_cb(const XrlError& xrl_error)
}
}
+
//
// De-register with the RIB
//
@@ -1036,6 +1244,10 @@ XrlStaticRoutesNode::finder_event_observer_0_1_xrl_target_birth(
send_rib_add_tables();
}
+ if (target_class == _mfea_target) {
+ _is_mfea_alive = true;
+ }
+
return XrlCmdError::OKAY();
UNUSED(target_instance);
}
@@ -1070,6 +1282,16 @@ XrlStaticRoutesNode::finder_event_observer_0_1_xrl_target_death(
do_shutdown = true;
}
+ if (target_class == _mfea_target) {
+ // If it was never started (by us), then ignore.
+ if (_is_mfea_alive) {
+ XLOG_ERROR("MFEA (instance %s) has died, shutting down.",
+ target_instance.c_str());
+ do_shutdown = true;
+ _is_mfea_alive = false;
+ }
+ }
+
if (do_shutdown)
StaticRoutesNode::shutdown();
@@ -1251,6 +1473,51 @@ XrlStaticRoutesNode::static_routes_0_1_delete_route6(
return XrlCmdError::OKAY();
}
+XrlCmdError XrlStaticRoutesNode::static_routes_0_1_add_mcast_route4(
+ // Input values,
+ const IPv4& mcast_addr,
+ const string& input_if,
+ const IPv4& input_ip,
+ const string& output_ifs,
+ const uint32_t& distance)
+{
+ string error_msg;
+ if (StaticRoutesNode::add_mcast_route4(mcast_addr, input_if, input_ip, output_ifs, distance, error_msg) != XORP_OK) {
+ return XrlCmdError::COMMAND_FAILED(error_msg);
+ }
+ return XrlCmdError::OKAY();
+}
+
+XrlCmdError XrlStaticRoutesNode::static_routes_0_1_replace_mcast_route4(
+ // Input values,
+ const IPv4& mcast_addr,
+ const string& input_if,
+ const IPv4& input_ip,
+ const string& output_ifs,
+ const uint32_t& distance)
+{
+ string error_msg;
+ if (StaticRoutesNode::replace_mcast_route4(mcast_addr, input_if, input_ip, output_ifs, distance, error_msg) != XORP_OK) {
+ return XrlCmdError::COMMAND_FAILED(error_msg);
+ }
+ return XrlCmdError::OKAY();
+}
+
+
+XrlCmdError XrlStaticRoutesNode::static_routes_0_1_delete_mcast_route4(
+ // Input values,
+ const IPv4& mcast_addr,
+ const IPv4& input_ip)
+{
+ string error_msg;
+ if (StaticRoutesNode::delete_mcast_route4(mcast_addr, input_ip, error_msg) != XORP_OK) {
+ return XrlCmdError::COMMAND_FAILED(error_msg);
+ }
+ return XrlCmdError::OKAY();
+}
+
+
+
/**
* Add/replace/delete a backup static route.
*
@@ -1757,6 +2024,33 @@ XrlStaticRoutesNode::inform_rib_route_change(const StaticRoute& static_route)
}
}
+void
+XrlStaticRoutesNode::inform_mfea_mfc_change(const McastRoute& static_route)
+{
+ // Add the request to the queue
+ _inform_mfea_queue.push_back(static_route);
+
+ // If the queue was empty before, start sending the routes
+ if (_inform_mfea_queue.size() == 1) {
+ send_mfea_mfc_change();
+ }
+}
+
+void
+XrlStaticRoutesNode::cancel_mfea_mfc_change(const McastRoute& static_route)
+{
+ list<McastRoute>::iterator iter;
+
+ for (iter = _inform_mfea_queue.begin();
+ iter != _inform_mfea_queue.end();
+ ++iter) {
+ McastRoute& tmp_static_route = *iter;
+ if (tmp_static_route == static_route)
+ tmp_static_route.set_ignored(true);
+ }
+}
+
+
/**
* Cancel a pending request to inform the RIB about a route change.
*
@@ -1995,6 +2289,167 @@ XrlStaticRoutesNode::send_rib_route_change()
}
}
+
+void
+XrlStaticRoutesNode::send_mfea_mfc_change()
+{
+ bool success = true;
+
+ if (! _is_finder_alive)
+ return; // The Finder is dead
+
+ do {
+ // Pop-up all routes that are to be ignored
+ if (_inform_mfea_queue.empty())
+ return; // No more route changes to send
+
+ McastRoute& tmp_static_route = _inform_mfea_queue.front();
+ if (tmp_static_route.is_ignored()) {
+ _inform_mfea_queue.pop_front();
+ continue;
+ }
+ break;
+ } while (true);
+
+ McastRoute& static_route = _inform_mfea_queue.front();
+
+ //
+ // Check whether we have already registered with the MFEA
+ //
+ if (! _is_mfea_registered) {
+ mfea_register_startup();
+ success = false;
+ goto start_timer_label;
+ }
+
+ //
+ // Send the appropriate XRL
+ //
+ if (static_route.is_add_route() || static_route.is_replace_route()) {
+ XLOG_INFO("sending mfea add-mfc command, input: %s mcast-addr: %s ifname: %s output_ifs: %s\n",
+ static_route.input_ip().str().c_str(),
+ static_route.mcast_addr().str().c_str(),
+ static_route.ifname().c_str(),
+ static_route.output_ifs().c_str());
+ success = _xrl_mfea_client.send_add_mfc4_str(
+ _mfea_target.c_str(),
+ StaticRoutesNode::protocol_name(),
+ static_route.input_ip().get_ipv4(),
+ static_route.mcast_addr().get_ipv4(),
+ static_route.ifname(),
+ static_route.output_ifs(),
+ static_route.distance(),
+ callback(this, &XrlStaticRoutesNode::send_mfea_mfc_change_cb));
+ if (success)
+ return;
+ }
+
+ if (static_route.is_delete_route()) {
+ success = _xrl_mfea_client.send_delete_mfc4(
+ _mfea_target.c_str(),
+ StaticRoutesNode::protocol_name(),
+ static_route.input_ip().get_ipv4(),
+ static_route.mcast_addr().get_ipv4(),
+ callback(this, &XrlStaticRoutesNode::send_mfea_mfc_change_cb));
+ if (success)
+ return;
+ }
+
+ if (! success) {
+ //
+ // If an error, then start a timer to try again.
+ //
+ XLOG_ERROR("Failed to %s mcast-route for %s with the RIB. "
+ "Will try again.",
+ (static_route.is_add_route())? "add"
+ : (static_route.is_replace_route())? "replace"
+ : "delete",
+ static_route.mcast_addr().str().c_str());
+ start_timer_label:
+ _inform_mfea_queue_timer = _eventloop.new_oneoff_after(
+ RETRY_TIMEVAL,
+ callback(this, &XrlStaticRoutesNode::send_mfea_mfc_change));
+ }
+}
+
+void
+XrlStaticRoutesNode::send_mfea_mfc_change_cb(const XrlError& xrl_error)
+{
+ switch (xrl_error.error_code()) {
+ case OKAY:
+ //
+ // If success, then send the next route change
+ //
+ _inform_mfea_queue.pop_front();
+ send_mfea_mfc_change();
+ break;
+
+ case COMMAND_FAILED:
+ //
+ // If a command failed because the other side rejected it,
+ // then print an error and send the next one.
+ //
+ XLOG_ERROR("Cannot %s an mcast-routing entry with the MFEA: %s",
+ (_inform_mfea_queue.front().is_add_route())? "add"
+ : (_inform_mfea_queue.front().is_replace_route())? "replace"
+ : "delete",
+ xrl_error.str().c_str());
+ _inform_mfea_queue.pop_front();
+ send_mfea_mfc_change();
+ break;
+
+ case NO_FINDER:
+ case RESOLVE_FAILED:
+ case SEND_FAILED:
+ //
+ // A communication error that should have been caught elsewhere
+ // (e.g., by tracking the status of the Finder and the other targets).
+ // Probably we caught it here because of event reordering.
+ // In some cases we print an error. In other cases our job is done.
+ //
+ XLOG_ERROR("Cannot %s an mcast-routing entry with the MFEA: %s",
+ (_inform_mfea_queue.front().is_add_route())? "add"
+ : (_inform_mfea_queue.front().is_replace_route())? "replace"
+ : "delete",
+ xrl_error.str().c_str());
+ _inform_mfea_queue.pop_front();
+ send_mfea_mfc_change();
+ break;
+
+ case BAD_ARGS:
+ case NO_SUCH_METHOD:
+ case INTERNAL_ERROR:
+ //
+ // An error that should happen only if there is something unusual:
+ // e.g., there is XRL mismatch, no enough internal resources, etc.
+ // We don't try to recover from such errors, hence this is fatal.
+ //
+ XLOG_FATAL("Fatal XRL error: %s", xrl_error.str().c_str());
+ break;
+
+ case REPLY_TIMED_OUT:
+ case SEND_FAILED_TRANSIENT:
+ //
+ // If a transient error, then start a timer to try again
+ // (unless the timer is already running).
+ //
+ if (! _inform_mfea_queue_timer.scheduled()) {
+ XLOG_ERROR("Failed to %s an mcast-routing entry with the RIB: %s. "
+ "Will try again.",
+ (_inform_mfea_queue.front().is_add_route())? "add"
+ : (_inform_mfea_queue.front().is_replace_route())? "replace"
+ : "delete",
+ xrl_error.str().c_str());
+ _inform_mfea_queue_timer = _eventloop.new_oneoff_after(
+ RETRY_TIMEVAL,
+ callback(this, &XrlStaticRoutesNode::send_mfea_mfc_change));
+ }
+ break;
+ }
+}
+
+
+
void
XrlStaticRoutesNode::send_rib_route_change_cb(const XrlError& xrl_error)
{
diff --git a/xorp/static_routes/xrl_static_routes_node.hh b/xorp/static_routes/xrl_static_routes_node.hh
index 905d2ac..aff1585 100644
--- a/xorp/static_routes/xrl_static_routes_node.hh
+++ b/xorp/static_routes/xrl_static_routes_node.hh
@@ -18,7 +18,6 @@
// XORP Inc, 2953 Bunker Hill Lane, Suite 204, Santa Clara, CA 95054, USA;
// http://xorp.net
-// $XORP: xorp/static_routes/xrl_static_routes_node.hh,v 1.26 2008/10/02 21:58:29 bms Exp $
#ifndef __STATIC_ROUTES_XRL_STATIC_ROUTES_NODE_HH__
#define __STATIC_ROUTES_XRL_STATIC_ROUTES_NODE_HH__
@@ -29,11 +28,10 @@
//
#include "libxipc/xrl_std_router.hh"
-
#include "libfeaclient/ifmgr_xrl_mirror.hh"
-
#include "xrl/interfaces/finder_event_notifier_xif.hh"
#include "xrl/interfaces/rib_xif.hh"
+#include "xrl/interfaces/mfea_xif.hh"
#include "xrl/targets/static_routes_base.hh"
#include "static_routes_node.hh"
@@ -52,8 +50,9 @@ public:
uint16_t finder_port,
const string& finder_target,
const string& fea_target,
- const string& rib_target);
- ~XrlStaticRoutesNode();
+ const string& rib_target,
+ const string& mfea_target);
+ virtual ~XrlStaticRoutesNode();
/**
* Startup the node operation.
@@ -210,6 +209,37 @@ protected:
const IPv6& nexthop);
/**
+ * Add/replace/delete multicast routes (not MRIB)
+ *
+ * @param mcast_addr Multicast-address to be routed.
+ *
+ * @param input_if Input interface name.
+ *
+ * @param input_ip Input interface IP address.
+ *
+ * @param output_ifs Output interface name(s). Space-separated list.
+ */
+ XrlCmdError static_routes_0_1_add_mcast_route4(
+ // Input values,
+ const IPv4& mcast_addr,
+ const string& input_if,
+ const IPv4& input_ip,
+ const string& output_ifs,
+ const uint32_t& distance);
+
+ XrlCmdError static_routes_0_1_replace_mcast_route4(
+ // Input values,
+ const IPv4& mcast_addr,
+ const string& input_if,
+ const IPv4& input_ip,
+ const string& output_ifs,
+ const uint32_t& distance);
+ XrlCmdError static_routes_0_1_delete_mcast_route4(
+ // Input values,
+ const IPv4& mcast_addr,
+ const IPv4& input_ip);
+
+ /**
* Add/replace/delete a backup static route.
*
* @param unicast if true, then the route would be used for unicast
@@ -442,6 +472,73 @@ protected:
// Input values,
const bool& enable);
+ XrlCmdError mfea_client_0_1_recv_kernel_signal_message4(
+ // Input values,
+ const string&,
+ const uint32_t&,
+ const string&,
+ const uint32_t&,
+ const IPv4&,
+ const IPv4&,
+ const vector<uint8_t>&) {
+ return XrlCmdError::OKAY();
+ }
+
+ XrlCmdError mfea_client_0_1_recv_dataflow_signal4(
+ // Input values,
+ const string&,
+ const IPv4&,
+ const IPv4&,
+ const uint32_t&,
+ const uint32_t&,
+ const uint32_t&,
+ const uint32_t&,
+ const uint32_t&,
+ const uint32_t&,
+ const uint32_t&,
+ const uint32_t&,
+ const bool&,
+ const bool&,
+ const bool&,
+ const bool&) {
+ return XrlCmdError::OKAY();
+ }
+
+
+#ifdef HAVE_IPV6
+ XrlCmdError mfea_client_0_1_recv_kernel_signal_message6(
+ // Input values,
+ const string&,
+ const uint32_t&,
+ const string&,
+ const uint32_t&,
+ const IPv6&,
+ const IPv6&,
+ const vector<uint8_t>&) {
+ return XrlCmdError::OKAY();
+ }
+
+ XrlCmdError mfea_client_0_1_recv_dataflow_signal6(
+ // Input values,
+ const string&,
+ const IPv6&,
+ const IPv6&,
+ const uint32_t&,
+ const uint32_t&,
+ const uint32_t&,
+ const uint32_t&,
+ const uint32_t&,
+ const uint32_t&,
+ const uint32_t&,
+ const uint32_t&,
+ const bool&,
+ const bool&,
+ const bool&,
+ const bool&) {
+ return XrlCmdError::OKAY();
+ }
+#endif
+
/**
* Configure a policy filter.
*
@@ -493,6 +590,11 @@ private:
void fea_register_shutdown();
void finder_deregister_interest_fea_cb(const XrlError& xrl_error);
+ void mfea_register_startup();
+ void finder_register_interest_mfea_cb(const XrlError& xrl_error);
+ void mfea_register_shutdown();
+ void finder_deregister_interest_mfea_cb(const XrlError& xrl_error);
+
void rib_register_startup();
void finder_register_interest_rib_cb(const XrlError& xrl_error);
void rib_register_shutdown();
@@ -515,6 +617,7 @@ private:
*/
void inform_rib_route_change(const StaticRoute& static_route);
+
/**
* Cancel a pending request to inform the RIB about a route change.
*
@@ -522,18 +625,28 @@ private:
*/
void cancel_rib_route_change(const StaticRoute& static_route);
+ void inform_mfea_mfc_change(const McastRoute& static_route);
+ void cancel_mfea_mfc_change(const McastRoute& static_route);
+
void send_rib_route_change();
void send_rib_route_change_cb(const XrlError& xrl_error);
+ void send_mfea_mfc_change();
+ void send_mfea_mfc_change_cb(const XrlError& xrl_error);
+
EventLoop& _eventloop;
XrlRibV0p1Client _xrl_rib_client;
+ XrlMfeaV0p1Client _xrl_mfea_client;
const string _finder_target;
const string _fea_target;
const string _rib_target;
+ const string _mfea_target;
IfMgrXrlMirror _ifmgr;
list<StaticRoute> _inform_rib_queue;
XorpTimer _inform_rib_queue_timer;
+ list<McastRoute> _inform_mfea_queue;
+ XorpTimer _inform_mfea_queue_timer;
XrlFinderEventNotifierV0p1Client _xrl_finder_client;
static const TimeVal RETRY_TIMEVAL;
@@ -552,6 +665,14 @@ private:
bool _is_rib_registering;
bool _is_rib_deregistering;
bool _is_rib_igp_table4_registered;
+
+ bool _is_mfea_alive;
+ bool _is_mfea_registered;
+ bool _is_mfea_registering;
+ bool _is_mfea_deregistering;
+ XorpTimer _mfea_register_startup_timer;
+ XorpTimer _mfea_register_shutdown_timer;
+
#ifdef HAVE_IPV6
bool _is_rib_igp_table6_registered;
#endif
diff --git a/xorp/xrl/interfaces/static_routes.xif b/xorp/xrl/interfaces/static_routes.xif
index f6da4f8..38a1622 100644
--- a/xorp/xrl/interfaces/static_routes.xif
+++ b/xorp/xrl/interfaces/static_routes.xif
@@ -1,4 +1,3 @@
-/* $XORP: xorp/xrl/interfaces/static_routes.xif,v 1.4 2007/01/23 01:57:38 pavlin Exp $ */
/*
* Static Routes XRL interface.
@@ -48,6 +47,13 @@ interface static_routes/0.1 {
& nexthop:ipv6;
/**
+ * Add/replace/delete multicast routes
+ */
+ add_mcast_route4 ? mcast_addr:ipv4 & input_if:txt & input_ip:ipv4 & output_ifs:txt & distance:u32;
+ replace_mcast_route4 ? mcast_addr:ipv4 & input_if:txt & input_ip:ipv4 & output_ifs:txt & distance:u32;
+ delete_mcast_route4 ? mcast_addr:ipv4 & input_ip:ipv4;
+
+ /**
* Add/replace/delete a backup static route.
*
* @param unicast if true, then the route would be used for unicast
diff --git a/xorp/xrl/targets/static_routes.tgt b/xorp/xrl/targets/static_routes.tgt
index 1bf764f..54e6396 100644
--- a/xorp/xrl/targets/static_routes.tgt
+++ b/xorp/xrl/targets/static_routes.tgt
@@ -1,11 +1,12 @@
-/* $XORP: xorp/xrl/targets/static_routes.tgt,v 1.4 2007/05/31 19:02:28 pavlin Exp $ */
#include "common.xif"
#include "finder_event_observer.xif"
#include "policy_backend.xif"
#include "static_routes.xif"
+#include "mfea_client.xif"
target static_routes implements common/0.1, \
finder_event_observer/0.1, \
policy_backend/0.1, \
+ mfea_client/0.1, \
static_routes/0.1;
--
1.7.7.6
More information about the Xorp-hackers
mailing list