Interface Manager#

The InterfaceManager class provides centralized management of network interfaces in Join. It monitors system interfaces, handles creation and deletion, and notifies listeners of network changes through an event-driven architecture.

InterfaceManager is integrated with the Join reactor and automatically tracks interface and address changes via Linux Netlink.


Getting an instance#

InterfaceManager is a singleton. Use instance() for normal usage. A custom Reactor* can be injected by constructing directly:

#include <join/interfacemanager.hpp>

using namespace join;

// Normal usage — singleton
InterfaceManager& manager = InterfaceManager::instance();

// Custom reactor
Reactor myReactor;
InterfaceManager manager(&myReactor);

On construction the manager opens a Netlink socket, subscribes to link and address events, registers itself with the reactor, and performs an initial synchronous refresh().

InterfaceManager is neither copyable nor movable.


Finding interfaces#

Find by index#

Interface::Ptr eth0 = manager.findByIndex(2);
if (!eth0)
{
    // interface not found
}

Find by name#

Interface::Ptr eth0 = manager.findByName("eth0");
if (!eth0)
{
    // interface not found
}

Enumerate all interfaces#

for (const auto& iface : manager.enumerate())
{
    std::cout << iface->name() << " (" << iface->index() << ")\n";
}

Creating interfaces#

All creation methods default to asynchronous (sync = false). Pass true to block until the kernel confirms the operation.

Dummy interface#

// Asynchronous (default)
manager.createDummyInterface("dummy0");

// Synchronous
if (manager.createDummyInterface("dummy0", true) == -1)
{
    std::cerr << lastError.message() << "\n";
}

Bridge interface#

manager.createBridgeInterface("br0", true);

VLAN interface#

uint16_t vlanId = 100;

// By parent index
manager.createVlanInterface("eth0.100", 2, vlanId, ETH_P_8021Q, true);

// By parent name
manager.createVlanInterface("eth0.100", "eth0", vlanId, ETH_P_8021Q, true);

⚠️ VLAN IDs must be in the range 1–4094. ID 0 is reserved and returns -1.

Virtual Ethernet pair (veth)#

// Both ends in current namespace
manager.createVethInterface("veth0", "veth1", nullptr, true);

// Peer moved to a different namespace
pid_t containerPid = 12345;
manager.createVethInterface("veth0", "veth1", &containerPid, true);

GRE tunnel#

IpAddress local("192.168.1.1");
IpAddress remote("192.168.2.1");
uint32_t ikey = 1000;
uint32_t okey = 2000;

// By parent index, with keys
manager.createGreInterface("gre0", 2, local, remote, &ikey, &okey, 64, true);

// By parent name, without keys
manager.createGreInterface("gre0", "eth0", local, remote, nullptr, nullptr, 64, true);

⚠️ localAddress and remoteAddress must be the same IP family (both IPv4 or both IPv6).


Deleting interfaces#

// By index
manager.removeInterface(10, true);

// By name
manager.removeInterface("dummy0", true);

Refreshing interface data#

refresh() clears the cache and re-dumps link and address data from the kernel synchronously:

if (manager.refresh() == -1)
{
    std::cerr << lastError.message() << "\n";
}

Event notifications#

InterfaceChangeType flags#

enum class InterfaceChangeType : uint32_t
{
    Added              = 1 << 0,  // interface did not exist before
    Deleted            = 1 << 1,  // interface was removed
    Modified           = 1 << 2,  // interface was updated
    AdminStateChanged  = 1 << 3,  // IFF_UP changed
    OperStateChanged   = 1 << 4,  // IFF_RUNNING changed
    MacChanged         = 1 << 5,
    NameChanged        = 1 << 6,
    MtuChanged         = 1 << 7,
    KindChanged        = 1 << 8,
    MasterChanged      = 1 << 9,  // bridge membership changed
};

Flags support all bitwise operators (&, |, ^, ~, &=, |=, ^=).

addLinkListener returns a unique uint64_t id used to unregister later:

uint64_t id = manager.addLinkListener([](const LinkInfo& info) {
    if (info.flags & InterfaceChangeType::Added)
    {
        std::cout << "Interface added: " << info.interface->name() << "\n";
    }

    if (info.flags & InterfaceChangeType::Deleted)
    {
        std::cout << "Interface deleted\n";
    }

    if (info.flags & InterfaceChangeType::AdminStateChanged)
    {
        std::cout << info.interface->name()
                  << (info.interface->isEnabled() ? " up" : " down") << "\n";
    }
});

// Unregister
manager.removeLinkListener(id);

Address notifications#

uint64_t id = manager.addAddressListener([](const AddressInfo& info) {
    if (info.flags & InterfaceChangeType::Added)
    {
        std::cout << "Address added: "
                  << info.address.ip.toString()
                  << "/" << info.address.prefix
                  << " on " << info.interface->name() << "\n";
    }

    if (info.flags & InterfaceChangeType::Deleted)
    {
        std::cout << "Address removed: "
                  << info.address.ip.toString()
                  << "/" << info.address.prefix << "\n";
    }
});

// Unregister
manager.removeAddressListener(id);

Reactor integration#

InterfaceManager 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;
InterfaceManager 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#

LinkInfo#

struct LinkInfo
{
    Interface::Ptr interface;   // the affected interface
    InterfaceChangeType flags;  // what changed (bitmask)
};

AddressInfo#

struct AddressInfo : public LinkInfo
{
    Interface::Address address; // address that was added or removed
                                //   .ip        — IpAddress
                                //   .prefix    — uint32_t
                                //   .broadcast — IpAddress (IPv4)
};

Error handling#

Methods return 0 on success, -1 on failure. Check join::lastError for details:

if (manager.createDummyInterface("dummy0", true) == -1)
{
    std::cerr << "Failed: " << lastError.message() << "\n";
}

Synchronous operations time out after 5 seconds and set Errc::TimedOut.


Best practices#

  • Use synchronous mode when you need confirmation before proceeding.
  • Use asynchronous mode for fire-and-forget batch operations.
  • 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 addLinkListener / addAddressListener to be able to unregister.
  • Call refresh() after external tools (e.g. ip, ifconfig) modify interfaces.

Summary#

FeatureSupported
Interface enumeration
Interface lookup
Dummy interfaces
Bridge interfaces
VLAN interfaces
Veth pairs
GRE tunnels
Interface deletion
Link notifications
Address notifications
Reactor integration
Custom Reactor
Sync/async operations