/* * Copyright (c) 2001-2020 Mellanox Technologies, Ltd. All rights reserved. * * This software is available to you under a choice of one of two * licenses. You may choose to be licensed under the terms of the GNU * General Public License (GPL) Version 2, available from the file * COPYING in the main directory of this source tree, or the * BSD license below: * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include "vlogger/vlogger.h" #include "utils/bullseye.h" #include "netlink_wrapper.h" #include #include #include #include #include #include #define MODULE_NAME "nl_wrapper:" #define nl_logpanic __log_panic #define nl_logerr __log_err #define nl_logwarn __log_warn #define nl_loginfo __log_info #define nl_logdbg __log_dbg #define nl_logfunc __log_func netlink_wrapper* g_p_netlink_handler = NULL; // structure to pass arguments on internal netlink callbacks handling typedef struct rcv_msg_arg { netlink_wrapper* netlink; nl_socket_handle* socket_handle; map* subjects_map; nlmsghdr* msghdr; } rcv_msg_arg_t; static rcv_msg_arg_t g_nl_rcv_arg; int nl_msg_rcv_cb(struct nl_msg *msg, void *arg) { nl_logfunc( "---> nl_msg_rcv_cb"); NOT_IN_USE(arg); g_nl_rcv_arg.msghdr = nlmsg_hdr(msg); // NETLINK MESAGE DEBUG //nl_msg_dump(msg, stdout); nl_logfunc( "<--- nl_msg_rcv_cb"); return 0; } /* This function is called from internal thread only as neigh_timer_expired() * so it is protected by m_cache_lock call */ void netlink_wrapper::notify_observers(netlink_event *p_new_event, e_netlink_event_type type) { g_nl_rcv_arg.netlink->m_cache_lock.unlock(); g_nl_rcv_arg.netlink->m_subj_map_lock.lock(); subject_map_iter iter = g_nl_rcv_arg.subjects_map->find(type); if(iter != g_nl_rcv_arg.subjects_map->end()) iter->second->notify_observers(p_new_event); g_nl_rcv_arg.netlink->m_subj_map_lock.unlock(); /* coverity[missing_unlock] */ g_nl_rcv_arg.netlink->m_cache_lock.lock(); } extern void link_event_callback(nl_object* obj) { netlink_wrapper::link_cache_callback(obj); } extern void neigh_event_callback(nl_object* obj) { netlink_wrapper::neigh_cache_callback(obj); } extern void route_event_callback(nl_object* obj) { netlink_wrapper::route_cache_callback(obj); } void netlink_wrapper::neigh_cache_callback(nl_object* obj) { nl_logdbg( "---> neigh_cache_callback"); struct rtnl_neigh* neigh = (struct rtnl_neigh*)obj; neigh_nl_event new_event(g_nl_rcv_arg.msghdr, neigh, g_nl_rcv_arg.netlink); netlink_wrapper::notify_observers(&new_event, nlgrpNEIGH); g_nl_rcv_arg.msghdr = NULL; nl_logdbg( "<--- neigh_cache_callback"); } void netlink_wrapper::link_cache_callback(nl_object* obj) { nl_logfunc( "---> link_cache_callback"); struct rtnl_link* link = (struct rtnl_link*) obj; link_nl_event new_event(g_nl_rcv_arg.msghdr, link, g_nl_rcv_arg.netlink); netlink_wrapper::notify_observers(&new_event, nlgrpLINK); g_nl_rcv_arg.msghdr = NULL; nl_logfunc( "<--- link_cache_callback"); } void netlink_wrapper::route_cache_callback(nl_object* obj) { nl_logfunc( "---> route_cache_callback"); struct rtnl_route* route = (struct rtnl_route*) obj; if (route) { int table_id = rtnl_route_get_table(route); int family = rtnl_route_get_family(route); if ((table_id > (int)RT_TABLE_UNSPEC) && (table_id != RT_TABLE_LOCAL) && (family == AF_INET)) { route_nl_event new_event(g_nl_rcv_arg.msghdr, route, g_nl_rcv_arg.netlink); netlink_wrapper::notify_observers(&new_event, nlgrpROUTE); } else { nl_logdbg("Received event for not handled route entry: family=%d, table_id=%d", family, table_id); } } else { nl_logdbg("Received invalid route event"); } g_nl_rcv_arg.msghdr = NULL; nl_logfunc( "<--- route_cache_callback"); } netlink_wrapper::netlink_wrapper() : m_socket_handle(NULL), m_mngr(NULL), m_cache_link(NULL), m_cache_neigh( NULL), m_cache_route(NULL) { nl_logdbg( "---> netlink_route_listener CTOR"); g_nl_rcv_arg.subjects_map = &m_subjects_map; g_nl_rcv_arg.netlink = this; g_nl_rcv_arg.msghdr = NULL; nl_logdbg( "<--- netlink_route_listener CTOR"); } netlink_wrapper::~netlink_wrapper() { /* different handling under LIBNL1 versus LIBNL3 */ #ifdef HAVE_LIBNL3 nl_logdbg( "---> netlink_route_listener DTOR (LIBNL3)"); /* should not call nl_cache_free() for link, neigh, route as nl_cach_mngr_free() does the freeing */ // nl_cache_free(m_cache_link); // nl_cache_free(m_cache_neigh); // nl_cache_free(m_cache_route); nl_cache_mngr_free(m_mngr); nl_socket_handle_free(m_socket_handle); #else // HAVE_LINBL1 nl_logdbg( "---> netlink_route_listener DTOR (LIBNL1)"); /* should not call nl_socket_handle_free(m_socket_handle) as nl_cache_mngr_free() does the freeing */ /* nl_socket_handle_free(m_socket_handle); */ nl_cache_free(m_cache_link); nl_cache_free(m_cache_neigh); nl_cache_free(m_cache_route); nl_cache_mngr_free(m_mngr); #endif // HAVE_LIBNL3 subject_map_iter iter = m_subjects_map.begin(); while (iter != m_subjects_map.end()) { delete iter->second; iter++; } nl_logdbg( "<--- netlink_route_listener DTOR"); } int netlink_wrapper::open_channel() { auto_unlocker lock(m_cache_lock); nl_logdbg("opening netlink channel"); /* // build to subscriptions groups mask for indicating what type of events the kernel will send on channel unsigned subscriptions = ~RTMGRP_TC; if (netlink_route_group_mask & nlgrpLINK) { subscriptions |= (1 << (RTNLGRP_LINK - 1)); } if (netlink_route_group_mask & nlgrpADDRESS) { if (!m_preferred_family || m_preferred_family == AF_INET) subscriptions |= (1 << (RTNLGRP_IPV4_IFADDR - 1)); if (!m_preferred_family || m_preferred_family == AF_INET6) subscriptions |= (1 << (RTNLGRP_IPV6_IFADDR - 1)); } if (netlink_route_group_mask & nlgrpROUTE) { if (!m_preferred_family || m_preferred_family == AF_INET) subscriptions |= (1 << (RTNLGRP_IPV4_ROUTE - 1)); if (!m_preferred_family || m_preferred_family == AF_INET6) subscriptions |= (1 << (RTNLGRP_IPV4_ROUTE - 1)); } if (netlink_route_group_mask & nlgrpPREFIX) { if (!m_preferred_family || m_preferred_family == AF_INET6) subscriptions |= (1 << (RTNLGRP_IPV6_PREFIX - 1)); } if (netlink_route_group_mask & nlgrpNEIGH) { subscriptions |= (1 << (RTNLGRP_NEIGH - 1)); } */ // Allocate a new netlink socket/handle m_socket_handle = nl_socket_handle_alloc(); BULLSEYE_EXCLUDE_BLOCK_START if (m_socket_handle == NULL) { nl_logerr("failed to allocate netlink handle"); return -1; } BULLSEYE_EXCLUDE_BLOCK_END // set internal structure to pass the handle with callbacks from netlink g_nl_rcv_arg.socket_handle = m_socket_handle; // if multiple handles being allocated then a unique netlink PID need to be provided // If port is 0, a unique port identifier will be generated automatically as a unique PID nl_socket_set_local_port(m_socket_handle, 0); //Disables checking of sequence numbers on the netlink handle. //This is required to allow messages to be processed which were not requested by a preceding request message, e.g. netlink events. nl_socket_handle_disable_seq_check(m_socket_handle); //joining group //nl_join_groups(m_handle, 0); // Allocate a new cache manager for RTNETLINK // NL_AUTO_PROVIDE = automatically provide the caches added to the manager. m_mngr = nl_cache_mngr_compatible_alloc(m_socket_handle, NETLINK_ROUTE, NL_AUTO_PROVIDE); BULLSEYE_EXCLUDE_BLOCK_START if (!m_mngr) { nl_logerr("Fail to allocate cache manager"); return -1; } BULLSEYE_EXCLUDE_BLOCK_END nl_logdbg("netlink socket is open"); if (nl_cache_mngr_compatible_add(m_mngr, "route/link", link_callback, NULL, &m_cache_link)) return -1; if (nl_cache_mngr_compatible_add(m_mngr, "route/route", route_callback, NULL, &m_cache_route)) return -1; if (nl_cache_mngr_compatible_add(m_mngr, "route/neigh", neigh_callback, NULL, &m_cache_neigh)) return -1; // set custom callback for every message to update message nl_socket_modify_cb(m_socket_handle, NL_CB_MSG_IN, NL_CB_CUSTOM, nl_msg_rcv_cb ,NULL); // set the socket non-blocking BULLSEYE_EXCLUDE_BLOCK_START if (nl_socket_set_nonblocking(m_socket_handle)) { nl_logerr("Failed to set the socket non-blocking"); return -1; } BULLSEYE_EXCLUDE_BLOCK_END return 0; } int netlink_wrapper::get_channel() { auto_unlocker lock(m_cache_lock); if (m_socket_handle) return nl_socket_get_fd(m_socket_handle); else return -1; } int netlink_wrapper::handle_events() { m_cache_lock.lock(); nl_logfunc("--->handle_events"); BULLSEYE_EXCLUDE_BLOCK_START if (!m_socket_handle) { nl_logerr("Cannot handle events before opening the channel. please call first open_channel()"); m_cache_lock.unlock(); return -1; } BULLSEYE_EXCLUDE_BLOCK_END int n = nl_cache_mngr_data_ready(m_mngr); //int n = nl_recvmsgs_default(m_handle); nl_logfunc("nl_recvmsgs=%d", n); if (n < 0) nl_logdbg("recvmsgs returned with error = %d", n); nl_logfunc("<---handle_events"); m_cache_lock.unlock(); return n; } bool netlink_wrapper::register_event(e_netlink_event_type type, const observer* new_obs) { auto_unlocker lock(m_subj_map_lock); subject* sub; subject_map_iter iter = m_subjects_map.find(type); if (iter == m_subjects_map.end()) { sub = new subject(); m_subjects_map[type] = sub; } else { sub = m_subjects_map[type]; } return sub->register_observer(new_obs); } bool netlink_wrapper::unregister(e_netlink_event_type type, const observer* obs) { auto_unlocker lock(m_subj_map_lock); if (obs == NULL) return false; subject_map_iter iter = m_subjects_map.find(type); if (iter != m_subjects_map.end()) { return m_subjects_map[type]->unregister_observer(obs); } return true; } int netlink_wrapper::get_neigh(const char* ipaddr, int ifindex, netlink_neigh_info* new_neigh_info) { auto_unlocker lock(m_cache_lock); nl_logfunc("--->netlink_listener::get_neigh"); nl_object* obj; rtnl_neigh* neigh; char addr_str[256]; BULLSEYE_EXCLUDE_BLOCK_START if (!new_neigh_info) { nl_logerr("Illegal argument. user pass NULL neigh_info to fill"); return -1; } BULLSEYE_EXCLUDE_BLOCK_END obj = nl_cache_get_first(m_cache_neigh); while (obj) { nl_object_get(obj); //Acquire a reference on a cache object. cache won't use/free it until calling to nl_object_put(obj) neigh = (rtnl_neigh*) obj; nl_addr* addr = rtnl_neigh_get_dst(neigh); int index = rtnl_neigh_get_ifindex(neigh); if ((addr) && (index > 0)) { nl_addr2str(addr, addr_str, 255); if (!strcmp(addr_str, ipaddr) && (ifindex == index)) { new_neigh_info->fill(neigh); nl_object_put(obj); nl_logdbg("neigh - DST_IP:%s IF_INDEX:%d LLADDR:%s", addr_str, index, new_neigh_info->lladdr_str.c_str() ); nl_logfunc("<---netlink_listener::get_neigh"); return 1; } } nl_object_put(obj); obj = nl_cache_get_next(obj); } nl_logfunc("<---netlink_listener::get_neigh"); return 0; } void netlink_wrapper::neigh_timer_expired() { m_cache_lock.lock(); nl_logfunc("--->netlink_wrapper::neigh_timer_expired"); nl_cache_refill(m_socket_handle, m_cache_neigh); notify_neigh_cache_entries(); nl_logfunc("<---netlink_wrapper::neigh_timer_expired"); m_cache_lock.unlock(); } void netlink_wrapper::notify_neigh_cache_entries() { nl_logfunc("--->netlink_wrapper::notify_cache_entries"); g_nl_rcv_arg.msghdr = NULL; nl_object* obj = nl_cache_get_first(m_cache_neigh); while (obj) { nl_object_get(obj); neigh_event_callback(obj); nl_object_put(obj); obj = nl_cache_get_next(obj); } nl_logfunc("<---netlink_wrapper::notify_cache_entries"); }