| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include <errno.h> |
| #include <net/if.h> |
| #include <linux/if_arp.h> |
| #include <linux/if_vlan.h> |
| |
| #include "parse-util.h" |
| #include "vlan-util.h" |
| #include "vlan.h" |
| |
| static int netdev_vlan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *req) { |
| struct ifla_vlan_flags flags = {}; |
| VLan *v; |
| int r; |
| |
| assert(netdev); |
| assert(link); |
| assert(req); |
| |
| v = VLAN(netdev); |
| |
| assert(v); |
| |
| r = sd_netlink_message_append_u16(req, IFLA_VLAN_ID, v->id); |
| if (r < 0) |
| return r; |
| |
| if (v->protocol >= 0) { |
| r = sd_netlink_message_append_u16(req, IFLA_VLAN_PROTOCOL, htobe16(v->protocol)); |
| if (r < 0) |
| return r; |
| } |
| |
| if (v->gvrp != -1) { |
| flags.mask |= VLAN_FLAG_GVRP; |
| SET_FLAG(flags.flags, VLAN_FLAG_GVRP, v->gvrp); |
| } |
| |
| if (v->mvrp != -1) { |
| flags.mask |= VLAN_FLAG_MVRP; |
| SET_FLAG(flags.flags, VLAN_FLAG_MVRP, v->mvrp); |
| } |
| |
| if (v->reorder_hdr != -1) { |
| flags.mask |= VLAN_FLAG_REORDER_HDR; |
| SET_FLAG(flags.flags, VLAN_FLAG_REORDER_HDR, v->reorder_hdr); |
| } |
| |
| if (v->loose_binding != -1) { |
| flags.mask |= VLAN_FLAG_LOOSE_BINDING; |
| SET_FLAG(flags.flags, VLAN_FLAG_LOOSE_BINDING, v->loose_binding); |
| } |
| |
| r = sd_netlink_message_append_data(req, IFLA_VLAN_FLAGS, &flags, sizeof(struct ifla_vlan_flags)); |
| if (r < 0) |
| return r; |
| |
| if (!set_isempty(v->egress_qos_maps)) { |
| struct ifla_vlan_qos_mapping *m; |
| |
| r = sd_netlink_message_open_container(req, IFLA_VLAN_EGRESS_QOS); |
| if (r < 0) |
| return r; |
| |
| SET_FOREACH(m, v->egress_qos_maps) { |
| r = sd_netlink_message_append_data(req, IFLA_VLAN_QOS_MAPPING, m, sizeof(struct ifla_vlan_qos_mapping)); |
| if (r < 0) |
| return r; |
| } |
| |
| r = sd_netlink_message_close_container(req); |
| if (r < 0) |
| return r; |
| } |
| |
| if (!set_isempty(v->ingress_qos_maps)) { |
| struct ifla_vlan_qos_mapping *m; |
| |
| r = sd_netlink_message_open_container(req, IFLA_VLAN_INGRESS_QOS); |
| if (r < 0) |
| return r; |
| |
| SET_FOREACH(m, v->ingress_qos_maps) { |
| r = sd_netlink_message_append_data(req, IFLA_VLAN_QOS_MAPPING, m, sizeof(struct ifla_vlan_qos_mapping)); |
| if (r < 0) |
| return r; |
| } |
| |
| r = sd_netlink_message_close_container(req); |
| if (r < 0) |
| return r; |
| } |
| |
| return 0; |
| } |
| |
| static void vlan_qos_maps_hash_func(const struct ifla_vlan_qos_mapping *x, struct siphash *state) { |
| siphash24_compress(&x->from, sizeof(x->from), state); |
| siphash24_compress(&x->to, sizeof(x->to), state); |
| } |
| |
| static int vlan_qos_maps_compare_func(const struct ifla_vlan_qos_mapping *a, const struct ifla_vlan_qos_mapping *b) { |
| int r; |
| |
| r = CMP(a->from, b->from); |
| if (r != 0) |
| return r; |
| |
| return CMP(a->to, b->to); |
| } |
| |
| DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( |
| vlan_qos_maps_hash_ops, |
| struct ifla_vlan_qos_mapping, |
| vlan_qos_maps_hash_func, |
| vlan_qos_maps_compare_func, |
| free); |
| |
| int config_parse_vlan_qos_maps( |
| 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) { |
| |
| Set **s = ASSERT_PTR(data); |
| int r; |
| |
| assert(filename); |
| assert(lvalue); |
| assert(rvalue); |
| |
| if (isempty(rvalue)) { |
| *s = set_free(*s); |
| return 0; |
| } |
| |
| for (const char *p = rvalue;;) { |
| _cleanup_free_ struct ifla_vlan_qos_mapping *m = NULL; |
| _cleanup_free_ char *w = NULL; |
| |
| r = extract_first_word(&p, &w, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); |
| if (r == -ENOMEM) |
| return log_oom(); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse %s, ignoring: %s", lvalue, rvalue); |
| return 0; |
| } |
| if (r == 0) |
| return 0; |
| |
| m = new0(struct ifla_vlan_qos_mapping, 1); |
| if (!m) |
| return log_oom(); |
| |
| r = parse_range(w, &m->from, &m->to); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse %s, ignoring: %s", lvalue, w); |
| continue; |
| } |
| |
| if (m->to > m->from || m->to == 0 || m->from == 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid %s, ignoring: %s", lvalue, w); |
| continue; |
| } |
| |
| r = set_ensure_consume(s, &vlan_qos_maps_hash_ops, TAKE_PTR(m)); |
| if (r < 0) { |
| log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to store %s, ignoring: %s", lvalue, w); |
| continue; |
| } |
| } |
| } |
| |
| static int netdev_vlan_verify(NetDev *netdev, const char *filename) { |
| VLan *v; |
| |
| assert(netdev); |
| assert(filename); |
| |
| v = VLAN(netdev); |
| |
| assert(v); |
| |
| if (v->id == VLANID_INVALID) { |
| log_netdev_warning(netdev, "VLAN without valid Id (%"PRIu16") configured in %s.", v->id, filename); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static void vlan_done(NetDev *n) { |
| VLan *v; |
| |
| v = VLAN(n); |
| |
| assert(v); |
| |
| set_free(v->egress_qos_maps); |
| set_free(v->ingress_qos_maps); |
| } |
| |
| static void vlan_init(NetDev *netdev) { |
| VLan *v = VLAN(netdev); |
| |
| assert(netdev); |
| assert(v); |
| |
| v->id = VLANID_INVALID; |
| v->protocol = -1; |
| v->gvrp = -1; |
| v->mvrp = -1; |
| v->loose_binding = -1; |
| v->reorder_hdr = -1; |
| } |
| |
| const NetDevVTable vlan_vtable = { |
| .object_size = sizeof(VLan), |
| .init = vlan_init, |
| .sections = NETDEV_COMMON_SECTIONS "VLAN\0", |
| .fill_message_create = netdev_vlan_fill_message_create, |
| .create_type = NETDEV_CREATE_STACKED, |
| .config_verify = netdev_vlan_verify, |
| .done = vlan_done, |
| .iftype = ARPHRD_ETHER, |
| }; |