diff --git a/lib/automake.mk b/lib/automake.mk
index faace7993e79..f5baba27179f 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -352,6 +352,8 @@ if LINUX
 lib_libopenvswitch_la_SOURCES += \
 	lib/dpif-netlink.c \
 	lib/dpif-netlink.h \
+	lib/dpif-netlink-rtnl.c \
+	lib/dpif-netlink-rtnl.h \
 	lib/if-notifier.c \
 	lib/if-notifier.h \
 	lib/netdev-linux.c \
@@ -382,6 +384,7 @@ if WIN32
 lib_libopenvswitch_la_SOURCES += \
 	lib/dpif-netlink.c \
 	lib/dpif-netlink.h \
+	lib/dpif-netlink-rtnl.h \
 	lib/netdev-windows.c \
 	lib/netlink-conntrack.c \
 	lib/netlink-conntrack.h \
diff --git a/lib/dpif-netlink-rtnl.c b/lib/dpif-netlink-rtnl.c
new file mode 100644
index 000000000000..906e05145190
--- /dev/null
+++ b/lib/dpif-netlink-rtnl.c
@@ -0,0 +1,227 @@
+/*
+ * 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"
+
+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 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)
+{
+    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;
+    bool retried = false;
+    const char *name;
+    uint32_t flags;
+    int err;
+
+    tnl_cfg = netdev_get_tunnel_config(netdev);
+    if (!tnl_cfg) {
+        return EINVAL;
+    }
+
+    type = netdev_to_ovs_vport_type(netdev_get_type(netdev));
+    name = netdev_vport_get_dpif_port(netdev, namebuf, sizeof namebuf);
+    flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
+
+try_again:
+    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;
+    }
+
+    if (!err || (err == EEXIST && !retried)) {
+        int err2 = dpif_netlink_rtnl_verify(tnl_cfg, type, name);
+        if (err2 && err == EEXIST) {
+            err2 = dpif_netlink_rtnl_destroy(name);
+            if (!err2) {
+                retried = true;
+                goto try_again;
+            }
+        }
+        err = 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;
+}
diff --git a/lib/dpif-netlink-rtnl.h b/lib/dpif-netlink-rtnl.h
new file mode 100644
index 000000000000..5fef314a20f6
--- /dev/null
+++ b/lib/dpif-netlink-rtnl.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#ifndef DPIF_NETLINK_RTNL_H
+#define DPIF_NETLINK_RTNL_H 1
+
+#include <errno.h>
+
+#include "netdev.h"
+
+/* Declare these to keep sparse happy. */
+int dpif_netlink_rtnl_port_create(struct netdev *netdev);
+int dpif_netlink_rtnl_port_destroy(const char *name OVS_UNUSED,
+                                   const char *type);
+
+#ifndef __linux__
+/* Dummy implementations for non Linux builds. */
+
+static inline int
+dpif_netlink_rtnl_port_create(struct netdev *netdev OVS_UNUSED)
+{
+    return EOPNOTSUPP;
+}
+
+static inline int
+dpif_netlink_rtnl_port_destroy(const char *name OVS_UNUSED,
+                               const char *type OVS_UNUSED)
+{
+    return EOPNOTSUPP;
+}
+
+#endif
+
+#endif /* DPIF_NETLINK_RTNL_H */
diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index 4c4106c9b33f..52d28fb39e7b 100644
--- a/lib/dpif-netlink.c
+++ b/lib/dpif-netlink.c
@@ -34,6 +34,7 @@
 
 #include "bitmap.h"
 #include "dpif-provider.h"
+#include "dpif-netlink-rtnl.h"
 #include "openvswitch/dynamic-string.h"
 #include "flow.h"
 #include "fat-rwlock.h"
@@ -784,7 +785,7 @@ get_vport_type(const struct dpif_netlink_vport *vport)
     return "unknown";
 }
 
-static enum ovs_vport_type
+enum ovs_vport_type
 netdev_to_ovs_vport_type(const char *type)
 {
     if (!strcmp(type, "tap") || !strcmp(type, "system")) {
@@ -947,8 +948,34 @@ dpif_netlink_port_add_compat(struct dpif_netlink *dpif, struct netdev *netdev,
 
 }
 
+static int OVS_UNUSED
+dpif_netlink_rtnl_port_create_and_add(struct dpif_netlink *dpif,
+                                      struct netdev *netdev,
+                                      odp_port_t *port_nop)
+    OVS_REQ_WRLOCK(dpif->upcall_lock)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
+    char namebuf[NETDEV_VPORT_NAME_BUFSIZE];
+    const char *name;
+    int error;
 
+    error = dpif_netlink_rtnl_port_create(netdev);
+    if (error) {
+        if (error != EOPNOTSUPP) {
+            VLOG_INFO_RL(&rl, "Failed to create %s with rtnetlink: %s",
+                         netdev_get_name(netdev), ovs_strerror(error));
+        }
+        return error;
+    }
 
+    name = netdev_vport_get_dpif_port(netdev, namebuf, sizeof namebuf);
+    error = dpif_netlink_port_add__(dpif, name, OVS_VPORT_TYPE_NETDEV, NULL,
+                                    port_nop);
+    if (error) {
+        dpif_netlink_rtnl_port_destroy(name, netdev_get_type(netdev));
+    }
+    return error;
+}
 
 static int
 dpif_netlink_port_add(struct dpif *dpif_, struct netdev *netdev,
diff --git a/lib/dpif-netlink.h b/lib/dpif-netlink.h
index 6d1505713a72..568b81441ddc 100644
--- a/lib/dpif-netlink.h
+++ b/lib/dpif-netlink.h
@@ -58,4 +58,6 @@ int dpif_netlink_vport_get(const char *name, struct dpif_netlink_vport *reply,
 
 bool dpif_netlink_is_internal_device(const char *name);
 
+enum ovs_vport_type netdev_to_ovs_vport_type(const char *type);
+
 #endif /* dpif-netlink.h */
