I created a set of functions to capture the interface and report changes as they occur. Also, I show an interface to the uevent mechanism to get device plug and unplug events.
A more powerful utility called udev provides a better solution but I was looking for a minimum implementation without the extra baggage that comes with udev.
The source can be found here.
netlink devices
The libnl API provides away of getting the device information. It also provides a caching system to hold this information and report back changes via a callback. netlink_devices.c provides this feature:
Call netlinkdev_start to setup the interface to the netlink layer. A context pointer to struct netlinkdev_info is filled in by the function. A callback netlink_event_cb is provided to report back device interface change events. The event is either a address or link event. The information is in the struct netlinkdev_data.
int netlinkdev_start(struct netlinkdev_info *nl,void (*netlink_event_cb)(int,struct netlinkdev_data *,void *),void *caller_context)
Use netlinkdev_stop to shutdown the netlink infterface.
int netlinkdev_stop(struct netlinkdev_info *nl)
The libnl maintains 2 caches: "route/address" and "route/link". When changes occur at the link layer, the cache is updated and reported back via the callback installed when nl_cache_mngr_add() is called.
nl_cache_mngr_add(nl->mngr, "route/link",(change_func_t)&netlinkdev_changecb, nl, &nl->links);
nl_cache_mngr_add(nl->mngr, "route/addr", (change_func_t)&netlinkdev_changecb, nl, &nl->addrs);
The netlinkdev_changecb() is the modified from the original to supply the old and new objects. Whereas, the libnl only supplies the new object as change_func_t shows:
typedef void (*change_func_t)(struct nl_cache *, struct nl_object *, int, void *)Our new callback has boththe old object and the updated object. This allows a comparison to be performed.
static void netlinkdev_changecb(struct nl_cache *cache, struct nl_object *old, struct nl_object *obj, int action, void *arg)
{
struct netlinkdev_info *nl = (struct netlinkdev_info *)arg;
if( strcmp("route/link", nl_object_get_type(obj)) == 0 )
netlinkdev_changelinkcb(nl, old, obj, action);
else if( strcmp("route/addr", nl_object_get_type(obj)) == 0 )
netlinkdev_changeaddrcb(nl, old, obj, action);
}
The new callback is casted in the nl_cache_mngr_add() and a version of the libnl internal nl_cache_include() operations is replaced with netlinkdev_nlcacheinclude().
static void netlinkdev_opsinit(struct netlinkdev_info *nl)
{
/* overwrite the co_include_event so we can have both -old and
* new- objects in the change_cb */
if (nl->links) nl_cache_get_ops(nl->links)->co_include_event = netlinkdev_nlcacheinclude;
if (nl->addrs) nl_cache_get_ops(nl->addrs)->co_include_event = netlinkdev_nlcacheinclude;
/* the initial set of addrs is not propagated by the
* nl_cache_mngr, iterate the cache */
if (nl->addrs) nl_cache_foreach(nl->addrs, netlinkdev_bootcache, nl);
/* allow sharing of info */
if (nl->addrs) nl_cache_mngt_provide(nl->addrs);
}
The difference being netlinkdev_diffinclude() is called instead of cache_include() using our diff_func_t callback function.
The address and link handlers will do the comparison and send the info up the stack.
static void netlinkdev_changelinkcb(struct netlinkdev_info *nl, struct nl_object *_old, struct nl_object *obj, int action)
{
struct rtnl_link *link = (struct rtnl_link *)obj;
struct rtnl_link *old = (struct rtnl_link *)_old;
struct rtnl_addr *filter = rtnl_addr_alloc();
if (filter) rtnl_addr_set_ifindex(filter, rtnl_link_get_ifindex(link));
switch( action )
{
case NL_ACT_NEW:
if (LOG_DETAILS) NL_LOG(NLLOG_DEBUG, "link: NEW");
/* fall through */
case NL_ACT_CHANGE:
if (LOG_DETAILS) if (action == NL_ACT_CHANGE) NL_LOG(NLLOG_DEBUG, "link: CHG");
if ( (rtnl_link_get_flags(link) & IFF_UP) != (old?(rtnl_link_get_flags(old) & IFF_UP):0) )
{
nl_cache_foreach_filter(nl->addrs, (struct nl_object *)filter, netlinkdev_actionaddrcb, nl);
netlinkdev_actionlinkcb(obj, nl);
}
break;
case NL_ACT_DEL:
if (LOG_DETAILS) NL_LOG(NLLOG_DEBUG, "link: DEL");
nl_cache_foreach_filter(nl->addrs, (struct nl_object *)filter, netlinkdev_actionaddrcb, nl);
netlinkdev_actionlinkcb(obj, nl);
break;
}
if (filter) rtnl_addr_put(filter);
}
static void netlinkdev_changeaddrcb(struct netlinkdev_info *nl, struct nl_object *old, struct nl_object *obj, int action)
{
/* if the interface associated with the address is down, we got nothing to do */
struct rtnl_link *link = rtnl_link_get(nl->links, rtnl_addr_get_ifindex((struct rtnl_addr *)obj));
if (!link)
return;
if ((rtnl_link_get_flags(link) & IFF_UP) != 0)
{
switch( action )
{
case NL_ACT_NEW:
if (LOG_DETAILS) NL_LOG(NLLOG_DEBUG, "addr: NEW");
netlinkdev_actionaddrcb(obj, nl);
break;
case NL_ACT_CHANGE:
if (LOG_DETAILS) NL_LOG(NLLOG_DEBUG, "addr: CHG");
netlinkdev_actionaddrcb(obj, nl);
break;
case NL_ACT_DEL:
if (LOG_DETAILS) NL_LOG(NLLOG_DEBUG, "addr: DEL");
netlinkdev_actionaddrcb(obj, nl);
break;
}
}
rtnl_link_put(link);
}
The functions netlinkdev_actionaddrcb() and netlinkdev_actionlinkcb() will perform the callback using the installed event function passed in with netlinkdev_start().
Note that the new versions of libnl hides the struct nl_msgtype and struct nl_object structures that we need. That means the private headers need to be installed along with the public ones in order to include this:
#include <netlink-private/cache-api.h>
#include <netlink-private/object-api.h>
This can be seen in the upper level Makefile I provide.
Lastly, the cache needs to polled so a function to perform that is here:
int netlinkdev_poll(struct netlinkdev_info *nl)
{
nl_cache_mngr_poll(nl->mngr, 1000);
return 0;
}
All of these changes in caching technique was taken from here.
uevent devices
libnl does not totally support uevents but it does allow installing a custom callback to handle the hotplug type uevents I was looking for. uevent_devices.c was written to handle the events and callback during changes:
Starting and stopping is perform by these functions:int ueventdev_start(struct ueventdev_info *ul,
void (*ueventdev_cb)(struct ueventdev_data *, void *),
void *caller_context)
int ueventdev_stop(struct ueventdev_info *ul)
The struct eventdev_info is used as the context. The callback ueventdev_cb will be used to report device changes.
ueventdev_poll() must be called periodically:
int ueventdev_poll(struct ueventdev_info *ul)
ueventdev_init() will create a socket to the kernel. We want to get msg uevents so we will need to clone the callback using nl_cb_clone(). ueventdev_recvcb() will be the callback used to process the uevents. ueventdev_customcb() is the custom callback used to do the parsing of the message.
The uevents messages contain a key name. ueventdev_parseuevent() will look for the keys "ACTION" and "DEVNAME" in the message payload. If they are present and the action is either "add" or "remove" the event is passed to the installed callback.
main
The example main can be run as either a standalone or daemon process.
# ./nltest --help
Usage: ./nltest [--daemon|-d] [--loglevel|-l <level>] [--help|-h] [interface-name]
If interface-name is passed in, it will only monitor this interface.
int main(int argc, char *argv[])init() sets up the callbacks to netevent() and hotplugevent() functions.
{
parse_options(argc, argv);
if (start_as_daemon) {
fprintf(stdout, "Starting Netlink Test as daemon...\n");
running_daemon = 1;
daemonize_me(argv[0]);
}
NL_LOG_OPEN(netlinklogs_level);
NL_LOG(NLLOG_INFO, "Netlink Test Started.");
if (init() < 0) {
NL_LOG(NLLOG_FATAL, "Failure during init.");
NL_LOG_CLOSE();
exit(EXIT_FAILURE);
}
while (running) {
/* Process any netlink events */
poll();
if (strlen(interface_poll_name) > 0)
interfacestatus(interface_poll_name);
sleep(1);
}
NL_LOG(NLLOG_INFO, "Netlink Test Stopping.");
deinit();
NL_LOG_CLOSE();
return EXIT_SUCCESS;
}
poll() is called every second to poll the netlink and uevent devices.
deinit() stops the netlink and uevent devices.
Building
Building requires the libnl to be compiled and installed first. I included a top level Makefile to handle that. I built this for a BeagleBone Black using a toolchain I built with OpenEmbedded. Any toolchain can be used or change it to use desktop Linux.
I used libnl 3.2.25 for this test which can be obtained from here.
tar xzf netlink_devices.tar.gz
tar xzf libnl-3.2.25.tar.gz
make libnl-config
make libnl-build
make libnl-install
make netlink-devices-build
make netlink-devices-install
I install to the local dir by default under sysrootfs. Copy sysrootfs/usr/bin/nltest to the target device.
Example Output
# ./nltest
Netlink Test Started.
Initially grabs all the interfaces:interface ADDR event status UP (addr: 127.0.0.1)
interface ADDR event status UP (addr: 192.168.201.210)
interface ADDR event status UP (addr: ::1)
interface ADDR event status UP (addr: fe80::7aa5:4ff:fef1:25ac)
Ethernet interface is pulled:interface ADDR event status DOWN (addr: 192.168.201.210)
interface ADDR event status DOWN (addr: fe80::7aa5:4ff:fef1:25ac)
interface LINK event status:DOWN linkaddr:78:a5:04:f1:25:ac
interface LINK event status:DOWN linkaddr:78:a5:04:f1:25:ac
Ethernet interface plugged back in:
interface ADDR event status UP (addr: 192.168.201.210)
interface ADDR event status UP (addr: fe80::7aa5:4ff:fef1:25ac)
No comments:
Post a Comment