| /* |
| * ifinfo.c - Wrapper for rtnetlink interface info |
| * Copyright (C) 2012-2015 Jiri Pirko <jiri@resnulli.us> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| /** |
| * @ingroup libteam |
| * @defgroup ifinfo Interface information functions |
| * Wrapper for rtnetlink interface info |
| * |
| * @{ |
| * |
| * Header |
| * ------ |
| * ~~~~{.c} |
| * #include <team.h> |
| * ~~~~ |
| */ |
| |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <netlink/netlink.h> |
| #include <netlink/cli/utils.h> |
| #include <netlink/cli/link.h> |
| #include <netlink/data.h> |
| #include <linux/netdevice.h> |
| #include <linux/types.h> |
| #include <team.h> |
| #include <private/list.h> |
| #include <private/misc.h> |
| #include "team_private.h" |
| |
| /* \cond HIDDEN_SYMBOLS */ |
| |
| struct team_ifinfo { |
| struct list_item list; |
| bool linked; |
| uint32_t ifindex; |
| struct team_port * port; /* NULL if device is not team port */ |
| char hwaddr[MAX_ADDR_LEN]; |
| size_t hwaddr_len; |
| char orig_hwaddr[MAX_ADDR_LEN]; |
| size_t orig_hwaddr_len; |
| char ifname[IFNAMSIZ]; |
| uint32_t master_ifindex; |
| bool admin_state; |
| #define MAX_PHYS_PORT_ID_LEN 32 |
| char phys_port_id[MAX_PHYS_PORT_ID_LEN]; |
| size_t phys_port_id_len; |
| int changed; |
| }; |
| |
| #define CHANGED_REMOVED (1 << 0) |
| #define CHANGED_HWADDR (1 << 1) |
| #define CHANGED_HWADDR_LEN (1 << 2) |
| #define CHANGED_IFNAME (1 << 3) |
| #define CHANGED_MASTER_IFINDEX (1 << 4) |
| #define CHANGED_PHYS_PORT_ID (1 << 5) |
| #define CHANGED_PHYS_PORT_ID_LEN (1 << 6) |
| #define CHANGED_ADMIN_STATE (1 << 7) |
| #define CHANGED_ANY (CHANGED_REMOVED | CHANGED_HWADDR | \ |
| CHANGED_HWADDR_LEN | CHANGED_IFNAME | \ |
| CHANGED_MASTER_IFINDEX | CHANGED_PHYS_PORT_ID | \ |
| CHANGED_PHYS_PORT_ID_LEN | CHANGED_ADMIN_STATE) |
| |
| static void set_changed(struct team_ifinfo *ifinfo, int bit) |
| { |
| ifinfo->changed |= bit; |
| } |
| |
| static bool is_changed(struct team_ifinfo *ifinfo, int bit) |
| { |
| return ifinfo->changed & bit ? true: false; |
| } |
| |
| static void clear_changed(struct team_ifinfo *ifinfo) |
| { |
| ifinfo->changed = 0; |
| } |
| |
| static void update_hwaddr(struct team_ifinfo *ifinfo, struct rtnl_link *link) |
| { |
| struct nl_addr *nl_addr; |
| char *hwaddr; |
| size_t hwaddr_len; |
| |
| nl_addr = rtnl_link_get_addr(link); |
| if (!nl_addr) |
| return; |
| |
| hwaddr_len = nl_addr_get_len(nl_addr); |
| if (ifinfo->hwaddr_len != hwaddr_len) { |
| ifinfo->hwaddr_len = hwaddr_len; |
| if (!ifinfo->master_ifindex) |
| ifinfo->orig_hwaddr_len = hwaddr_len; |
| set_changed(ifinfo, CHANGED_HWADDR_LEN); |
| } |
| hwaddr = nl_addr_get_binary_addr(nl_addr); |
| if (memcmp(ifinfo->hwaddr, hwaddr, hwaddr_len)) { |
| memcpy(ifinfo->hwaddr, hwaddr, hwaddr_len); |
| if (!ifinfo->master_ifindex) |
| memcpy(ifinfo->orig_hwaddr, hwaddr, hwaddr_len); |
| set_changed(ifinfo, CHANGED_HWADDR); |
| } |
| } |
| |
| static void update_ifname(struct team_ifinfo *ifinfo, struct rtnl_link *link) |
| { |
| char *ifname; |
| |
| ifname = rtnl_link_get_name(link); |
| if (ifname && strcmp(ifinfo->ifname, ifname)) { |
| mystrlcpy(ifinfo->ifname, ifname, sizeof(ifinfo->ifname)); |
| set_changed(ifinfo, CHANGED_IFNAME); |
| } |
| } |
| |
| static void update_admin_state(struct team_ifinfo *ifinfo, struct rtnl_link *link) |
| { |
| unsigned int flags; |
| bool admin_state; |
| |
| flags = rtnl_link_get_flags(link); |
| admin_state = ((flags & IFF_UP) == IFF_UP); |
| |
| if (admin_state != ifinfo->admin_state) { |
| ifinfo->admin_state = admin_state; |
| set_changed(ifinfo, CHANGED_ADMIN_STATE); |
| } |
| } |
| |
| static void update_master(struct team_ifinfo *ifinfo, struct rtnl_link *link) |
| { |
| uint32_t master_ifindex; |
| |
| master_ifindex = rtnl_link_get_master(link); |
| if (ifinfo->master_ifindex != master_ifindex) { |
| ifinfo->master_ifindex = master_ifindex; |
| set_changed(ifinfo, CHANGED_MASTER_IFINDEX); |
| } |
| } |
| |
| static void update_phys_port_id(struct team_ifinfo *ifinfo, |
| struct rtnl_link *link) |
| { |
| #ifdef HAVE_RTNL_LINK_GET_PHYS_ID |
| struct nl_data *nl_data; |
| char *phys_port_id = NULL; |
| size_t phys_port_id_len = 0; |
| |
| nl_data = rtnl_link_get_phys_port_id(link); |
| if (nl_data) { |
| phys_port_id_len = nl_data_get_size(nl_data); |
| if (phys_port_id_len > MAX_PHYS_PORT_ID_LEN) |
| phys_port_id_len = 0; |
| phys_port_id = nl_data_get(nl_data); |
| } |
| |
| if (ifinfo->phys_port_id_len != phys_port_id_len) { |
| ifinfo->phys_port_id_len = phys_port_id_len; |
| set_changed(ifinfo, CHANGED_PHYS_PORT_ID_LEN); |
| } |
| if (phys_port_id_len && |
| memcmp(ifinfo->phys_port_id, phys_port_id, phys_port_id_len)) { |
| memcpy(ifinfo->phys_port_id, phys_port_id, phys_port_id_len); |
| set_changed(ifinfo, CHANGED_PHYS_PORT_ID); |
| } |
| #endif |
| } |
| |
| static void ifinfo_update(struct team_ifinfo *ifinfo, struct rtnl_link *link) |
| { |
| update_ifname(ifinfo, link); |
| update_master(ifinfo, link); |
| update_hwaddr(ifinfo, link); |
| update_phys_port_id(ifinfo, link); |
| update_admin_state(ifinfo, link); |
| } |
| |
| static struct team_ifinfo *ifinfo_find(struct team_handle *th, uint32_t ifindex) |
| { |
| struct team_ifinfo *ifinfo; |
| |
| list_for_each_node_entry(ifinfo, &th->ifinfo_list, list) { |
| if (ifinfo->ifindex == ifindex) |
| return ifinfo; |
| } |
| return NULL; |
| } |
| |
| static void clear_last_changed(struct team_handle *th) |
| { |
| struct team_ifinfo *ifinfo; |
| |
| list_for_each_node_entry(ifinfo, &th->ifinfo_list, list) |
| clear_changed(ifinfo); |
| } |
| |
| static struct team_ifinfo *ifinfo_find_create(struct team_handle *th, |
| uint32_t ifindex) |
| { |
| struct team_ifinfo *ifinfo; |
| |
| ifinfo = ifinfo_find(th, ifindex); |
| if (ifinfo) |
| return ifinfo; |
| |
| ifinfo = myzalloc(sizeof(*ifinfo)); |
| if (!ifinfo) |
| return NULL; |
| |
| ifinfo->ifindex = ifindex; |
| list_add(&th->ifinfo_list, &ifinfo->list); |
| return ifinfo; |
| } |
| |
| static void ifinfo_destroy(struct team_ifinfo *ifinfo) |
| { |
| if (ifinfo->linked && ifinfo->port) |
| port_unlink(ifinfo->port); |
| list_del(&ifinfo->list); |
| free(ifinfo); |
| } |
| |
| static void ifinfo_destroy_removed(struct team_handle *th) |
| { |
| struct team_ifinfo *ifinfo, *tmp; |
| |
| list_for_each_node_entry_safe(ifinfo, tmp, &th->ifinfo_list, list) { |
| if (is_changed(ifinfo, CHANGED_REMOVED)) |
| ifinfo_destroy(ifinfo); |
| } |
| } |
| |
| static void obj_input_newlink(struct nl_object *obj, void *arg, bool event) |
| { |
| struct team_handle *th = arg; |
| struct rtnl_link *link; |
| struct team_ifinfo *ifinfo; |
| uint32_t ifindex; |
| int err; |
| |
| ifinfo_destroy_removed(th); |
| |
| link = (struct rtnl_link *) obj; |
| |
| ifindex = rtnl_link_get_ifindex(link); |
| ifinfo = ifinfo_find_create(th, ifindex); |
| if (!ifinfo) |
| return; |
| |
| if (event) { |
| err = rtnl_link_get_kernel(th->nl_cli.sock, ifindex, NULL, &link); |
| if (err) |
| return; |
| } |
| |
| clear_last_changed(th); |
| ifinfo_update(ifinfo, link); |
| |
| if (event) |
| rtnl_link_put(link); |
| |
| if (ifinfo->changed || !event) |
| set_call_change_handlers(th, TEAM_IFINFO_CHANGE); |
| } |
| |
| static void event_handler_obj_input_newlink(struct nl_object *obj, void *arg) |
| { |
| return obj_input_newlink(obj, arg, true); |
| } |
| |
| static void event_handler_obj_input_dellink(struct nl_object *obj, void *arg) |
| { |
| struct team_handle *th = arg; |
| struct rtnl_link *link; |
| struct team_ifinfo *ifinfo; |
| uint32_t ifindex; |
| int err; |
| |
| ifinfo_destroy_removed(th); |
| |
| link = (struct rtnl_link *) obj; |
| |
| ifindex = rtnl_link_get_ifindex(link); |
| ifinfo = ifinfo_find_create(th, ifindex); |
| if (!ifinfo) |
| return; |
| |
| /* It might happen that dellink message comes even in case the device |
| * is not actually removed. For example in case of bridge port removal. |
| * So better to check actual state before taking actions |
| */ |
| err = rtnl_link_get_kernel(th->nl_cli.sock, ifindex, NULL, &link); |
| if (!err) { |
| rtnl_link_put(link); |
| return; |
| } |
| |
| clear_last_changed(th); |
| set_changed(ifinfo, CHANGED_REMOVED); |
| set_call_change_handlers(th, TEAM_IFINFO_CHANGE); |
| } |
| |
| int ifinfo_event_handler(struct nl_msg *msg, void *arg) |
| { |
| struct team_handle *th = arg; |
| |
| switch (nlmsg_hdr(msg)->nlmsg_type) { |
| case RTM_NEWLINK: |
| if (nl_msg_parse(msg, &event_handler_obj_input_newlink, th) < 0) |
| err(th, "Unknown message type."); |
| break; |
| case RTM_DELLINK: |
| if (nl_msg_parse(msg, &event_handler_obj_input_dellink, th) < 0) |
| err(th, "Unknown message type."); |
| break; |
| default: |
| return NL_OK; |
| } |
| return NL_STOP; |
| } |
| |
| int ifinfo_list_alloc(struct team_handle *th) |
| { |
| list_init(&th->ifinfo_list); |
| return 0; |
| } |
| |
| static void valid_handler_obj_input_newlink(struct nl_object *obj, void *arg) |
| { |
| return obj_input_newlink(obj, arg, false); |
| } |
| |
| static int valid_handler(struct nl_msg *msg, void *arg) |
| { |
| struct team_handle *th = arg; |
| |
| if (nlmsg_hdr(msg)->nlmsg_type != RTM_NEWLINK) |
| return NL_OK; |
| |
| if (nl_msg_parse(msg, &valid_handler_obj_input_newlink, th) < 0) |
| err(th, "Unknown message type."); |
| return NL_OK; |
| } |
| |
| int get_ifinfo_list(struct team_handle *th) |
| { |
| struct nl_cb *cb; |
| struct nl_cb *orig_cb; |
| struct rtgenmsg rt_hdr = { |
| .rtgen_family = AF_UNSPEC, |
| }; |
| int ret; |
| int retry = 1; |
| |
| while (retry) { |
| retry = 0; |
| ret = nl_send_simple(th->nl_cli.sock, RTM_GETLINK, NLM_F_DUMP, |
| &rt_hdr, sizeof(rt_hdr)); |
| if (ret < 0) { |
| err(th, "get_ifinfo_list: nl_send_simple failed"); |
| return -nl2syserr(ret); |
| } |
| orig_cb = nl_socket_get_cb(th->nl_cli.sock); |
| cb = nl_cb_clone(orig_cb); |
| nl_cb_put(orig_cb); |
| if (!cb) { |
| err(th, "get_ifinfo_list: nl_cb_clone failed"); |
| return -ENOMEM; |
| } |
| |
| nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, valid_handler, th); |
| |
| ret = nl_recvmsgs(th->nl_cli.sock, cb); |
| nl_cb_put(cb); |
| if (ret < 0) { |
| err(th, "get_ifinfo_list: nl_recvmsgs failed"); |
| if (ret != -NLE_DUMP_INTR) |
| return -nl2syserr(ret); |
| retry = 1; |
| } |
| } |
| ret = check_call_change_handlers(th, TEAM_IFINFO_CHANGE); |
| if (ret < 0) |
| err(th, "get_ifinfo_list: check_call_change_handers failed"); |
| return ret; |
| } |
| |
| int ifinfo_list_init(struct team_handle *th) |
| { |
| int err; |
| |
| err = get_ifinfo_list(th); |
| if (err) { |
| err(th, "Failed to get interface information list."); |
| return err; |
| } |
| return 0; |
| } |
| |
| static void flush_port_list(struct team_handle *th) |
| { |
| struct team_ifinfo *ifinfo, *tmp; |
| |
| list_for_each_node_entry_safe(ifinfo, tmp, &th->ifinfo_list, list) |
| ifinfo_destroy(ifinfo); |
| } |
| |
| void ifinfo_list_free(struct team_handle *th) |
| { |
| flush_port_list(th); |
| } |
| |
| int ifinfo_link_with_port(struct team_handle *th, uint32_t ifindex, |
| struct team_port *port, struct team_ifinfo **p_ifinfo) |
| { |
| struct team_ifinfo *ifinfo; |
| |
| ifinfo = ifinfo_find(th, ifindex); |
| if (!ifinfo) |
| return -ENOENT; |
| if (ifinfo->linked) |
| return -EBUSY; |
| ifinfo->port = port; |
| ifinfo->linked = true; |
| if (p_ifinfo) |
| *p_ifinfo = ifinfo; |
| return 0; |
| } |
| |
| int ifinfo_link(struct team_handle *th, uint32_t ifindex, |
| struct team_ifinfo **p_ifinfo) |
| { |
| return ifinfo_link_with_port(th, ifindex, NULL, p_ifinfo); |
| } |
| |
| void ifinfo_unlink(struct team_ifinfo *ifinfo) |
| { |
| ifinfo->port = NULL; |
| ifinfo->linked = false; |
| } |
| |
| /* \endcond */ |
| |
| /** |
| * @param th libteam library context |
| * @param ifinfo ifinfo structure |
| * |
| * @details Get next ifinfo in list. |
| * |
| * @return Ifinfo next to ifinfo passed. |
| **/ |
| TEAM_EXPORT |
| struct team_ifinfo *team_get_next_ifinfo(struct team_handle *th, |
| struct team_ifinfo *ifinfo) |
| { |
| do { |
| ifinfo = list_get_next_node_entry(&th->ifinfo_list, ifinfo, list); |
| if (ifinfo && ifinfo->linked) |
| return ifinfo; |
| } while (ifinfo); |
| return NULL; |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details See if ifinfo got removed. This means that the interface |
| * got removed. |
| * |
| * @return True if ifinfo got changed. |
| **/ |
| TEAM_EXPORT |
| bool team_is_ifinfo_removed(struct team_ifinfo *ifinfo) |
| { |
| return is_changed(ifinfo, CHANGED_REMOVED); |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details Get ifinfo interface index. |
| * |
| * @return Ifinfo interface index as idenfified by in kernel. |
| **/ |
| TEAM_EXPORT |
| uint32_t team_get_ifinfo_ifindex(struct team_ifinfo *ifinfo) |
| { |
| return ifinfo->ifindex; |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details Get ifinfo admin state. |
| * |
| * @return Ifinfo interface index as idenfified by in kernel. |
| **/ |
| TEAM_EXPORT |
| bool team_get_ifinfo_admin_state(struct team_ifinfo *ifinfo) |
| { |
| return ifinfo->admin_state; |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details Get port associated to rtnetlink interface info. |
| * |
| * @return Pointer to appropriate team_port structure |
| * or NULL if not associated. |
| **/ |
| TEAM_EXPORT |
| struct team_port *team_get_ifinfo_port(struct team_ifinfo *ifinfo) |
| { |
| return ifinfo->port; |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details Get ifinfo hardware address. |
| * |
| * @return Pointer to memory place where hwaddr is. |
| **/ |
| TEAM_EXPORT |
| char *team_get_ifinfo_hwaddr(struct team_ifinfo *ifinfo) |
| { |
| return ifinfo->hwaddr; |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details See if ifinfo hardware address got changed. |
| * |
| * @return True if hardware address got changed. |
| **/ |
| TEAM_EXPORT |
| bool team_is_ifinfo_hwaddr_changed(struct team_ifinfo *ifinfo) |
| { |
| return is_changed(ifinfo, CHANGED_HWADDR); |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details Get ifinfo hardware address length. |
| * |
| * @return Hardware address length. |
| **/ |
| TEAM_EXPORT |
| size_t team_get_ifinfo_hwaddr_len(struct team_ifinfo *ifinfo) |
| { |
| return ifinfo->hwaddr_len; |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details See if ifinfo hardware address length got changed. |
| * |
| * @return True if ifinfo hardware address length changed. |
| **/ |
| TEAM_EXPORT |
| bool team_is_ifinfo_hwaddr_len_changed(struct team_ifinfo *ifinfo) |
| { |
| return is_changed(ifinfo, CHANGED_HWADDR_LEN); |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details Get ifinfo original hardware address. |
| * |
| * @return Pointer to memory place where hwaddr is. |
| **/ |
| TEAM_EXPORT |
| char *team_get_ifinfo_orig_hwaddr(struct team_ifinfo *ifinfo) |
| { |
| return ifinfo->orig_hwaddr; |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details Get ifinfo original hardware address length. |
| * |
| * @return Hardware address length. |
| **/ |
| TEAM_EXPORT |
| uint8_t team_get_ifinfo_orig_hwaddr_len(struct team_ifinfo *ifinfo) |
| { |
| return ifinfo->orig_hwaddr_len; |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details Get ifinfo interface name. |
| * |
| * @return Pointer to memory place where interface name is. |
| **/ |
| TEAM_EXPORT |
| char *team_get_ifinfo_ifname(struct team_ifinfo *ifinfo) |
| { |
| return ifinfo->ifname; |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details See if ifinfo interface name got changed. |
| * |
| * @return True if ifinfo interface name got changed. |
| **/ |
| TEAM_EXPORT |
| bool team_is_ifinfo_ifname_changed(struct team_ifinfo *ifinfo) |
| { |
| return is_changed(ifinfo, CHANGED_IFNAME); |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details Get interface index of master interface. |
| * |
| * @return Master interface index as idenfified by in kernel. |
| **/ |
| TEAM_EXPORT |
| uint32_t team_get_ifinfo_master_ifindex(struct team_ifinfo *ifinfo) |
| { |
| return ifinfo->master_ifindex; |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details See if interface index of master interface got changed. |
| * |
| * @return True if interface index of master interface got changed. |
| **/ |
| TEAM_EXPORT |
| bool team_is_ifinfo_master_ifindex_changed(struct team_ifinfo *ifinfo) |
| { |
| return is_changed(ifinfo, CHANGED_MASTER_IFINDEX); |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details See if admin state of interface got changed. |
| * |
| * @return True if admin state of interface got changed. |
| **/ |
| TEAM_EXPORT |
| bool team_is_ifinfo_admin_state_changed(struct team_ifinfo *ifinfo) |
| { |
| return is_changed(ifinfo, CHANGED_ADMIN_STATE); |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details Get ifinfo physical port ID. |
| * |
| * @return Pointer to memory place where physical por ID is. |
| **/ |
| TEAM_EXPORT |
| char *team_get_ifinfo_phys_port_id(struct team_ifinfo *ifinfo) |
| { |
| return ifinfo->phys_port_id; |
| } |
| |
| /** |
| * team_is_ifinfo_phys_port_id_changed: |
| * |
| * @details See if ifinfo physical port ID got changed. |
| * |
| * @return True if physical port ID. got changed. |
| **/ |
| TEAM_EXPORT |
| bool team_is_ifinfo_phys_port_id_changed(struct team_ifinfo *ifinfo) |
| { |
| return is_changed(ifinfo, CHANGED_PHYS_PORT_ID); |
| } |
| |
| /** |
| * team_get_ifinfo_phys_port_id_len: |
| * |
| * @details Get ifinfo physical port ID length. |
| * |
| * @return Physical port ID length. |
| **/ |
| TEAM_EXPORT |
| size_t team_get_ifinfo_phys_port_id_len(struct team_ifinfo *ifinfo) |
| { |
| return ifinfo->phys_port_id_len; |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details See if ifinfo physical port ID length got changed. |
| * |
| * @return True if ifinfo physical port ID length changed. |
| **/ |
| TEAM_EXPORT |
| bool team_is_ifinfo_phys_port_id_len_changed(struct team_ifinfo *ifinfo) |
| { |
| return is_changed(ifinfo, CHANGED_PHYS_PORT_ID_LEN); |
| } |
| |
| /** |
| * @param ifinfo ifinfo structure |
| * |
| * @details See if ifinfo got changed. |
| * |
| * @return True if ifinfo changed. |
| **/ |
| TEAM_EXPORT |
| bool team_is_ifinfo_changed(struct team_ifinfo *ifinfo) |
| { |
| return is_changed(ifinfo, CHANGED_ANY); |
| } |
| |
| /** |
| * @} |
| */ |