| /* |
| * teamd_phys_port_check.c - Physical port checking support for teamd |
| * Copyright (C) 2013-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 |
| */ |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include "teamd.h" |
| |
| struct pcie_addr { |
| uint16_t domain; |
| uint8_t bus; |
| uint8_t slot; |
| uint8_t function; |
| }; |
| |
| static int teamd_sriov_physfn_addr(struct pcie_addr *addr, const char *ifname) |
| { |
| char link[256]; |
| char *path; |
| char *start; |
| int ret; |
| |
| ret = asprintf(&path, "/sys/class/net/%s/device/physfn", ifname); |
| if (ret == -1) |
| return -ENOMEM; |
| |
| ret = readlink(path, link, sizeof(link) - 1); |
| free(path); |
| if (ret == -1) |
| return -errno; |
| link[ret] = '\0'; |
| |
| start = strrchr(link, '/'); |
| if (!start) |
| return -EINVAL; |
| start++; |
| memset(addr, 0, sizeof(*addr)); |
| ret = sscanf(start, "%04x:%02x:%02x.%x", (unsigned int *) &addr->domain, |
| (unsigned int *) &addr->bus, |
| (unsigned int *) &addr->slot, |
| (unsigned int *) &addr->function); |
| if (ret != 4) |
| return -EINVAL; |
| return 0; |
| } |
| |
| static bool teamd_phys_port_sriovsysfs_cmp(struct teamd_port *tdport1, |
| struct teamd_port *tdport2) |
| { |
| struct pcie_addr physfnaddr1; |
| struct pcie_addr physfnaddr2; |
| int err; |
| |
| err = teamd_sriov_physfn_addr(&physfnaddr1, tdport1->ifname); |
| if (err) |
| return false; |
| |
| err = teamd_sriov_physfn_addr(&physfnaddr2, tdport2->ifname); |
| if (err) |
| return false; |
| if (!memcmp(&physfnaddr1, &physfnaddr2, sizeof(physfnaddr1))) |
| return true; |
| return false; |
| } |
| |
| static bool teamd_phys_port_ifinfo_cmp(struct teamd_port *tdport1, |
| struct teamd_port *tdport2) |
| { |
| const char *id1 = team_get_ifinfo_phys_port_id(tdport1->team_ifinfo); |
| const char *id2 = team_get_ifinfo_phys_port_id(tdport2->team_ifinfo); |
| size_t id1_len = team_get_ifinfo_phys_port_id_len(tdport1->team_ifinfo); |
| size_t id2_len = team_get_ifinfo_phys_port_id_len(tdport2->team_ifinfo); |
| |
| if (!id1_len || !id2_len || id1_len != id2_len) |
| return false; |
| |
| if (!memcmp(id1, id2, id1_len)) |
| return true; |
| return false; |
| } |
| |
| static int teamd_phys_port_check_event_watch_port_added(struct teamd_context *ctx, |
| struct teamd_port *tdport, |
| void *priv) |
| { |
| struct teamd_port *cur_tdport; |
| |
| teamd_for_each_tdport(cur_tdport, ctx) { |
| if (cur_tdport == tdport) |
| continue; |
| /* Once all drivers implement ndo_get_phys_port_id() function, |
| * teamd_phys_port_sriovsysfs_cmp() would not be needed to be |
| * called here. |
| */ |
| if (teamd_phys_port_ifinfo_cmp(tdport, cur_tdport) || |
| teamd_phys_port_sriovsysfs_cmp(tdport, cur_tdport)) |
| teamd_log_warn("%s: device is using the same physical port as device %s. Note that teaming multiple devices which use the same physical port makes no sense.", |
| tdport->ifname, cur_tdport->ifname); |
| } |
| return 0; |
| } |
| |
| static const struct teamd_event_watch_ops teamd_phys_port_check_event_watch_ops = { |
| .port_added = teamd_phys_port_check_event_watch_port_added, |
| }; |
| |
| int teamd_phys_port_check_init(struct teamd_context *ctx) |
| { |
| int err; |
| |
| err = teamd_event_watch_register(ctx, &teamd_phys_port_check_event_watch_ops, |
| NULL); |
| if (err) { |
| teamd_log_err("Failed to register event watch."); |
| return err; |
| } |
| return 0; |
| } |
| |
| void teamd_phys_port_check_fini(struct teamd_context *ctx) |
| { |
| teamd_event_watch_unregister(ctx, &teamd_phys_port_check_event_watch_ops, |
| NULL); |
| } |