.. SPDX-License-Identifier: Marvell-MIT Copyright (c) 2024 Marvell. ****************** Netlink Library ****************** Introduction ============ Netlink library provides an abstraction on top of open source `libnl library `_ using which application gets notified for a received netlink message from LINUX. Applications are notified via function callback for the netlink protocols they have registered. .. _Native_Netlink_Registration: Native netlink registration =========================== Registering for a netlink protocol via ``dao_netlink_register()`` is named as *Native netlink registration*. ``dao_netlink_register()`` is a lowest level abstraction API to get notifications for any netlink message that corresponds to netlink protocol provided as an argument to it. ``dao_netlink_register()`` has following declaration: .. code-block:: c int dao_netlink_register(int protocol, dao_netlink_parse_cb_t parse_cb, void *app_ops, void *app_cookie, ...); where, protocol eg: NETLINK_ROUTE, NETLINK_XFRM etc. parse_cb : See documentation for more details app_ops : Application specific function pointers app_cookie : Application cookie to identify received notification ... : comma-separated netlink multicast groups. See documentation for more details This registration is *native* in a sense that application is expected to parse received netlink object (``struct nl_object *``) by itself, perhaps via `native libnl APIs `_, which is passed as an argument to :ref:`parse_cb` Applications which does not wish to parse netlink object (``struct nl_object *``) or rather work with any libnl APIs, please refer to :ref:`high level netlink registration` section Netlink protocols ----------------- ``dao_netlink_register()`` takes `netlink protocol or netlink family `_ as a first argument. Examples of netlink protocols are: *NETLINK_ROUTE*, *NETLINK_XFRM*, *NETLINK_GENERIC* .. _Netlink_Object: Netlink Object ~~~~~~~~~~~~~~ On successful return, ``dao_netlink_register()`` internally opens a netlink socket for provided netlink ``protocol``. It also internally creates a ``netlink`` object for registered protocol. For a given netlink protocol, exactly one ``netlink`` object is created which holds ``netlink`` socket and file descriptor associated with it. Application can perform following actions on a ``netlink`` object Registered netlink object lookup '''''''''''''''''''''''''''''''' Get netlink object corresponding registered netlink protocol .. code-block:: c void *notifier = dao_netlink_lookup(NETLINK_ROUTE); Get netlink file descriptor ''''''''''''''''''''''''''' .. code-block:: c int fd = dao_netlink_fd_get(netlink); Netlink object cleanup '''''''''''''''''''''' Close netlink socket and free any associated memory including all ``notifier`` objects .. code-block:: c dao_netlink_close(netlink); .. _Native_Parse_Callback: Native parse callbacks ---------------------- Function callbacks of type ``dao_netlink_parse_cb_t`` are called as native parse callbacks which has following function declaration .. code-block:: c typedef void (*dao_netlink_parse_cb_t) (struct nl_object *nl_obj, void *notifier); Applications are expected to parse ``struct nl_object *`` by itself using `libnl core APIs `_.``notifier`` is a :ref:`notifier object` .. _Multicast_Groups: Netlink multicast groups ------------------------ Each netlink family or protocol has set of defined multicast groups. Application should be able to provide specific multicast group it would like to get notification for within a given protocol. Examples for multicast groups are .. code-block:: c Protocol Multicast Groups -------- ---------------- NETLINK_ROUTE RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV4_ROUTE, RTNLGRP_IPV4_RULE, RTNLGRP_IPV6_IFADDR, RTNLGRP_IPV6_ROUTE, RTNLGRP_IPV6_RULE, RTNLGRP_NOTIFY, RTNLGRP_NEIGH etc.. NETLINK_XFRM XFRMGRP_SA, XFRMGRP_POLICY, XFRMGRP_EXPIRE Multicast groups for a netlink protocol are provided as comma-separated arguments to ``dao_netlink_register()``. For eg: .. code-block:: c dao_netlink_register(NETLINK_ROUTE, route_parse_cb, NULL, NULL, RTNLGRP_IPV4_ROUTE, RTNLGRP_LINK, RTNL_GRP_IPV4_IFADDR); dao_netlink_register(NETLINK_XFRM, xfrm_parse_cb, NULL, NULL, XFRMGRP_SA, XFRMGRP_POLICY, XFRMGRP_EXPIRE); It is possible to provide separate ``parse_cb()`` for each multicast group like following .. code-block:: c dao_netlink_register(NETLINK_ROUTE, parse_cb1, ops1, aux1, RTNLGRP_IPV4_ROUTE); dao_netlink_register(NETLINK_ROUTE, parse_cb2, ops2, aux2,, RTNLGRP_LINK); In above case, parse_cb1() will be called once RTNLGRP_IPV4_ROUTE netlink messages are received while parse_cb2() will be called if netlink messages corresponding to RTNLGRP_LINK are received. .. warning:: Providing different parse callbacks for same multicast group is not supported .. _Notifier_Object: Notifier Object --------------- As :ref:`described above`, ``dao_netlink_register()`` can be called multiple times for each combination of *[protocol, multicast group]*. For each multicast group, different specific cookies can be provided. For eg: .. code-block:: c dao_netlink_register(NETLINK_ROUTE, parse_cb1, app_ops1, app_cookie1, RTNLGRP_IPV4_ROUTE); dao_netlink_register(NETLINK_ROUTE, parse_cb2, app_ops2, app_cookie2, RTNLGRP_LINK); For each ``dao_netlink_register()`` successful registration, library internally creates a ``notifier`` object which keep hold of all provided multicast groups, application specific ``app_ops`` and ``app_cookie``. ``Notifier`` object is passed as an argument to :ref:`parse_cb(struct nl_object *obj, void *netlink)`. Application specific cookies can be retrieved in ``parse_cb()`` Application specific ops ~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: c void *app_ops = dao_netlink_notifier_callback_ops_get(notifier); Application specific cookie ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: c void *app_cookie = dao_netlink_notifier_app_cookie_get(notifier); Get notifier object for a given parse_cb ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: c void *notifier = dao_netlink_notifier_lookup_by_parse_cb(netlink, parse_cb); .. _HighLevel_Netlink_Registration: High level netlink registration =============================== High level netlink registrations are wrappers on top of :ref:`native netlink registration` where application does not deal with libnl APIs or structures, instead this library defines function callbacks for each of the netlink protocol supported for high level registration. Following protocols are supported for high level netlink registration. NETLINK_ROUTE ------------- .. code-block:: c dao_netlink_route_notifier_register(dao_netlink_route_callback_ops_t *ops, const char *filter_prefix); Refer to ``dao_netlink_route_callback_ops_t`` for getting route netlink message notifications NETLINK_XFRM ------------- .. code-block:: c dao_netlink_xfrm_notifier_register(dao_netlink_xfrm_callback_ops_t *ops, void *app_cookie); Refer to ``dao_netlink_xfrm_callback_ops_t`` for getting XFRM netlink message notifications Programming model ================= Initialization -------------- Either use :ref:`native` or :ref:`high level` registration mechanism for getting netlink message notifications Periodic Netlink polling ------------------------ A control core is supposed to poll all ``netlink`` objects for any new netlink message arrival and hence ``recvmsg()`` like function must be invoked on all opened netlink sockets. For netlink polling, applications are required to call following APIs periodically for getting any new netlink notifications dao_netlink_poll() ~~~~~~~~~~~~~~~~~~ Periodically calls ``recvmsg()`` on all created netlink sockets and call application specific function callback for any new netlink message dao_netlink_poll_complete() ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once a notification is sent to application for a netlink message via ``dao_netlink_poll()``, library disables further polling on that specific netlink socket until application does not call ``dao_netlink_poll_complete()``. This API enables polling on all netlink sockets which are disabled temporarily after new message notification ``dao_netlink_poll_complete()`` is useful in use-case where ``dao_netlink_poll()`` is running in another thread context, perhaps in continuous loop, and current thread wants to control its polling using ``dao_netlink_poll_complete()`` Pseudo-code ~~~~~~~~~~~ Following example shows how to receive route updates for LINUX tap interfaces: ``dtap0`` and ``dtap1`` .. code-block:: c /* Get application specific identifier or cookie for each interface name * which is passed in remaining function callbacks */ int rops_app_interface_cookie (char *interface_name, int interface_name, uint32_t *cookie) { if(strstr(interface_name, "dtap0") { /* Return 0th index for tap0 interface */ *cookie = 0; return 0; } else if (strstr (interface_name, "dptap1") { /* Return 1st index for tap1 interface */ *cookie = 0; return 0; } else { /* interested only on dtap0 and dtap1 interface only */ return -1; } } /* Set Local IP to interface */ int rops_app_ip_local_addr_add_del(dao_netlink_route_ip_addr_t *addr, int is_add) { int interface_cookie = addr->app_if_cookie; /*< Set in get_app_interface_cookie() */ if(interface_cookie == 0){ /* Apply IP address to interface dtap0 */ } else /* Apply IP address to interface dtap1 */ } /* Update mac address */ int rops_app_link_addr_add_del(dao_netlink_route_link_t *link, int is_add) { int interface_cookie = link->app_if_cookie; /*< Set in get_app_interface_cookie() */ if(interface_cookie == 0){ /* Update link "dtap0" */ } else /* Update link "dtap1" *// } dao_netlink_route_callback_ops_t rops { .get_app_interface_cookie = rops_app_interface_cookie, .ip_local_addr_add_del = rops_app_ip_local_addr_add_del, .ip_route_add_del = rops_app_ip_route_add_del, .link_add_del = rops_app_link_add_del, .ip_neigh_add_del = rops_app_ip_neigh_add_del, }; int __poll_function(void *obj, const int is_main) { rte_graph_t *graph = (rte_graph_t *)obj /* Get this worker core handle for graph */ graph = graph + rte_lcore_id(); while (1) { if (is_main) { dao_netlink_poll(); ... ... Do other stuff ... dao_netlink_poll_complete(); } else { rte_graph_walk(graph); } } } void poll_function(void *obj) { if (rte_lcore_id() == rte_get_main_lcore()) { __poll_function(obj, 1); } else { __poll_function(obj, 0); } } int main () { rte_graph_t graph[RTE_LCORE_MAX]; /* Use high level netlink registration method */ if (dao_netlink_route_notifier_register(&rops, "dtap" /* prefix string for dtap0 and dtap1 */) < 0) return -1; /* Create rte_graph object for every lcore_id */ ... ... ... rte_eal_remote_launch(poll_function, graph, CALL_MAIN); } Netlink Cleanup --------------- Call ``dao_netlink_cleanup()`` to close all netlink sockets and notifier object including any associated memory