| /* SPDX-License-Identifier: LGPL-2.1-or-later |
| * Copyright © 2020 VMware, Inc. */ |
| |
| #include "alloc-util.h" |
| #include "netlink-util.h" |
| #include "networkd-manager.h" |
| #include "networkd-sriov.h" |
| #include "parse-util.h" |
| #include "set.h" |
| #include "string-util.h" |
| |
| static int sr_iov_new(SRIOV **ret) { |
| SRIOV *sr_iov; |
| |
| sr_iov = new(SRIOV, 1); |
| if (!sr_iov) |
| return -ENOMEM; |
| |
| *sr_iov = (SRIOV) { |
| .vf = (uint32_t) -1, |
| .vlan_proto = ETH_P_8021Q, |
| .vf_spoof_check_setting = -1, |
| .trust = -1, |
| .query_rss = -1, |
| .link_state = _SR_IOV_LINK_STATE_INVALID, |
| }; |
| |
| *ret = TAKE_PTR(sr_iov); |
| |
| return 0; |
| } |
| |
| static int sr_iov_new_static(Network *network, const char *filename, unsigned section_line, SRIOV **ret) { |
| _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; |
| _cleanup_(sr_iov_freep) SRIOV *sr_iov = NULL; |
| SRIOV *existing = NULL; |
| int r; |
| |
| assert(network); |
| assert(ret); |
| assert(filename); |
| assert(section_line > 0); |
| |
| r = network_config_section_new(filename, section_line, &n); |
| if (r < 0) |
| return r; |
| |
| existing = ordered_hashmap_get(network->sr_iov_by_section, n); |
| if (existing) { |
| *ret = existing; |
| return 0; |
| } |
| |
| r = sr_iov_new(&sr_iov); |
| if (r < 0) |
| return r; |
| |
| sr_iov->network = network; |
| sr_iov->section = TAKE_PTR(n); |
| |
| r = ordered_hashmap_ensure_allocated(&network->sr_iov_by_section, &network_config_hash_ops); |
| if (r < 0) |
| return r; |
| |
| r = ordered_hashmap_put(network->sr_iov_by_section, sr_iov->section, sr_iov); |
| if (r < 0) |
| return r; |
| |
| *ret = TAKE_PTR(sr_iov); |
| return 0; |
| } |
| |
| SRIOV *sr_iov_free(SRIOV *sr_iov) { |
| if (!sr_iov) |
| return NULL; |
| |
| if (sr_iov->network && sr_iov->section) |
| ordered_hashmap_remove(sr_iov->network->sr_iov_by_section, sr_iov->section); |
| |
| network_config_section_free(sr_iov->section); |
| |
| return mfree(sr_iov); |
| } |
| |
| static int sr_iov_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { |
| int r; |
| |
| assert(link); |
| assert(link->sr_iov_messages > 0); |
| link->sr_iov_messages--; |
| |
| if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) |
| return 1; |
| |
| r = sd_netlink_message_get_errno(m); |
| if (r < 0 && r != -EEXIST) { |
| log_link_message_error_errno(link, m, r, "Could not set up SR-IOV"); |
| link_enter_failed(link); |
| return 1; |
| } |
| |
| if (link->sr_iov_messages == 0) { |
| log_link_debug(link, "SR-IOV configured"); |
| link->sr_iov_configured = true; |
| link_check_ready(link); |
| } |
| |
| return 1; |
| } |
| |
| static int sr_iov_configure(Link *link, SRIOV *sr_iov) { |
| _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; |
| int r; |
| |
| assert(link); |
| assert(link->manager); |
| assert(link->manager->rtnl); |
| assert(link->ifindex > 0); |
| |
| log_link_debug(link, "Setting SR-IOV virtual function %"PRIu32, sr_iov->vf); |
| |
| r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); |
| |
| r = sd_netlink_message_open_container(req, IFLA_VFINFO_LIST); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not open IFLA_VFINFO_LIST container: %m"); |
| |
| r = sd_netlink_message_open_container(req, IFLA_VF_INFO); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not open IFLA_VF_INFO container: %m"); |
| |
| if (!ether_addr_is_null(&sr_iov->mac)) { |
| struct ifla_vf_mac ivm = { |
| .vf = sr_iov->vf, |
| }; |
| |
| memcpy(ivm.mac, &sr_iov->mac, ETH_ALEN); |
| r = sd_netlink_message_append_data(req, IFLA_VF_MAC, &ivm, sizeof(struct ifla_vf_mac)); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not append IFLA_VF_MAC: %m"); |
| } |
| |
| if (sr_iov->vf_spoof_check_setting >= 0) { |
| struct ifla_vf_spoofchk ivs = { |
| .vf = sr_iov->vf, |
| .setting = sr_iov->vf_spoof_check_setting, |
| }; |
| |
| r = sd_netlink_message_append_data(req, IFLA_VF_SPOOFCHK, &ivs, sizeof(struct ifla_vf_spoofchk)); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not append IFLA_VF_SPOOFCHK: %m"); |
| } |
| |
| if (sr_iov->query_rss >= 0) { |
| struct ifla_vf_rss_query_en ivs = { |
| .vf = sr_iov->vf, |
| .setting = sr_iov->query_rss, |
| }; |
| |
| r = sd_netlink_message_append_data(req, IFLA_VF_RSS_QUERY_EN, &ivs, sizeof(struct ifla_vf_rss_query_en)); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not append IFLA_VF_RSS_QUERY_EN: %m"); |
| } |
| |
| if (sr_iov->trust >= 0) { |
| struct ifla_vf_trust ivt = { |
| .vf = sr_iov->vf, |
| .setting = sr_iov->trust, |
| }; |
| |
| r = sd_netlink_message_append_data(req, IFLA_VF_TRUST, &ivt, sizeof(struct ifla_vf_trust)); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not append IFLA_VF_TRUST: %m"); |
| } |
| |
| if (sr_iov->link_state >= 0) { |
| struct ifla_vf_link_state ivl = { |
| .vf = sr_iov->vf, |
| .link_state = sr_iov->link_state, |
| }; |
| |
| r = sd_netlink_message_append_data(req, IFLA_VF_LINK_STATE, &ivl, sizeof(struct ifla_vf_link_state)); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not append IFLA_VF_LINK_STATE: %m"); |
| } |
| |
| if (sr_iov->vlan > 0) { |
| /* Because of padding, first the buffer must be initialized with 0. */ |
| struct ifla_vf_vlan_info ivvi = {}; |
| ivvi.vf = sr_iov->vf; |
| ivvi.vlan = sr_iov->vlan; |
| ivvi.qos = sr_iov->qos; |
| ivvi.vlan_proto = htobe16(sr_iov->vlan_proto); |
| |
| r = sd_netlink_message_open_container(req, IFLA_VF_VLAN_LIST); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not open IFLA_VF_VLAN_LIST container: %m"); |
| |
| r = sd_netlink_message_append_data(req, IFLA_VF_VLAN_INFO, &ivvi, sizeof(struct ifla_vf_vlan_info)); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not append IFLA_VF_VLAN_INFO: %m"); |
| |
| r = sd_netlink_message_close_container(req); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not close IFLA_VF_VLAN_LIST container: %m"); |
| } |
| |
| r = sd_netlink_message_close_container(req); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not close IFLA_VF_INFO container: %m"); |
| |
| r = sd_netlink_message_close_container(req); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not close IFLA_VFINFO_LIST container: %m"); |
| |
| r = netlink_call_async(link->manager->rtnl, NULL, req, sr_iov_handler, |
| link_netlink_destroy_callback, link); |
| if (r < 0) |
| return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); |
| |
| link_ref(link); |
| link->sr_iov_messages++; |
| |
| return 0; |
| } |
| |
| int link_configure_sr_iov(Link *link) { |
| SRIOV *sr_iov; |
| int r; |
| |
| link->sr_iov_configured = false; |
| link->sr_iov_messages = 0; |
| |
| ORDERED_HASHMAP_FOREACH(sr_iov, link->network->sr_iov_by_section) { |
| r = sr_iov_configure(link, sr_iov); |
| if (r < 0) |
| return r; |
| } |
| |
| if (link->sr_iov_messages == 0) |
| link->sr_iov_configured = true; |
| else |
| log_link_debug(link, "Configuring SR-IOV"); |
| |
| return 0; |
| } |
| |
| static int sr_iov_section_verify(SRIOV *sr_iov) { |
| assert(sr_iov); |
| |
| if (section_is_invalid(sr_iov->section)) |
| return -EINVAL; |
| |
| if (sr_iov->vf == (uint32_t) -1) |
| return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), |
| "%s: [SRIOV] section without VirtualFunction= field configured. " |
| "Ignoring [SRIOV] section from line %u.", |
| sr_iov->section->filename, sr_iov->section->line); |
| |
| return 0; |
| } |
| |
| void network_drop_invalid_sr_iov(Network *network) { |
| SRIOV *sr_iov; |
| |
| assert(network); |
| |
| ORDERED_HASHMAP_FOREACH(sr_iov, network->sr_iov_by_section) |
| if (sr_iov_section_verify(sr_iov) < 0) |
| sr_iov_free(sr_iov); |
| } |
| |
| int config_parse_sr_iov_uint32( |
| const char *unit, |
| const char *filename, |
| unsigned line, |
| const char *section, |
| unsigned section_line, |
| const char *lvalue, |
| int ltype, |
| const char *rvalue, |
| void *data, |
| void *userdata) { |
| |
| _cleanup_(sr_iov_free_or_set_invalidp) SRIOV *sr_iov = NULL; |
| Network *network = data; |
| uint32_t k; |
| int r; |
| |
| assert(filename); |
| assert(lvalue); |
| assert(rvalue); |
| assert(data); |
| |
| r = sr_iov_new_static(network, filename, section_line, &sr_iov); |
| if (r < 0) |
| return r; |
| |
| if (isempty(rvalue)) { |
| if (streq(lvalue, "VirtualFunction")) |
| sr_iov->vf = (uint32_t) -1; |
| else if (streq(lvalue, "VLANId")) |
| sr_iov->vlan = 0; |
| else if (streq(lvalue, "QualityOfService")) |
| sr_iov->qos = 0; |
| else |
| assert_not_reached("Invalid lvalue"); |
| |
| TAKE_PTR(sr_iov); |
| return 0; |
| } |
| |
| r = safe_atou32(rvalue, &k); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, |
| "Failed to parse SR-IOV '%s=', ignoring assignment: %s", lvalue, rvalue); |
| return 0; |
| } |
| |
| if (streq(lvalue, "VLANId")) { |
| if (k == 0 || k > 4095) { |
| log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid SR-IOV VLANId: %d", k); |
| return 0; |
| } |
| sr_iov->vlan = k; |
| } else if (streq(lvalue, "VirtualFunction")) { |
| if (k >= INT_MAX) { |
| log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid SR-IOV virtual function: %d", k); |
| return 0; |
| } |
| sr_iov->vf = k; |
| } else if (streq(lvalue, "QualityOfService")) |
| sr_iov->qos = k; |
| else |
| assert_not_reached("Invalid lvalue"); |
| |
| TAKE_PTR(sr_iov); |
| return 0; |
| } |
| |
| int config_parse_sr_iov_vlan_proto( |
| const char *unit, |
| const char *filename, |
| unsigned line, |
| const char *section, |
| unsigned section_line, |
| const char *lvalue, |
| int ltype, |
| const char *rvalue, |
| void *data, |
| void *userdata) { |
| |
| _cleanup_(sr_iov_free_or_set_invalidp) SRIOV *sr_iov = NULL; |
| Network *network = data; |
| int r; |
| |
| assert(filename); |
| assert(lvalue); |
| assert(rvalue); |
| assert(data); |
| |
| r = sr_iov_new_static(network, filename, section_line, &sr_iov); |
| if (r < 0) |
| return r; |
| |
| if (isempty(rvalue) || streq(rvalue, "802.1Q")) |
| sr_iov->vlan_proto = ETH_P_8021Q; |
| else if (streq(rvalue, "802.1ad")) |
| sr_iov->vlan_proto = ETH_P_8021AD; |
| else { |
| log_syntax(unit, LOG_WARNING, filename, line, 0, |
| "Invalid SR-IOV '%s=', ignoring assignment: %s", lvalue, rvalue); |
| return 0; |
| } |
| |
| TAKE_PTR(sr_iov); |
| return 0; |
| } |
| |
| int config_parse_sr_iov_link_state( |
| const char *unit, |
| const char *filename, |
| unsigned line, |
| const char *section, |
| unsigned section_line, |
| const char *lvalue, |
| int ltype, |
| const char *rvalue, |
| void *data, |
| void *userdata) { |
| |
| _cleanup_(sr_iov_free_or_set_invalidp) SRIOV *sr_iov = NULL; |
| Network *network = data; |
| int r; |
| |
| assert(filename); |
| assert(lvalue); |
| assert(rvalue); |
| assert(data); |
| |
| r = sr_iov_new_static(network, filename, section_line, &sr_iov); |
| if (r < 0) |
| return r; |
| |
| /* Unfortunately, SR_IOV_LINK_STATE_DISABLE is 2, not 0. So, we cannot use |
| * DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN() macro. */ |
| |
| if (isempty(rvalue)) { |
| sr_iov->link_state = _SR_IOV_LINK_STATE_INVALID; |
| TAKE_PTR(sr_iov); |
| return 0; |
| } |
| |
| if (streq(rvalue, "auto")) { |
| sr_iov->link_state = SR_IOV_LINK_STATE_AUTO; |
| TAKE_PTR(sr_iov); |
| return 0; |
| } |
| |
| r = parse_boolean(rvalue); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, |
| "Failed to parse SR-IOV '%s=', ignoring assignment: %s", lvalue, rvalue); |
| return 0; |
| } |
| |
| sr_iov->link_state = r ? SR_IOV_LINK_STATE_ENABLE : SR_IOV_LINK_STATE_DISABLE; |
| TAKE_PTR(sr_iov); |
| return 0; |
| } |
| |
| int config_parse_sr_iov_boolean( |
| const char *unit, |
| const char *filename, |
| unsigned line, |
| const char *section, |
| unsigned section_line, |
| const char *lvalue, |
| int ltype, |
| const char *rvalue, |
| void *data, |
| void *userdata) { |
| |
| _cleanup_(sr_iov_free_or_set_invalidp) SRIOV *sr_iov = NULL; |
| Network *network = data; |
| int r; |
| |
| assert(filename); |
| assert(lvalue); |
| assert(rvalue); |
| assert(data); |
| |
| r = sr_iov_new_static(network, filename, section_line, &sr_iov); |
| if (r < 0) |
| return r; |
| |
| if (isempty(rvalue)) { |
| if (streq(lvalue, "MACSpoofCheck")) |
| sr_iov->vf_spoof_check_setting = -1; |
| else if (streq(lvalue, "QueryReceiveSideScaling")) |
| sr_iov->query_rss = -1; |
| else if (streq(lvalue, "Trust")) |
| sr_iov->trust = -1; |
| else |
| assert_not_reached("Invalid lvalue"); |
| |
| TAKE_PTR(sr_iov); |
| return 0; |
| } |
| |
| r = parse_boolean(rvalue); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse '%s=', ignoring: %s", lvalue, rvalue); |
| return 0; |
| } |
| |
| if (streq(lvalue, "MACSpoofCheck")) |
| sr_iov->vf_spoof_check_setting = r; |
| else if (streq(lvalue, "QueryReceiveSideScaling")) |
| sr_iov->query_rss = r; |
| else if (streq(lvalue, "Trust")) |
| sr_iov->trust = r; |
| else |
| assert_not_reached("Invalid lvalue"); |
| |
| TAKE_PTR(sr_iov); |
| return 0; |
| } |
| |
| int config_parse_sr_iov_mac( |
| const char *unit, |
| const char *filename, |
| unsigned line, |
| const char *section, |
| unsigned section_line, |
| const char *lvalue, |
| int ltype, |
| const char *rvalue, |
| void *data, |
| void *userdata) { |
| |
| _cleanup_(sr_iov_free_or_set_invalidp) SRIOV *sr_iov = NULL; |
| Network *network = data; |
| int r; |
| |
| assert(filename); |
| assert(lvalue); |
| assert(rvalue); |
| assert(data); |
| |
| r = sr_iov_new_static(network, filename, section_line, &sr_iov); |
| if (r < 0) |
| return r; |
| |
| if (isempty(rvalue)) { |
| sr_iov->mac = ETHER_ADDR_NULL; |
| TAKE_PTR(sr_iov); |
| return 0; |
| } |
| |
| r = ether_addr_from_string(rvalue, &sr_iov->mac); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, |
| "Failed to parse SR-IOV '%s=', ignoring assignment: %s", lvalue, rvalue); |
| return 0; |
| } |
| |
| TAKE_PTR(sr_iov); |
| return 0; |
| } |