/* SPDX-License-Identifier: LGPL-2.1-only */ /* * Copyright (c) 2013 Michael Braun */ /** * @ingroup link * @defgroup macvlan MACVLAN/MACVTAP * MAC-based Virtual LAN link module * * @details * \b Link Type Name: "macvlan" * * @route_doc{link_macvlan, MACVLAN Documentation} * @route_doc{link_macvtap, MACVTAP Documentation} * * @{ */ #include #include #include #include #include #include #include #include #include #include /** @cond SKIP */ #define MACVLAN_HAS_MODE (1<<0) #define MACVLAN_HAS_FLAGS (1<<1) #define MACVLAN_HAS_MACADDR (1<<2) struct macvlan_info { uint32_t mvi_mode; uint16_t mvi_flags; // there currently is only one flag and kernel has no flags_mask yet uint32_t mvi_mask; uint32_t mvi_maccount; uint32_t mvi_macmode; struct nl_addr **mvi_macaddr; }; /** @endcond */ static struct nla_policy macvlan_policy[IFLA_MACVLAN_MAX+1] = { [IFLA_MACVLAN_MODE] = { .type = NLA_U32 }, [IFLA_MACVLAN_FLAGS] = { .type = NLA_U16 }, [IFLA_MACVLAN_MACADDR_MODE] = { .type = NLA_U32 }, [IFLA_MACVLAN_MACADDR] = { .type = NLA_UNSPEC }, [IFLA_MACVLAN_MACADDR_DATA] = { .type = NLA_NESTED }, [IFLA_MACVLAN_MACADDR_COUNT] = { .type = NLA_U32 }, }; static int macvlan_alloc(struct rtnl_link *link) { struct macvlan_info *mvi; uint32_t i; if (link->l_info) { mvi = link->l_info; for (i = 0; i < mvi->mvi_maccount; i++) nl_addr_put(mvi->mvi_macaddr[i]); free(mvi->mvi_macaddr); memset(mvi, 0, sizeof(*mvi)); } else { if ((mvi = calloc(1, sizeof(*mvi))) == NULL) return -NLE_NOMEM; link->l_info = mvi; } mvi->mvi_macmode = MACVLAN_MACADDR_SET; return 0; } static int macvlan_parse(struct rtnl_link *link, struct nlattr *data, struct nlattr *xstats) { struct nlattr *tb[IFLA_MACVLAN_MAX+1]; struct macvlan_info *mvi; struct nlattr *nla; int len; int err; NL_DBG(3, "Parsing %s link info", link->l_info_ops->io_name); if ((err = nla_parse_nested(tb, IFLA_MACVLAN_MAX, data, macvlan_policy)) < 0) goto errout; if ((err = macvlan_alloc(link)) < 0) goto errout; mvi = link->l_info; if (tb[IFLA_MACVLAN_MODE]) { mvi->mvi_mode = nla_get_u32(tb[IFLA_MACVLAN_MODE]); mvi->mvi_mask |= MACVLAN_HAS_MODE; } if (tb[IFLA_MACVLAN_FLAGS]) { mvi->mvi_flags = nla_get_u16(tb[IFLA_MACVLAN_FLAGS]); mvi->mvi_mask |= MACVLAN_HAS_FLAGS; } if ( tb[IFLA_MACVLAN_MACADDR_COUNT] && tb[IFLA_MACVLAN_MACADDR_DATA]) { mvi->mvi_maccount = nla_get_u32(tb[IFLA_MACVLAN_MACADDR_COUNT]); if (mvi->mvi_maccount > 0) { uint32_t i; nla = nla_data(tb[IFLA_MACVLAN_MACADDR_DATA]); len = nla_len(tb[IFLA_MACVLAN_MACADDR_DATA]); mvi->mvi_macaddr = calloc(mvi->mvi_maccount, sizeof(*(mvi->mvi_macaddr))); if (mvi->mvi_macaddr == NULL) { err = -NLE_NOMEM; goto errout; } i = 0; for (; nla_ok(nla, len); nla = nla_next(nla, &len)) { if (i >= mvi->mvi_maccount) break; if (nla_type(nla) != IFLA_MACVLAN_MACADDR || nla_len(nla) < ETH_ALEN) continue; mvi->mvi_macaddr[i] = nl_addr_alloc_attr(nla, AF_LLC); i++; } } mvi->mvi_mask |= MACVLAN_HAS_MACADDR; } err = 0; errout: return err; } static void macvlan_free(struct rtnl_link *link) { struct macvlan_info *mvi; uint32_t i; mvi = link->l_info; if (!mvi) return; for (i = 0; i < mvi->mvi_maccount; i++) nl_addr_put(mvi->mvi_macaddr[i]); free(mvi->mvi_macaddr); free(mvi); link->l_info = NULL; } static void macvlan_dump_details(struct rtnl_link *link, struct nl_dump_params *p) { char buf[64]; uint32_t i; struct macvlan_info *mvi = link->l_info; if (mvi->mvi_mask & MACVLAN_HAS_MODE) { rtnl_link_macvlan_mode2str(mvi->mvi_mode, buf, sizeof(buf)); nl_dump(p, " %s-mode %s", link->l_info_ops->io_name, buf); } if (mvi->mvi_mask & MACVLAN_HAS_FLAGS) { rtnl_link_macvlan_flags2str(mvi->mvi_flags, buf, sizeof(buf)); nl_dump(p, " %s-flags %s", link->l_info_ops->io_name, buf); } if (mvi->mvi_mask & MACVLAN_HAS_MACADDR) { nl_dump(p, " macvlan-count %u", (unsigned) mvi->mvi_maccount); if (mvi->mvi_maccount) nl_dump(p, " macvlan-sourcemac"); for (i = 0; i < mvi->mvi_maccount; i++) { nl_dump(p, " %s", nl_addr2str(mvi->mvi_macaddr[i], buf, sizeof(buf))); } } nl_dump(p, "\n"); } static int macvlan_clone(struct rtnl_link *dst, struct rtnl_link *src) { struct macvlan_info *vdst, *vsrc = src->l_info; int err; uint32_t i; dst->l_info = NULL; if ((err = rtnl_link_set_type(dst, "macvlan")) < 0) return err; vdst = dst->l_info; if (!vdst || !vsrc) return -NLE_NOMEM; memcpy(vdst, vsrc, sizeof(struct macvlan_info)); if ( vsrc->mvi_mask & MACVLAN_HAS_MACADDR && vsrc->mvi_maccount > 0) { vdst->mvi_macaddr = calloc(vdst->mvi_maccount, sizeof(*(vdst->mvi_macaddr))); for (i = 0; i < vdst->mvi_maccount; i++) vdst->mvi_macaddr[i] = nl_addr_clone(vsrc->mvi_macaddr[i]); } else vdst->mvi_macaddr = NULL; return 0; } static int macvlan_put_attrs(struct nl_msg *msg, struct rtnl_link *link) { struct macvlan_info *mvi = link->l_info; struct nlattr *data, *datamac = NULL; int i, ret; if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) return -NLE_MSGSIZE; ret = -NLE_NOMEM; if (mvi->mvi_mask & MACVLAN_HAS_MODE) NLA_PUT_U32(msg, IFLA_MACVLAN_MODE, mvi->mvi_mode); if (mvi->mvi_mask & MACVLAN_HAS_FLAGS) NLA_PUT_U16(msg, IFLA_MACVLAN_FLAGS, mvi->mvi_flags); if (mvi->mvi_mask & MACVLAN_HAS_MACADDR) { NLA_PUT_U32(msg, IFLA_MACVLAN_MACADDR_MODE, mvi->mvi_macmode); datamac = nla_nest_start(msg, IFLA_MACVLAN_MACADDR_DATA); if (!datamac) goto nla_put_failure; for (i = 0; i < mvi->mvi_maccount; i++) { NLA_PUT_ADDR(msg, IFLA_MACVLAN_MACADDR, mvi->mvi_macaddr[i]); } } ret = 0; nla_put_failure: if (datamac) nla_nest_end(msg, datamac); nla_nest_end(msg, data); return ret; } static struct rtnl_link_info_ops macvlan_info_ops = { .io_name = "macvlan", .io_alloc = macvlan_alloc, .io_parse = macvlan_parse, .io_dump = { [NL_DUMP_DETAILS] = macvlan_dump_details, }, .io_clone = macvlan_clone, .io_put_attrs = macvlan_put_attrs, .io_free = macvlan_free, }; static struct rtnl_link_info_ops macvtap_info_ops = { .io_name = "macvtap", .io_alloc = macvlan_alloc, .io_parse = macvlan_parse, .io_dump = { [NL_DUMP_DETAILS] = macvlan_dump_details, }, .io_clone = macvlan_clone, .io_put_attrs = macvlan_put_attrs, .io_free = macvlan_free, }; /** @cond SKIP */ #define IS_MACVLAN_LINK_ASSERT(link) \ if ((link)->l_info_ops != &macvlan_info_ops) { \ APPBUG("Link is not a macvlan link. set type \"macvlan\" first."); \ return -NLE_OPNOTSUPP; \ } #define IS_MACVTAP_LINK_ASSERT(link) \ if ((link)->l_info_ops != &macvtap_info_ops) { \ APPBUG("Link is not a macvtap link. set type \"macvtap\" first."); \ return -NLE_OPNOTSUPP; \ } /** @endcond */ /** * @name MACVLAN Object * @{ */ /** * Allocate link object of type MACVLAN * * @return Allocated link object or NULL. */ struct rtnl_link *rtnl_link_macvlan_alloc(void) { struct rtnl_link *link; if (!(link = rtnl_link_alloc())) return NULL; if (rtnl_link_set_type(link, "macvlan") < 0) { rtnl_link_put(link); return NULL; } return link; } /** * Check if link is a MACVLAN link * @arg link Link object * * @return True if link is a MACVLAN link, otherwise false is returned. */ int rtnl_link_is_macvlan(struct rtnl_link *link) { return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "macvlan"); } /** * Set MACVLAN MODE * @arg link Link object * @arg mode MACVLAN mode * * @return 0 on success or a negative error code */ int rtnl_link_macvlan_set_mode(struct rtnl_link *link, uint32_t mode) { struct macvlan_info *mvi = link->l_info; int i; IS_MACVLAN_LINK_ASSERT(link); mvi->mvi_mode = mode; mvi->mvi_mask |= MACVLAN_HAS_MODE; if (mode != MACVLAN_MODE_SOURCE) { for (i = 0; i < mvi->mvi_maccount; i++) nl_addr_put(mvi->mvi_macaddr[i]); free(mvi->mvi_macaddr); mvi->mvi_maccount = 0; mvi->mvi_macaddr = NULL; mvi->mvi_macmode = MACVLAN_MACADDR_SET; mvi->mvi_mask &= ~MACVLAN_HAS_MACADDR; } return 0; } /** * Get MACVLAN Mode * @arg link Link object * * @return MACVLAN mode, 0 if not set or a negative error code. */ uint32_t rtnl_link_macvlan_get_mode(struct rtnl_link *link) { struct macvlan_info *mvi = link->l_info; IS_MACVLAN_LINK_ASSERT(link); if (mvi->mvi_mask & MACVLAN_HAS_MODE) return mvi->mvi_mode; else return 0; } /** * Set MACVLAN MACMODE * @arg link Link object * @arg mode MACVLAN mac list modification mode * * Only for macvlan SOURCE mode. * * @return 0 on success or a negative error code */ int rtnl_link_macvlan_set_macmode(struct rtnl_link *link, uint32_t macmode) { struct macvlan_info *mvi = link->l_info; IS_MACVLAN_LINK_ASSERT(link); if (!(mvi->mvi_mask & MACVLAN_HAS_MODE) || (mvi->mvi_mode != MACVLAN_MODE_SOURCE)) return -NLE_INVAL; mvi->mvi_macmode = macmode; mvi->mvi_mask |= MACVLAN_HAS_MACADDR; return 0; } /** * Get MACVLAN MACMODE * @arg link Link object * @arg out_macmode mac list modification mode * * Only for SOURCE mode. * * @return 0 on success or a negative error code. */ int rtnl_link_macvlan_get_macmode(struct rtnl_link *link, uint32_t *out_macmode) { struct macvlan_info *mvi = link->l_info; IS_MACVLAN_LINK_ASSERT(link); if (!(mvi->mvi_mask & MACVLAN_HAS_MODE) || (mvi->mvi_mode != MACVLAN_MODE_SOURCE)) return -NLE_INVAL; if (!(mvi->mvi_mask & MACVLAN_HAS_MACADDR)) return -NLE_INVAL; *out_macmode = mvi->mvi_macmode; return 0; } /** * Set MACVLAN flags * @arg link Link object * @arg flags MACVLAN flags * * @return 0 on success or a negative error code. */ int rtnl_link_macvlan_set_flags(struct rtnl_link *link, uint16_t flags) { struct macvlan_info *mvi = link->l_info; IS_MACVLAN_LINK_ASSERT(link); mvi->mvi_flags |= flags; mvi->mvi_mask |= MACVLAN_HAS_FLAGS; return 0; } /** * Unset MACVLAN flags * @arg link Link object * @arg flags MACVLAN flags * * Note: kernel currently only has a single flag and lacks flags_mask to * indicate which flags shall be changed (it always all). * * @return 0 on success or a negative error code. */ int rtnl_link_macvlan_unset_flags(struct rtnl_link *link, uint16_t flags) { struct macvlan_info *mvi = link->l_info; IS_MACVLAN_LINK_ASSERT(link); mvi->mvi_flags &= ~flags; mvi->mvi_mask |= MACVLAN_HAS_FLAGS; return 0; } /** * Get MACVLAN flags * @arg link Link object * * @return MACVLAN flags, 0 if none set, or a negative error code. */ uint16_t rtnl_link_macvlan_get_flags(struct rtnl_link *link) { struct macvlan_info *mvi = link->l_info; IS_MACVLAN_LINK_ASSERT(link); return mvi->mvi_flags; } /** * Get number of MAC-Addr for MACVLAN device in source mode * @arg link Link object * @arg out_count number of mac addresses * * @return 0 on success or a negative error code. */ int rtnl_link_macvlan_count_macaddr(struct rtnl_link *link, uint32_t *out_count) { struct macvlan_info *mvi = link->l_info; IS_MACVLAN_LINK_ASSERT(link); if (!(mvi->mvi_mask & MACVLAN_HAS_MODE) || (mvi->mvi_mode != MACVLAN_MODE_SOURCE)) return -NLE_INVAL; if (!(mvi->mvi_mask & MACVLAN_HAS_MACADDR)) return -NLE_INVAL; *out_count = mvi->mvi_maccount; return 0; } /** * Get configured remote MAC-Addr from MACVLAN device in source mode * @arg link Link object * @arg out_addr address object * * The returned nl_addr struct needs NOT to be released using nl_addr_put. * It is only valid until the address is not removed from this link object * or its mode is changed to non-source. * * @return 0 on success or negative error code */ int rtnl_link_macvlan_get_macaddr(struct rtnl_link *link, uint32_t idx, const struct nl_addr **out_addr) { struct macvlan_info *mvi = link->l_info; IS_MACVLAN_LINK_ASSERT(link); if (!(mvi->mvi_mask & MACVLAN_HAS_MODE) || (mvi->mvi_mode != MACVLAN_MODE_SOURCE)) return -NLE_INVAL; if (!(mvi->mvi_mask & MACVLAN_HAS_MACADDR)) return -NLE_INVAL; if (idx >= mvi->mvi_maccount) return -NLE_INVAL; *out_addr = mvi->mvi_macaddr[idx]; return 0; } /** * Add MAC-Addr to MACVLAN device in source mode * @arg link Link object * @arg addr MAC-Addr * * addr is not release but cloned by this method. * * @return 0 on success or a negative error code. */ int rtnl_link_macvlan_add_macaddr(struct rtnl_link *link, struct nl_addr *addr) { struct macvlan_info *mvi = link->l_info; struct nl_addr **mvi_macaddr; size_t newsize; IS_MACVLAN_LINK_ASSERT(link); if (nl_addr_get_family(addr) != AF_LLC) return -NLE_INVAL; if (!(mvi->mvi_mask & MACVLAN_HAS_MODE) || (mvi->mvi_mode != MACVLAN_MODE_SOURCE)) return -NLE_INVAL; if (!(mvi->mvi_mask & MACVLAN_HAS_MACADDR)) return -NLE_INVAL; if (mvi->mvi_maccount >= UINT32_MAX) return -NLE_INVAL; newsize = (mvi->mvi_maccount + 1) * sizeof(*(mvi->mvi_macaddr)); mvi_macaddr = realloc(mvi->mvi_macaddr, newsize); if (!mvi_macaddr) return -NLE_NOMEM; mvi->mvi_macaddr = mvi_macaddr; mvi->mvi_macaddr[mvi->mvi_maccount] = nl_addr_clone(addr); mvi->mvi_maccount++; mvi->mvi_mask |= MACVLAN_HAS_MACADDR; return 0; } /** * Remove MAC-Addr from MACVLAN device in source mode * @arg link Link object * @arg addr MAC-Addr * * addr is not release by this method. * * @return a negative error code on failure, or the number * of deleted addresses on success. */ int rtnl_link_macvlan_del_macaddr(struct rtnl_link *link, struct nl_addr *addr) { struct macvlan_info *mvi = link->l_info; uint32_t found, i; IS_MACVLAN_LINK_ASSERT(link); if (nl_addr_get_family(addr) != AF_LLC) return -NLE_INVAL; if (!(mvi->mvi_mask & MACVLAN_HAS_MODE) || (mvi->mvi_mode != MACVLAN_MODE_SOURCE)) return -NLE_INVAL; if (!(mvi->mvi_mask & MACVLAN_HAS_MACADDR)) return -NLE_INVAL; nl_addr_get(addr); found = 0; i = 0; while (i + found < mvi->mvi_maccount) { mvi->mvi_macaddr[i] = mvi->mvi_macaddr[i + found]; if (found > 0) mvi->mvi_macaddr[i + found] = NULL; if (nl_addr_cmp(addr, mvi->mvi_macaddr[i]) == 0) { nl_addr_put(mvi->mvi_macaddr[i]); mvi->mvi_macaddr[i] = NULL; found++; } else i++; } nl_addr_put(addr); mvi->mvi_maccount -= found; return found > INT_MAX ? INT_MAX : (int) found; } /** @} */ /** * @name MACVTAP Object * @{ */ /** * Allocate link object of type MACVTAP * * @return Allocated link object or NULL. */ struct rtnl_link *rtnl_link_macvtap_alloc(void) { struct rtnl_link *link; if (!(link = rtnl_link_alloc())) return NULL; if (rtnl_link_set_type(link, "macvtap") < 0) { rtnl_link_put(link); return NULL; } return link; } /** * Check if link is a MACVTAP link * @arg link Link object * * @return True if link is a MACVTAP link, otherwise false is returned. */ int rtnl_link_is_macvtap(struct rtnl_link *link) { return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "macvtap"); } /** * Set MACVTAP MODE * @arg link Link object * @arg mode MACVTAP mode * * @return 0 on success or a negative error code */ int rtnl_link_macvtap_set_mode(struct rtnl_link *link, uint32_t mode) { struct macvlan_info *mvi = link->l_info; IS_MACVTAP_LINK_ASSERT(link); mvi->mvi_mode = mode; mvi->mvi_mask |= MACVLAN_HAS_MODE; return 0; } /** * Get MACVTAP Mode * @arg link Link object * * @return MACVTAP mode, 0 if not set or a negative error code. */ uint32_t rtnl_link_macvtap_get_mode(struct rtnl_link *link) { struct macvlan_info *mvi = link->l_info; IS_MACVTAP_LINK_ASSERT(link); if (mvi->mvi_mask & MACVLAN_HAS_MODE) return mvi->mvi_mode; else return 0; } /** * Set MACVTAP flags * @arg link Link object * @arg flags MACVTAP flags * * @return 0 on success or a negative error code. */ int rtnl_link_macvtap_set_flags(struct rtnl_link *link, uint16_t flags) { struct macvlan_info *mvi = link->l_info; IS_MACVTAP_LINK_ASSERT(link); mvi->mvi_flags |= flags; mvi->mvi_mask |= MACVLAN_HAS_FLAGS; return 0; } /** * Unset MACVTAP flags * @arg link Link object * @arg flags MACVTAP flags * * Note: kernel currently only has a single flag and lacks flags_mask to * indicate which flags shall be changed (it always all). * * @return 0 on success or a negative error code. */ int rtnl_link_macvtap_unset_flags(struct rtnl_link *link, uint16_t flags) { struct macvlan_info *mvi = link->l_info; IS_MACVTAP_LINK_ASSERT(link); mvi->mvi_flags &= ~flags; mvi->mvi_mask |= MACVLAN_HAS_FLAGS; return 0; } /** * Get MACVTAP flags * @arg link Link object * * @return MACVTAP flags, 0 if none set, or a negative error code. */ uint16_t rtnl_link_macvtap_get_flags(struct rtnl_link *link) { struct macvlan_info *mvi = link->l_info; IS_MACVTAP_LINK_ASSERT(link); return mvi->mvi_flags; } /** @} */ static const struct trans_tbl macvlan_flags[] = { __ADD(MACVLAN_FLAG_NOPROMISC, nopromisc), }; static const struct trans_tbl macvlan_modes[] = { __ADD(MACVLAN_MODE_PRIVATE, private), __ADD(MACVLAN_MODE_VEPA, vepa), __ADD(MACVLAN_MODE_BRIDGE, bridge), __ADD(MACVLAN_MODE_PASSTHRU, passthru), __ADD(MACVLAN_MODE_SOURCE, source), }; static const struct trans_tbl macvlan_macmodes[] = { __ADD(MACVLAN_MACADDR_ADD, "add"), __ADD(MACVLAN_MACADDR_DEL, "del"), __ADD(MACVLAN_MACADDR_SET, "set"), __ADD(MACVLAN_MACADDR_FLUSH, "flush"), }; /** * @name Flag Translation * @{ */ char *rtnl_link_macvlan_flags2str(int flags, char *buf, size_t len) { return __flags2str(flags, buf, len, macvlan_flags, ARRAY_SIZE(macvlan_flags)); } int rtnl_link_macvlan_str2flags(const char *name) { return __str2flags(name, macvlan_flags, ARRAY_SIZE(macvlan_flags)); } char *rtnl_link_macvtap_flags2str(int flags, char *buf, size_t len) { return __flags2str(flags, buf, len, macvlan_flags, ARRAY_SIZE(macvlan_flags)); } int rtnl_link_macvtap_str2flags(const char *name) { return __str2flags(name, macvlan_flags, ARRAY_SIZE(macvlan_flags)); } /** @} */ /** * @name Mode Translation * @{ */ char *rtnl_link_macvlan_mode2str(int mode, char *buf, size_t len) { return __type2str(mode, buf, len, macvlan_modes, ARRAY_SIZE(macvlan_modes)); } int rtnl_link_macvlan_str2mode(const char *name) { return __str2type(name, macvlan_modes, ARRAY_SIZE(macvlan_modes)); } char *rtnl_link_macvlan_macmode2str(int mode, char *buf, size_t len) { return __type2str(mode, buf, len, macvlan_macmodes, ARRAY_SIZE(macvlan_macmodes)); } int rtnl_link_macvlan_str2macmode(const char *name) { return __str2type(name, macvlan_macmodes, ARRAY_SIZE(macvlan_macmodes)); } char *rtnl_link_macvtap_mode2str(int mode, char *buf, size_t len) { return __type2str(mode, buf, len, macvlan_modes, ARRAY_SIZE(macvlan_modes)); } int rtnl_link_macvtap_str2mode(const char *name) { return __str2type(name, macvlan_modes, ARRAY_SIZE(macvlan_modes)); } /** @} */ static void __init macvlan_init(void) { rtnl_link_register_info(&macvlan_info_ops); rtnl_link_register_info(&macvtap_info_ops); } static void __exit macvlan_exit(void) { rtnl_link_unregister_info(&macvlan_info_ops); rtnl_link_unregister_info(&macvtap_info_ops); } /** @} */