/* SPDX-License-Identifier: LGPL-2.1-only */ /* * Copyright (c) 2013 Cong Wang */ /** * @ingroup link * @defgroup veth VETH * Virtual Ethernet * * @details * \b Link Type Name: "veth" * * @route_doc{link_veth, VETH Documentation} * * @{ */ #include #include #include #include #include #include #include #include #include #include static struct nla_policy veth_policy[VETH_INFO_MAX+1] = { [VETH_INFO_PEER] = { .minlen = sizeof(struct ifinfomsg) }, }; static int veth_parse(struct rtnl_link *link, struct nlattr *data, struct nlattr *xstats) { struct nlattr *tb[VETH_INFO_MAX+1]; struct nlattr *peer_tb[IFLA_MAX + 1]; struct rtnl_link *peer = link->l_info; int err; NL_DBG(3, "Parsing veth link info\n"); if ((err = nla_parse_nested(tb, VETH_INFO_MAX, data, veth_policy)) < 0) goto errout; if (tb[VETH_INFO_PEER]) { struct nlattr *nla_peer; struct ifinfomsg *ifi; nla_peer = tb[VETH_INFO_PEER]; ifi = nla_data(nla_peer); peer->l_family = ifi->ifi_family; peer->l_arptype = ifi->ifi_type; peer->l_index = ifi->ifi_index; peer->l_flags = ifi->ifi_flags; peer->l_change = ifi->ifi_change; err = nla_parse(peer_tb, IFLA_MAX, (struct nlattr *) ((char *) nla_data(nla_peer) + sizeof(struct ifinfomsg)), nla_len(nla_peer) - sizeof(struct ifinfomsg), rtln_link_policy); if (err < 0) goto errout; err = rtnl_link_info_parse(peer, peer_tb); if (err < 0) goto errout; } err = 0; errout: return err; } static void veth_dump_line(struct rtnl_link *link, struct nl_dump_params *p) { } static void veth_dump_details(struct rtnl_link *link, struct nl_dump_params *p) { struct rtnl_link *peer = link->l_info; char *name; name = rtnl_link_get_name(peer); nl_dump(p, " peer "); if (name) nl_dump_line(p, "%s\n", name); else nl_dump_line(p, "%u\n", peer->l_index); } static int veth_clone(struct rtnl_link *dst, struct rtnl_link *src) { struct rtnl_link *dst_peer = NULL, *src_peer = src->l_info; /* we are calling nl_object_clone() recursively, this should * happen only once */ if (src_peer) { src_peer->l_info = NULL; dst_peer = (struct rtnl_link *)nl_object_clone(OBJ_CAST(src_peer)); if (!dst_peer) return -NLE_NOMEM; src_peer->l_info = src; dst_peer->l_info = dst; } dst->l_info = dst_peer; return 0; } static int veth_put_attrs(struct nl_msg *msg, struct rtnl_link *link) { struct rtnl_link *peer = link->l_info; struct ifinfomsg ifi; struct nlattr *data, *info_peer; memset(&ifi, 0, sizeof ifi); ifi.ifi_family = peer->l_family; ifi.ifi_type = peer->l_arptype; ifi.ifi_index = peer->l_index; ifi.ifi_flags = peer->l_flags; ifi.ifi_change = peer->l_change; if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) return -NLE_MSGSIZE; if (!(info_peer = nla_nest_start(msg, VETH_INFO_PEER))) return -NLE_MSGSIZE; if (nlmsg_append(msg, &ifi, sizeof(ifi), NLMSG_ALIGNTO) < 0) return -NLE_MSGSIZE; rtnl_link_fill_info(msg, peer); nla_nest_end(msg, info_peer); nla_nest_end(msg, data); return 0; } static int veth_alloc(struct rtnl_link *link) { struct rtnl_link *peer; int err; /* return early if we are in recursion */ if (link->l_info) return 0; if (!(peer = rtnl_link_alloc())) return -NLE_NOMEM; /* We don't need to hold a reference here, as link and * its peer should always be freed together. */ peer->l_info = link; if ((err = rtnl_link_set_type(peer, "veth")) < 0) { rtnl_link_put(peer); return err; } link->l_info = peer; return 0; } static void veth_free(struct rtnl_link *link) { struct rtnl_link *peer = link->l_info; if (peer) { link->l_info = NULL; /* avoid calling this recursively */ peer->l_info = NULL; rtnl_link_put(peer); } /* the caller should finally free link */ } static struct rtnl_link_info_ops veth_info_ops = { .io_name = "veth", .io_parse = veth_parse, .io_dump = { [NL_DUMP_LINE] = veth_dump_line, [NL_DUMP_DETAILS] = veth_dump_details, }, .io_alloc = veth_alloc, .io_clone = veth_clone, .io_put_attrs = veth_put_attrs, .io_free = veth_free, }; /** @cond SKIP */ #define IS_VETH_LINK_ASSERT(link) \ if ((link)->l_info_ops != &veth_info_ops) { \ APPBUG("Link is not a veth link. set type \"veth\" first."); \ return NULL; \ } /** @endcond */ /** * @name VETH Object * @{ */ /** * Allocate link object of type veth * * @return Allocated link object or NULL. */ struct rtnl_link *rtnl_link_veth_alloc(void) { struct rtnl_link *link; if (!(link = rtnl_link_alloc())) return NULL; if (rtnl_link_set_type(link, "veth") < 0) { rtnl_link_put(link); return NULL; } return link; } /** * Get the peer link of a veth link * * @return the peer link object. */ struct rtnl_link *rtnl_link_veth_get_peer(struct rtnl_link *link) { IS_VETH_LINK_ASSERT(link); nl_object_get(OBJ_CAST(link->l_info)); return link->l_info; } /** * Release a veth link and its peer * */ void rtnl_link_veth_release(struct rtnl_link *link) { veth_free(link); rtnl_link_put(link); } /** * Check if link is a veth link * @arg link Link object * * @return True if link is a veth link, otherwise false is returned. */ int rtnl_link_is_veth(struct rtnl_link *link) { return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "veth"); } /** * Create a new kernel veth device * @arg sock netlink socket * @arg name name of the veth device or NULL * @arg peer_name name of its peer or NULL * @arg pid pid of the process in the new netns * * Creates a new veth device pair in the kernel and move the peer * to the network namespace where the process is. If no name is * provided, the kernel will automatically pick a name of the * form "veth%d" (e.g. veth0, veth1, etc.) * * @return 0 on success or a negative error code */ int rtnl_link_veth_add(struct nl_sock *sock, const char *name, const char *peer_name, pid_t pid) { struct rtnl_link *link, *peer; int err = -NLE_NOMEM; if (!(link = rtnl_link_veth_alloc())) return -NLE_NOMEM; peer = link->l_info; if (name) rtnl_link_set_name(link, name); if (peer_name) rtnl_link_set_name(peer, peer_name); rtnl_link_set_ns_pid(peer, pid); err = rtnl_link_add(sock, link, NLM_F_CREATE | NLM_F_EXCL); rtnl_link_put(link); return err; } /** @} */ static void __init veth_init(void) { rtnl_link_register_info(&veth_info_ops); } static void __exit veth_exit(void) { rtnl_link_unregister_info(&veth_info_ops); } /** @} */