/* SPDX-License-Identifier: LGPL-2.1-only */ /* * Copyright (c) 2010-2013 Thomas Graf */ /** * @ingroup link * @defgroup bridge Bridging * * @details * @{ */ #include #include #include #include #include #include #include #define VLAN_VID_MASK 0x0fff /* VLAN Identifier */ /** @cond SKIP */ #define BRIDGE_ATTR_PORT_STATE (1 << 0) #define BRIDGE_ATTR_PRIORITY (1 << 1) #define BRIDGE_ATTR_COST (1 << 2) #define BRIDGE_ATTR_FLAGS (1 << 3) #define BRIDGE_ATTR_PORT_VLAN (1 << 4) #define BRIDGE_ATTR_HWMODE (1 << 5) #define BRIDGE_ATTR_SELF (1 << 6) #define PRIV_FLAG_NEW_ATTRS (1 << 0) struct bridge_data { uint8_t b_port_state; uint8_t b_priv_flags; /* internal flags */ uint16_t b_hwmode; uint16_t b_priority; uint16_t b_self; /* here for comparison reasons */ uint32_t b_cost; uint32_t b_flags; uint32_t b_flags_mask; uint32_t ce_mask; /* HACK to support attr macros */ struct rtnl_link_bridge_vlan vlan_info; }; static void set_bit(unsigned nr, uint32_t *addr) { if (nr < RTNL_LINK_BRIDGE_VLAN_BITMAP_MAX) addr[nr / 32] |= (((uint32_t) 1) << (nr % 32)); } static int find_next_bit(int i, uint32_t x) { int j; if (i >= 32) return -1; /* find first bit */ if (i < 0) return __builtin_ffs(x); /* mask off prior finds to get next */ j = __builtin_ffs(x >> i); return j ? j + i : 0; } static struct rtnl_link_af_ops bridge_ops; #define IS_BRIDGE_LINK_ASSERT(link) \ if (!rtnl_link_is_bridge(link)) { \ APPBUG("A function was expecting a link object of type bridge."); \ return -NLE_OPNOTSUPP; \ } static inline struct bridge_data *bridge_data(struct rtnl_link *link) { return rtnl_link_af_data(link, &bridge_ops); } static void *bridge_alloc(struct rtnl_link *link) { return calloc(1, sizeof(struct bridge_data)); } static void *bridge_clone(struct rtnl_link *link, void *data) { struct bridge_data *bd; if ((bd = bridge_alloc(link))) memcpy(bd, data, sizeof(*bd)); return bd; } static void bridge_free(struct rtnl_link *link, void *data) { free(data); } static struct nla_policy br_attrs_policy[IFLA_BRPORT_MAX+1] = { [IFLA_BRPORT_STATE] = { .type = NLA_U8 }, [IFLA_BRPORT_PRIORITY] = { .type = NLA_U16 }, [IFLA_BRPORT_COST] = { .type = NLA_U32 }, [IFLA_BRPORT_MODE] = { .type = NLA_U8 }, [IFLA_BRPORT_GUARD] = { .type = NLA_U8 }, [IFLA_BRPORT_PROTECT] = { .type = NLA_U8 }, [IFLA_BRPORT_FAST_LEAVE] = { .type = NLA_U8 }, [IFLA_BRPORT_LEARNING] = { .type = NLA_U8 }, [IFLA_BRPORT_LEARNING_SYNC] = { .type = NLA_U8 }, [IFLA_BRPORT_UNICAST_FLOOD] = { .type = NLA_U8 }, }; static void check_flag(struct rtnl_link *link, struct nlattr *attrs[], int type, int flag) { if (attrs[type] && nla_get_u8(attrs[type])) rtnl_link_bridge_set_flags(link, flag); } static int bridge_parse_protinfo(struct rtnl_link *link, struct nlattr *attr, void *data) { struct bridge_data *bd = data; struct nlattr *br_attrs[IFLA_BRPORT_MAX+1]; int err; /* Backwards compatibility */ if (!nla_is_nested(attr)) { if (nla_len(attr) < 1) return -NLE_RANGE; bd->b_port_state = nla_get_u8(attr); bd->ce_mask |= BRIDGE_ATTR_PORT_STATE; return 0; } if ((err = nla_parse_nested(br_attrs, IFLA_BRPORT_MAX, attr, br_attrs_policy)) < 0) return err; bd->b_priv_flags |= PRIV_FLAG_NEW_ATTRS; if (br_attrs[IFLA_BRPORT_STATE]) { bd->b_port_state = nla_get_u8(br_attrs[IFLA_BRPORT_STATE]); bd->ce_mask |= BRIDGE_ATTR_PORT_STATE; } if (br_attrs[IFLA_BRPORT_PRIORITY]) { bd->b_priority = nla_get_u16(br_attrs[IFLA_BRPORT_PRIORITY]); bd->ce_mask |= BRIDGE_ATTR_PRIORITY; } if (br_attrs[IFLA_BRPORT_COST]) { bd->b_cost = nla_get_u32(br_attrs[IFLA_BRPORT_COST]); bd->ce_mask |= BRIDGE_ATTR_COST; } check_flag(link, br_attrs, IFLA_BRPORT_MODE, RTNL_BRIDGE_HAIRPIN_MODE); check_flag(link, br_attrs, IFLA_BRPORT_GUARD, RTNL_BRIDGE_BPDU_GUARD); check_flag(link, br_attrs, IFLA_BRPORT_PROTECT, RTNL_BRIDGE_ROOT_BLOCK); check_flag(link, br_attrs, IFLA_BRPORT_FAST_LEAVE, RTNL_BRIDGE_FAST_LEAVE); check_flag(link, br_attrs, IFLA_BRPORT_UNICAST_FLOOD, RTNL_BRIDGE_UNICAST_FLOOD); check_flag(link, br_attrs, IFLA_BRPORT_LEARNING, RTNL_BRIDGE_LEARNING); check_flag(link, br_attrs, IFLA_BRPORT_LEARNING_SYNC, RTNL_BRIDGE_LEARNING_SYNC); return 0; } static int bridge_parse_af_full(struct rtnl_link *link, struct nlattr *attr_full, void *data) { struct bridge_data *bd = data; struct bridge_vlan_info *vinfo = NULL; uint16_t vid_range_start = 0; uint16_t vid_range_flags = -1; struct nlattr *attr; int remaining; nla_for_each_nested(attr, attr_full, remaining) { if (nla_type(attr) == IFLA_BRIDGE_MODE) { bd->b_hwmode = nla_get_u16(attr); bd->ce_mask |= BRIDGE_ATTR_HWMODE; } else if (nla_type(attr) != IFLA_BRIDGE_VLAN_INFO) continue; if (nla_len(attr) != sizeof(struct bridge_vlan_info)) return -EINVAL; vinfo = nla_data(attr); if (!vinfo->vid || vinfo->vid >= VLAN_VID_MASK) return -EINVAL; if (vinfo->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) { vid_range_start = vinfo->vid; vid_range_flags = (vinfo->flags ^ BRIDGE_VLAN_INFO_RANGE_BEGIN); continue; } if (vinfo->flags & BRIDGE_VLAN_INFO_RANGE_END) { /* sanity check the range flags */ if (vid_range_flags != (vinfo->flags ^ BRIDGE_VLAN_INFO_RANGE_END)) { NL_DBG(1, "VLAN range flags differ; can not handle it.\n"); return -EINVAL; } } else { vid_range_start = vinfo->vid; } for (; vid_range_start <= vinfo->vid; vid_range_start++) { if (vinfo->flags & BRIDGE_VLAN_INFO_PVID) bd->vlan_info.pvid = vinfo->vid; if (vinfo->flags & BRIDGE_VLAN_INFO_UNTAGGED) set_bit(vid_range_start, bd->vlan_info.untagged_bitmap); set_bit(vid_range_start, bd->vlan_info.vlan_bitmap); bd->ce_mask |= BRIDGE_ATTR_PORT_VLAN; } vid_range_flags = -1; } return 0; } static int bridge_fill_af(struct rtnl_link *link, struct nl_msg *msg, void *data) { struct bridge_data *bd = data; if ((bd->ce_mask & BRIDGE_ATTR_SELF)||(bd->ce_mask & BRIDGE_ATTR_HWMODE)) NLA_PUT_U16(msg, IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF); if (bd->ce_mask & BRIDGE_ATTR_HWMODE) NLA_PUT_U16(msg, IFLA_BRIDGE_MODE, bd->b_hwmode); return 0; nla_put_failure: return -NLE_MSGSIZE; } static int bridge_fill_pi(struct rtnl_link *link, struct nl_msg *msg, void *data) { struct bridge_data *bd = data; if (bd->ce_mask & BRIDGE_ATTR_FLAGS) { if (bd->b_flags_mask & RTNL_BRIDGE_BPDU_GUARD) { NLA_PUT_U8(msg, IFLA_BRPORT_GUARD, bd->b_flags & RTNL_BRIDGE_BPDU_GUARD); } if (bd->b_flags_mask & RTNL_BRIDGE_HAIRPIN_MODE) { NLA_PUT_U8(msg, IFLA_BRPORT_MODE, bd->b_flags & RTNL_BRIDGE_HAIRPIN_MODE); } if (bd->b_flags_mask & RTNL_BRIDGE_FAST_LEAVE) { NLA_PUT_U8(msg, IFLA_BRPORT_FAST_LEAVE, bd->b_flags & RTNL_BRIDGE_FAST_LEAVE); } if (bd->b_flags_mask & RTNL_BRIDGE_ROOT_BLOCK) { NLA_PUT_U8(msg, IFLA_BRPORT_PROTECT, bd->b_flags & RTNL_BRIDGE_ROOT_BLOCK); } if (bd->b_flags_mask & RTNL_BRIDGE_UNICAST_FLOOD) { NLA_PUT_U8(msg, IFLA_BRPORT_UNICAST_FLOOD, bd->b_flags & RTNL_BRIDGE_UNICAST_FLOOD); } if (bd->b_flags_mask & RTNL_BRIDGE_LEARNING) { NLA_PUT_U8(msg, IFLA_BRPORT_LEARNING, bd->b_flags & RTNL_BRIDGE_LEARNING); } if (bd->b_flags_mask & RTNL_BRIDGE_LEARNING_SYNC) { NLA_PUT_U8(msg, IFLA_BRPORT_LEARNING_SYNC, bd->b_flags & RTNL_BRIDGE_LEARNING_SYNC); } } if (bd->ce_mask & BRIDGE_ATTR_COST) NLA_PUT_U32(msg, IFLA_BRPORT_COST, bd->b_cost); if (bd->ce_mask & BRIDGE_ATTR_PRIORITY) NLA_PUT_U16(msg, IFLA_BRPORT_PRIORITY, bd->b_priority); if (bd->ce_mask & BRIDGE_ATTR_PORT_STATE) NLA_PUT_U8(msg, IFLA_BRPORT_STATE, bd->b_port_state); return 0; nla_put_failure: return -NLE_MSGSIZE; } static int bridge_override_rtm(struct rtnl_link *link) { struct bridge_data *bd; if (!rtnl_link_is_bridge(link)) return 0; bd = bridge_data(link); if (bd->ce_mask & BRIDGE_ATTR_FLAGS) return 1; return 0; } static int bridge_get_af(struct nl_msg *msg, uint32_t *ext_filter_mask) { *ext_filter_mask |= RTEXT_FILTER_BRVLAN; return 0; } static void dump_bitmap(struct nl_dump_params *p, const uint32_t *b) { int i = -1, j, k; int start = -1, prev = -1; int done, found = 0; for (k = 0; k < RTNL_LINK_BRIDGE_VLAN_BITMAP_LEN; k++) { int base_bit; uint32_t a = b[k]; base_bit = k * 32; i = -1; done = 0; while (!done) { j = find_next_bit(i, a); if (j > 0) { /* first hit of any bit */ if (start < 0 && prev < 0) { start = prev = j - 1 + base_bit; goto next; } /* this bit is a continuation of prior bits */ if (j - 2 + base_bit == prev) { prev++; goto next; } } else done = 1; if (start >= 0) { found++; if (done && k < RTNL_LINK_BRIDGE_VLAN_BITMAP_LEN - 1) break; nl_dump(p, " %d", start); if (start != prev) nl_dump(p, "-%d", prev); if (done) break; } if (j > 0) start = prev = j - 1 + base_bit; next: i = j; } } if (!found) nl_dump(p, " "); return; } static void rtnl_link_bridge_dump_vlans(struct nl_dump_params *p, struct bridge_data *bd) { nl_dump(p, "pvid %u", bd->vlan_info.pvid); nl_dump(p, " all vlans:"); dump_bitmap(p, bd->vlan_info.vlan_bitmap); nl_dump(p, " untagged vlans:"); dump_bitmap(p, bd->vlan_info.untagged_bitmap); } static void bridge_dump_details(struct rtnl_link *link, struct nl_dump_params *p, void *data) { struct bridge_data *bd = data; nl_dump_line(p, " bridge: "); if (bd->ce_mask & BRIDGE_ATTR_PORT_STATE) nl_dump(p, "port-state %u ", bd->b_port_state); if (bd->ce_mask & BRIDGE_ATTR_PRIORITY) nl_dump(p, "prio %u ", bd->b_priority); if (bd->ce_mask & BRIDGE_ATTR_COST) nl_dump(p, "cost %u ", bd->b_cost); if (bd->ce_mask & BRIDGE_ATTR_HWMODE) { char hbuf[32]; rtnl_link_bridge_hwmode2str(bd->b_hwmode, hbuf, sizeof(hbuf)); nl_dump(p, "hwmode %s", hbuf); } if (bd->ce_mask & BRIDGE_ATTR_PORT_VLAN) rtnl_link_bridge_dump_vlans(p, bd); if (bd->ce_mask & BRIDGE_ATTR_FLAGS) { char buf[256]; rtnl_link_bridge_flags2str(bd->b_flags & bd->b_flags_mask, buf, sizeof(buf)); nl_dump(p, "%s", buf); } nl_dump(p, "\n"); } static int bridge_compare(struct rtnl_link *_a, struct rtnl_link *_b, int family, uint32_t attrs, int flags) { struct bridge_data *a = bridge_data(_a); struct bridge_data *b = bridge_data(_b); int diff = 0; #define BRIDGE_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, BRIDGE_ATTR_##ATTR, a, b, EXPR) diff |= BRIDGE_DIFF(PORT_STATE, a->b_port_state != b->b_port_state); diff |= BRIDGE_DIFF(PRIORITY, a->b_priority != b->b_priority); diff |= BRIDGE_DIFF(COST, a->b_cost != b->b_cost); diff |= BRIDGE_DIFF(PORT_VLAN, memcmp(&a->vlan_info, &b->vlan_info, sizeof(struct rtnl_link_bridge_vlan))); diff |= BRIDGE_DIFF(HWMODE, a->b_hwmode != b->b_hwmode); diff |= BRIDGE_DIFF(SELF, a->b_self != b->b_self); if (flags & LOOSE_COMPARISON) diff |= BRIDGE_DIFF(FLAGS, (a->b_flags ^ b->b_flags) & b->b_flags_mask); else diff |= BRIDGE_DIFF(FLAGS, a->b_flags != b->b_flags); #undef BRIDGE_DIFF return diff; } /** @endcond */ /** * Allocate link object of type bridge * * @return Allocated link object or NULL. */ struct rtnl_link *rtnl_link_bridge_alloc(void) { struct rtnl_link *link; if (!(link = rtnl_link_alloc())) return NULL; if (rtnl_link_set_type(link, "bridge") < 0) { rtnl_link_put(link); return NULL; } return link; } /** * Create a new kernel bridge device * @arg sk netlink socket * @arg name name of the bridge device or NULL * * Creates a new bridge device in the kernel. If no name is * provided, the kernel will automatically pick a name of the * form "type%d" (e.g. bridge0, vlan1, etc.) * * @return 0 on success or a negative error code */ int rtnl_link_bridge_add(struct nl_sock *sk, const char *name) { int err; struct rtnl_link *link; if (!(link = rtnl_link_bridge_alloc())) return -NLE_NOMEM; if(name) rtnl_link_set_name(link, name); err = rtnl_link_add(sk, link, NLM_F_CREATE); rtnl_link_put(link); return err; } /** * Check if a link is a bridge * @arg link Link object * * @return 1 if the link is a bridge, 0 otherwise. */ int rtnl_link_is_bridge(struct rtnl_link *link) { return link->l_family == AF_BRIDGE && link->l_af_ops == &bridge_ops; } /** * Check if bridge has extended information * @arg link Link object of type bridge * * Checks if the bridge object has been constructed based on * information that is only available in newer kernels. This * affectes the following functions: * - rtnl_link_bridge_get_cost() * - rtnl_link_bridge_get_priority() * - rtnl_link_bridge_get_flags() * * @return 1 if extended information is available, otherwise 0 is returned. */ int rtnl_link_bridge_has_ext_info(struct rtnl_link *link) { struct bridge_data *bd; if (!rtnl_link_is_bridge(link)) return 0; bd = bridge_data(link); return !!(bd->b_priv_flags & PRIV_FLAG_NEW_ATTRS); } /** * Set Spanning Tree Protocol (STP) port state * @arg link Link object of type bridge * @arg state New STP port state * * The value of state must be one of the following: * - BR_STATE_DISABLED * - BR_STATE_LISTENING * - BR_STATE_LEARNING * - BR_STATE_FORWARDING * - BR_STATE_BLOCKING * * @see rtnl_link_bridge_get_port_state() * * @return 0 on success or a negative error code. * @retval -NLE_OPNOTSUPP Link is not a bridge * @retval -NLE_INVAL Invalid state value (0..BR_STATE_BLOCKING) */ int rtnl_link_bridge_set_port_state(struct rtnl_link *link, uint8_t state) { struct bridge_data *bd = bridge_data(link); IS_BRIDGE_LINK_ASSERT(link); if (state > BR_STATE_BLOCKING) return -NLE_INVAL; bd->b_port_state = state; bd->ce_mask |= BRIDGE_ATTR_PORT_STATE; return 0; } /** * Get Spanning Tree Protocol (STP) port state * @arg link Link object of type bridge * * @see rtnl_link_bridge_set_port_state() * * @return The STP port state or a negative error code. * @retval -NLE_OPNOTSUPP Link is not a bridge */ int rtnl_link_bridge_get_port_state(struct rtnl_link *link) { struct bridge_data *bd = bridge_data(link); IS_BRIDGE_LINK_ASSERT(link); return bd->b_port_state; } /** * Set priority * @arg link Link object of type bridge * @arg prio Bridge priority * * @see rtnl_link_bridge_get_priority() * * @return 0 on success or a negative error code. * @retval -NLE_OPNOTSUPP Link is not a bridge */ int rtnl_link_bridge_set_priority(struct rtnl_link *link, uint16_t prio) { struct bridge_data *bd = bridge_data(link); IS_BRIDGE_LINK_ASSERT(link); bd->b_priority = prio; bd->ce_mask |= BRIDGE_ATTR_PRIORITY; return 0; } /** * Get priority * @arg link Link object of type bridge * * @see rtnl_link_bridge_set_priority() * * @return 0 on success or a negative error code. * @retval -NLE_OPNOTSUPP Link is not a bridge */ int rtnl_link_bridge_get_priority(struct rtnl_link *link) { struct bridge_data *bd = bridge_data(link); IS_BRIDGE_LINK_ASSERT(link); return bd->b_priority; } /** * Set Spanning Tree Protocol (STP) path cost * @arg link Link object of type bridge * @arg cost New STP path cost value * * @see rtnl_link_bridge_get_cost() * * @return The bridge priority or a negative error code. * @retval -NLE_OPNOTSUPP Link is not a bridge */ int rtnl_link_bridge_set_cost(struct rtnl_link *link, uint32_t cost) { struct bridge_data *bd = bridge_data(link); IS_BRIDGE_LINK_ASSERT(link); bd->b_cost = cost; bd->ce_mask |= BRIDGE_ATTR_COST; return 0; } /** * Get Spanning Tree Protocol (STP) path cost * @arg link Link object of type bridge * @arg cost Pointer to store STP cost value * * @see rtnl_link_bridge_set_cost() * * @return 0 on success or a negative error code. * @retval -NLE_OPNOTSUPP Link is not a bridge * @retval -NLE_INVAL `cost` is not a valid pointer */ int rtnl_link_bridge_get_cost(struct rtnl_link *link, uint32_t *cost) { struct bridge_data *bd = bridge_data(link); IS_BRIDGE_LINK_ASSERT(link); if (!cost) return -NLE_INVAL; *cost = bd->b_cost; return 0; } /** * Unset flags * @arg link Link object of type bridge * @arg flags Bridging flags to unset * * @see rtnl_link_bridge_set_flags() * @see rtnl_link_bridge_get_flags() * * @return 0 on success or a negative error code. * @retval -NLE_OPNOTSUPP Link is not a bridge */ int rtnl_link_bridge_unset_flags(struct rtnl_link *link, unsigned int flags) { struct bridge_data *bd = bridge_data(link); IS_BRIDGE_LINK_ASSERT(link); bd->b_flags_mask |= flags; bd->b_flags &= ~flags; bd->ce_mask |= BRIDGE_ATTR_FLAGS; return 0; } /** * Set flags * @arg link Link object of type bridge * @arg flags Bridging flags to set * * Valid flags are: * - RTNL_BRIDGE_HAIRPIN_MODE * - RTNL_BRIDGE_BPDU_GUARD * - RTNL_BRIDGE_ROOT_BLOCK * - RTNL_BRIDGE_FAST_LEAVE * - RTNL_BRIDGE_UNICAST_FLOOD * - RTNL_BRIDGE_LEARNING * - RTNL_BRIDGE_LEARNING_SYNC * * @see rtnl_link_bridge_unset_flags() * @see rtnl_link_bridge_get_flags() * * @return 0 on success or a negative error code. * @retval -NLE_OPNOTSUPP Link is not a bridge */ int rtnl_link_bridge_set_flags(struct rtnl_link *link, unsigned int flags) { struct bridge_data *bd = bridge_data(link); IS_BRIDGE_LINK_ASSERT(link); bd->b_flags_mask |= flags; bd->b_flags |= flags; bd->ce_mask |= BRIDGE_ATTR_FLAGS; return 0; } /** * Get flags * @arg link Link object of type bridge * * @see rtnl_link_bridge_set_flags() * @see rtnl_link_bridge_unset_flags() * * @return Flags or a negative error code. * @retval -NLE_OPNOTSUPP Link is not a bridge */ int rtnl_link_bridge_get_flags(struct rtnl_link *link) { struct bridge_data *bd = bridge_data(link); IS_BRIDGE_LINK_ASSERT(link); return bd->b_flags; } /** * Set link change type to self * @arg link Link Object of type bridge * * This will set the bridge change flag to self, meaning that changes to * be applied with this link object will be applied directly to the physical * device in a bridge instead of the virtual device. * * @return 0 on success or negative error code * @return -NLE_OPNOTSUP Link is not a bridge */ int rtnl_link_bridge_set_self(struct rtnl_link *link) { struct bridge_data *bd = bridge_data(link); IS_BRIDGE_LINK_ASSERT(link); bd->b_self |= 1; bd->ce_mask |= BRIDGE_ATTR_SELF; return 0; } /** * Get hardware mode * @arg link Link object of type bridge * @arg hwmode Output argument. * * @see rtnl_link_bridge_set_hwmode() * * @return 0 if hardware mode is present and returned in hwmode * @return -NLE_NOATTR if hardware mode is not present * @return -NLE_OPNOTSUP Link is not a bridge */ int rtnl_link_bridge_get_hwmode(struct rtnl_link *link, uint16_t *hwmode) { struct bridge_data *bd = bridge_data(link); IS_BRIDGE_LINK_ASSERT(link); if (!(bd->ce_mask & BRIDGE_ATTR_HWMODE)) return -NLE_NOATTR; *hwmode = bd->b_hwmode; return 0; } /** * Set hardware mode * @arg link Link object of type bridge * @arg hwmode Hardware mode to set on link * * This will set the hardware mode of a link when it supports hardware * offloads for bridging. * @see rtnl_link_bridge_get_hwmode() * * Valid modes are: * - RTNL_BRIDGE_HWMODE_VEB * - RTNL_BRIDGE_HWMODE_VEPA * * When setting hardware mode, the change type will be set to self. * @see rtnl_link_bridge_set_self() * * @return 0 on success or negative error code * @return -NLE_OPNOTSUP Link is not a bridge * @return -NLE_INVAL when specified hwmode is unsupported. */ int rtnl_link_bridge_set_hwmode(struct rtnl_link *link, uint16_t hwmode) { int err; struct bridge_data *bd = bridge_data(link); if (hwmode > RTNL_BRIDGE_HWMODE_MAX) return -NLE_INVAL; if ((err = rtnl_link_bridge_set_self(link)) < 0) return err; bd->b_hwmode = hwmode; bd->ce_mask |= BRIDGE_ATTR_HWMODE; return 0; } static const struct trans_tbl bridge_flags[] = { __ADD(RTNL_BRIDGE_HAIRPIN_MODE, hairpin_mode), __ADD(RTNL_BRIDGE_BPDU_GUARD, bpdu_guard), __ADD(RTNL_BRIDGE_ROOT_BLOCK, root_block), __ADD(RTNL_BRIDGE_FAST_LEAVE, fast_leave), __ADD(RTNL_BRIDGE_UNICAST_FLOOD, flood), __ADD(RTNL_BRIDGE_LEARNING, learning), __ADD(RTNL_BRIDGE_LEARNING_SYNC, learning_sync), }; /** * @name Flag Translation * @{ */ char *rtnl_link_bridge_flags2str(int flags, char *buf, size_t len) { return __flags2str(flags, buf, len, bridge_flags, ARRAY_SIZE(bridge_flags)); } int rtnl_link_bridge_str2flags(const char *name) { return __str2flags(name, bridge_flags, ARRAY_SIZE(bridge_flags)); } /** @} */ static const struct trans_tbl port_states[] = { __ADD(BR_STATE_DISABLED, disabled), __ADD(BR_STATE_LISTENING, listening), __ADD(BR_STATE_LEARNING, learning), __ADD(BR_STATE_FORWARDING, forwarding), __ADD(BR_STATE_BLOCKING, blocking), }; /** * @name Port State Translation * @{ */ char *rtnl_link_bridge_portstate2str(int st, char *buf, size_t len) { return __type2str(st, buf, len, port_states, ARRAY_SIZE(port_states)); } int rtnl_link_bridge_str2portstate(const char *name) { return __str2type(name, port_states, ARRAY_SIZE(port_states)); } /** @} */ static const struct trans_tbl hw_modes[] = { __ADD(RTNL_BRIDGE_HWMODE_VEB, veb), __ADD(RTNL_BRIDGE_HWMODE_VEPA, vepa), __ADD(RTNL_BRIDGE_HWMODE_UNDEF, undef), }; /** * @name Hardware Mode Translation * @{ */ char *rtnl_link_bridge_hwmode2str(uint16_t st, char *buf, size_t len) { return __type2str(st, buf, len, hw_modes, ARRAY_SIZE(hw_modes)); } uint16_t rtnl_link_bridge_str2hwmode(const char *name) { return __str2type(name, hw_modes, ARRAY_SIZE(hw_modes)); } /** @} */ int rtnl_link_bridge_pvid(struct rtnl_link *link) { struct bridge_data *bd; IS_BRIDGE_LINK_ASSERT(link); bd = link->l_af_data[AF_BRIDGE]; if (bd->ce_mask & BRIDGE_ATTR_PORT_VLAN) return (int) bd->vlan_info.pvid; return -EINVAL; } int rtnl_link_bridge_has_vlan(struct rtnl_link *link) { struct bridge_data *bd; int i; IS_BRIDGE_LINK_ASSERT(link); bd = link->l_af_data[AF_BRIDGE]; if (bd->ce_mask & BRIDGE_ATTR_PORT_VLAN) { if (bd->vlan_info.pvid) return 1; for (i = 0; i < RTNL_LINK_BRIDGE_VLAN_BITMAP_LEN; ++i) { if (bd->vlan_info.vlan_bitmap[i] || bd->vlan_info.untagged_bitmap[i]) return 1; } } return 0; } struct rtnl_link_bridge_vlan *rtnl_link_bridge_get_port_vlan(struct rtnl_link *link) { struct bridge_data *data; if (!rtnl_link_is_bridge(link)) return NULL; data = link->l_af_data[AF_BRIDGE]; if (data && (data->ce_mask & BRIDGE_ATTR_PORT_VLAN)) return &data->vlan_info; return NULL; } static struct rtnl_link_af_ops bridge_ops = { .ao_family = AF_BRIDGE, .ao_alloc = &bridge_alloc, .ao_clone = &bridge_clone, .ao_free = &bridge_free, .ao_parse_protinfo = &bridge_parse_protinfo, .ao_dump[NL_DUMP_DETAILS] = &bridge_dump_details, .ao_compare = &bridge_compare, .ao_parse_af_full = &bridge_parse_af_full, .ao_get_af = &bridge_get_af, .ao_fill_af = &bridge_fill_af, .ao_fill_pi = &bridge_fill_pi, .ao_fill_pi_flags = NLA_F_NESTED, .ao_override_rtm = &bridge_override_rtm, .ao_fill_af_no_nest = 1, }; static void __init bridge_init(void) { rtnl_link_af_register(&bridge_ops); } static void __exit bridge_exit(void) { rtnl_link_af_unregister(&bridge_ops); } /** @} */