Route Manager
The RouteManager class provides centralized management of the kernel IPv4/IPv6 routing table in Join. It monitors route changes via Linux Netlink, maintains an in-memory cache of all RT_TABLE_MAIN routes, and notifies listeners of any change.
RouteManager is independent of InterfaceManager — the two managers can be used together or separately.
Getting an instance
RouteManager is a singleton. Use instance() for normal usage.
A custom Reactor* can be injected by constructing directly:
// libjoin.
#include <join/routemanager.hpp>
using namespace join;
// Normal usage — singleton
RouteManager& manager = RouteManager::instance();
// Custom reactor
Reactor myReactor;
RouteManager manager(&myReactor);On construction the manager opens a Netlink socket, subscribes to IPv4 and IPv6 route events, registers itself with the reactor, and performs an initial synchronous refresh().
RouteManager is neither copyable nor movable.
Finding routes
Find by interface index
IpAddress dest("10.0.0.0");
Route::Ptr route = manager.findByIndex(2, dest, 8);
if (!route)
{
// Route not found
}Find by interface name
Route::Ptr route = manager.findByName("eth0", dest, 8);Enumerating routes
All routes
for (const auto& route : manager.enumerate())
{
std::cout << route->dest().toString()
<< "/" << route->prefix()
<< " dev " << route->index() << "\n";
}Routes for a specific interface
// By interface index
for (const auto& route : manager.enumerate(2))
{
// ...
}
// By interface name
for (const auto& route : manager.enumerate("eth0"))
{
// ...
}Refreshing route data
refresh() clears the cache and re-dumps the kernel route table synchronously:
if (manager.refresh() == -1)
{
std::cerr << lastError.message() << "\n";
}Adding routes
IpAddress dest("10.0.0.0");
IpAddress gateway("192.168.1.1");
uint32_t metric = 100;
// By interface index — asynchronous (default)
manager.addRoute(2, dest, 8, gateway, metric);
// By interface index — synchronous
if (manager.addRoute(2, dest, 8, gateway, metric, true) == -1)
{
std::cerr << lastError.message() << "\n";
}
// By interface name
manager.addRoute("eth0", dest, 8, gateway, metric, true);
// On-link route (no gateway)
manager.addRoute("eth0", IpAddress("192.168.2.0"), 24, {}, 0, true);Removing routes
// By interface index
manager.removeRoute(2, dest, 8, true);
// By interface name
manager.removeRoute("eth0", dest, 8, true);removeRoute() looks up the current gateway in the cache before sending the kernel request, so the route must exist in the cache. Returns -1 with Errc::NotFound if not cached.
Flushing routes
Removes all non-kernel routes on a given interface:
// By interface index
manager.flushRoutes(2, true);
// By interface name
manager.flushRoutes("eth0", true);⚠️ Routes with RTPROT_KERNEL are skipped — only user-space routes are removed.
Updating a route
Use Route::set() to replace a route’s gateway and metric in place:
Route::Ptr route = manager.findByName("eth0", IpAddress("10.0.0.0"), 8);
if (route)
{
route->set(IpAddress("192.168.1.254"), 200, true);
}Event notifications
RouteChangeType flags
enum class RouteChangeType : uint32_t
{
Added = 1 << 0, // entry did not exist before
Deleted = 1 << 1, // entry was removed
Modified = 1 << 2, // entry was updated
GatewayChanged = 1 << 3,
MetricChanged = 1 << 4,
TypeChanged = 1 << 5,
ScopeChanged = 1 << 6,
ProtocolChanged = 1 << 7,
};Flags support all bitwise operators (&, |, ^, ~, &=, |=, ^=).
Route listener
addRouteListener returns a unique uint64_t id used to unregister later:
uint64_t id = manager.addRouteListener([](const RouteInfo& info) {
if (info.flags & RouteChangeType::Added)
{
std::cout << "Route added: "
<< info.route->dest().toString()
<< "/" << info.route->prefix();
if (!info.route->gateway().isWildcard())
std::cout << " via " << info.route->gateway().toString();
std::cout << " dev " << info.route->index() << "\n";
}
if (info.flags & RouteChangeType::Deleted)
{
std::cout << "Route deleted: "
<< info.route->dest().toString()
<< "/" << info.route->prefix() << "\n";
}
if (info.flags & RouteChangeType::GatewayChanged)
{
std::cout << "Gateway changed to "
<< info.route->gateway().toString() << "\n";
}
});
// Unregister
manager.removeRouteListener(id);Reactor integration
RouteManager registers itself with the reactor on construction and unregisters on destruction. All listener callbacks are invoked from the reactor’s dispatcher thread — keep them short and non-blocking.
With a custom Reactor
Reactor reactor;
RouteManager manager(&reactor);
ThreadPool pool;
pool.push([&reactor]() {
Thread::affinity(pthread_self(), 1);
Thread::priority(pthread_self(), 50);
reactor.run();
});
// ... application runs ...
reactor.stop();Information structures
RouteInfo
struct RouteInfo
{
Route::Ptr route; // the affected route
RouteChangeType flags; // what changed (bitmask)
};Route cache scope
Only routes from RT_TABLE_MAIN with family AF_INET or AF_INET6 are tracked. Routes from other tables (local, policy) are silently ignored.
Error handling
Methods return 0 on success, -1 on failure. Check join::lastError for details:
if (manager.addRoute("eth0", dest, 8, gateway, 0, true) == -1)
{
std::cerr << "Failed: " << lastError.message() << "\n";
}Synchronous operations time out after 5 seconds and set Errc::TimedOut.
Best practices
- Register listeners before doing anything else to avoid missing early events.
- Keep callbacks short and non-blocking — they run on the reactor dispatcher thread.
- Store the returned id from
addRouteListenerto be able to unregister. - Call
refresh()after external tools (e.g.ip route) modify the routing table. - Use
flushRoutes()to clean up all user routes on an interface before removing it. - Check return values for all synchronous operations.
Summary
| Feature | Supported |
|---|---|
| Route enumeration | ✅ |
| Route lookup | ✅ |
| Add route | ✅ |
| Remove route | ✅ |
| Update route | ✅ |
| Flush interface routes | ✅ |
| Route notifications | ✅ |
| IPv4 support | ✅ |
| IPv6 support | ✅ |
| Reactor integration | ✅ |
| Custom Reactor | ✅ |
| Sync/async operations | ✅ |