summaryrefslogtreecommitdiff
path: root/lib/dpif-netlink-rtnl.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dpif-netlink-rtnl.c')
-rw-r--r--lib/dpif-netlink-rtnl.c273
1 files changed, 273 insertions, 0 deletions
diff --git a/lib/dpif-netlink-rtnl.c b/lib/dpif-netlink-rtnl.c
new file mode 100644
index 000000000..7dfc945e7
--- /dev/null
+++ b/lib/dpif-netlink-rtnl.c
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "dpif-netlink-rtnl.h"
+
+#include <net/if.h>
+#include <linux/ip.h>
+#include <linux/rtnetlink.h>
+
+#include "dpif-netlink.h"
+#include "netdev-vport.h"
+#include "netlink-socket.h"
+#include "openvswitch/vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(dpif_netlink_rtnl);
+
+static const struct nl_policy rtlink_policy[] = {
+ [IFLA_LINKINFO] = { .type = NL_A_NESTED },
+};
+static const struct nl_policy linkinfo_policy[] = {
+ [IFLA_INFO_KIND] = { .type = NL_A_STRING },
+ [IFLA_INFO_DATA] = { .type = NL_A_NESTED },
+};
+
+static const char *
+vport_type_to_kind(enum ovs_vport_type type)
+{
+ switch (type) {
+ case OVS_VPORT_TYPE_VXLAN:
+ return "vxlan";
+ case OVS_VPORT_TYPE_GRE:
+ return "gretap";
+ case OVS_VPORT_TYPE_GENEVE:
+ return "geneve";
+ case OVS_VPORT_TYPE_NETDEV:
+ case OVS_VPORT_TYPE_INTERNAL:
+ case OVS_VPORT_TYPE_LISP:
+ case OVS_VPORT_TYPE_STT:
+ case OVS_VPORT_TYPE_UNSPEC:
+ case __OVS_VPORT_TYPE_MAX:
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static int
+rtnl_transact(uint32_t type, uint32_t flags, const char *name,
+ struct ofpbuf **reply)
+{
+ struct ofpbuf request;
+ int err;
+
+ ofpbuf_init(&request, 0);
+ nl_msg_put_nlmsghdr(&request, 0, type, flags);
+ ofpbuf_put_zeros(&request, sizeof(struct ifinfomsg));
+ nl_msg_put_string(&request, IFLA_IFNAME, name);
+
+ err = nl_transact(NETLINK_ROUTE, &request, reply);
+ ofpbuf_uninit(&request);
+
+ return err;
+}
+
+static int
+dpif_netlink_rtnl_destroy(const char *name)
+{
+ return rtnl_transact(RTM_DELLINK, NLM_F_REQUEST | NLM_F_ACK, name, NULL);
+}
+
+static int OVS_UNUSED
+dpif_netlink_rtnl_getlink(const char *name, struct ofpbuf **reply)
+{
+ return rtnl_transact(RTM_GETLINK, NLM_F_REQUEST, name, reply);
+}
+
+static int OVS_UNUSED
+rtnl_policy_parse(const char *kind, struct ofpbuf *reply,
+ const struct nl_policy *policy,
+ struct nlattr *tnl_info[],
+ size_t policy_size)
+{
+ struct nlattr *linkinfo[ARRAY_SIZE(linkinfo_policy)];
+ struct nlattr *rtlink[ARRAY_SIZE(rtlink_policy)];
+ struct ifinfomsg *ifmsg;
+ int error = 0;
+
+ ifmsg = ofpbuf_at(reply, NLMSG_HDRLEN, sizeof *ifmsg);
+ if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof *ifmsg,
+ rtlink_policy, rtlink, ARRAY_SIZE(rtlink_policy))
+ || !nl_parse_nested(rtlink[IFLA_LINKINFO], linkinfo_policy,
+ linkinfo, ARRAY_SIZE(linkinfo_policy))
+ || strcmp(nl_attr_get_string(linkinfo[IFLA_INFO_KIND]), kind)
+ || !nl_parse_nested(linkinfo[IFLA_INFO_DATA], policy,
+ tnl_info, policy_size)) {
+ error = EINVAL;
+ }
+
+ return error;
+}
+
+static int
+dpif_netlink_rtnl_verify(const struct netdev_tunnel_config OVS_UNUSED *tnl_cfg,
+ enum ovs_vport_type type, const char OVS_UNUSED *name)
+{
+ const char *kind;
+
+ kind = vport_type_to_kind(type);
+ if (!kind) {
+ return EOPNOTSUPP;
+ }
+
+ switch (type) {
+ case OVS_VPORT_TYPE_VXLAN:
+ case OVS_VPORT_TYPE_GRE:
+ case OVS_VPORT_TYPE_GENEVE:
+ case OVS_VPORT_TYPE_NETDEV:
+ case OVS_VPORT_TYPE_INTERNAL:
+ case OVS_VPORT_TYPE_LISP:
+ case OVS_VPORT_TYPE_STT:
+ case OVS_VPORT_TYPE_UNSPEC:
+ case __OVS_VPORT_TYPE_MAX:
+ default:
+ return EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int OVS_UNUSED
+dpif_netlink_rtnl_create(const struct netdev_tunnel_config OVS_UNUSED *tnl_cfg,
+ const char *name, enum ovs_vport_type type,
+ const char *kind, uint32_t flags)
+{
+ size_t linkinfo_off, infodata_off;
+ struct ifinfomsg *ifinfo;
+ struct ofpbuf request;
+ int err;
+
+ ofpbuf_init(&request, 0);
+ nl_msg_put_nlmsghdr(&request, 0, RTM_NEWLINK, flags);
+ ifinfo = ofpbuf_put_zeros(&request, sizeof(struct ifinfomsg));
+ ifinfo->ifi_change = ifinfo->ifi_flags = IFF_UP;
+ nl_msg_put_string(&request, IFLA_IFNAME, name);
+ nl_msg_put_u32(&request, IFLA_MTU, UINT16_MAX);
+ linkinfo_off = nl_msg_start_nested(&request, IFLA_LINKINFO);
+ nl_msg_put_string(&request, IFLA_INFO_KIND, kind);
+ infodata_off = nl_msg_start_nested(&request, IFLA_INFO_DATA);
+
+ /* tunnel unique info */
+ switch (type) {
+ case OVS_VPORT_TYPE_VXLAN:
+ case OVS_VPORT_TYPE_GRE:
+ case OVS_VPORT_TYPE_GENEVE:
+ case OVS_VPORT_TYPE_NETDEV:
+ case OVS_VPORT_TYPE_INTERNAL:
+ case OVS_VPORT_TYPE_LISP:
+ case OVS_VPORT_TYPE_STT:
+ case OVS_VPORT_TYPE_UNSPEC:
+ case __OVS_VPORT_TYPE_MAX:
+ default:
+ err = EOPNOTSUPP;
+ goto exit;
+ }
+
+ nl_msg_end_nested(&request, infodata_off);
+ nl_msg_end_nested(&request, linkinfo_off);
+
+ err = nl_transact(NETLINK_ROUTE, &request, NULL);
+
+exit:
+ ofpbuf_uninit(&request);
+
+ return err;
+}
+
+int
+dpif_netlink_rtnl_port_create(struct netdev *netdev)
+{
+ const struct netdev_tunnel_config *tnl_cfg;
+ char namebuf[NETDEV_VPORT_NAME_BUFSIZE];
+ enum ovs_vport_type type;
+ const char *name;
+ const char *kind;
+ uint32_t flags;
+ int err;
+
+ type = netdev_to_ovs_vport_type(netdev_get_type(netdev));
+ kind = vport_type_to_kind(type);
+ if (!kind) {
+ return EOPNOTSUPP;
+ }
+
+ tnl_cfg = netdev_get_tunnel_config(netdev);
+ if (!tnl_cfg) {
+ return EINVAL;
+ }
+
+ name = netdev_vport_get_dpif_port(netdev, namebuf, sizeof namebuf);
+ flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
+
+ err = dpif_netlink_rtnl_create(tnl_cfg, name, type, kind, flags);
+
+ /* If the device exists, validate and/or attempt to recreate it. */
+ if (err == EEXIST) {
+ err = dpif_netlink_rtnl_verify(tnl_cfg, type, name);
+ if (!err) {
+ return 0;
+ } else {
+ err = dpif_netlink_rtnl_destroy(name);
+ if (err) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+ VLOG_WARN_RL(&rl, "RTNL device %s exists and cannot be "
+ "deleted: %s", name, ovs_strerror(err));
+ return err;
+ }
+ err = dpif_netlink_rtnl_create(tnl_cfg, name, type, kind, flags);
+ }
+ }
+ if (err) {
+ return err;
+ }
+
+ err = dpif_netlink_rtnl_verify(tnl_cfg, type, name);
+ if (err) {
+ int err2 = dpif_netlink_rtnl_destroy(name);
+
+ if (err2) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+ VLOG_WARN_RL(&rl, "Failed to delete device %s during rtnl port "
+ "creation: %s", name, ovs_strerror(err2));
+ }
+ }
+
+ return err;
+}
+
+int
+dpif_netlink_rtnl_port_destroy(const char *name OVS_UNUSED, const char *type)
+{
+ switch (netdev_to_ovs_vport_type(type)) {
+ case OVS_VPORT_TYPE_VXLAN:
+ case OVS_VPORT_TYPE_GRE:
+ case OVS_VPORT_TYPE_GENEVE:
+ case OVS_VPORT_TYPE_NETDEV:
+ case OVS_VPORT_TYPE_INTERNAL:
+ case OVS_VPORT_TYPE_LISP:
+ case OVS_VPORT_TYPE_STT:
+ case OVS_VPORT_TYPE_UNSPEC:
+ case __OVS_VPORT_TYPE_MAX:
+ default:
+ return EOPNOTSUPP;
+ }
+ return 0;
+}