+setup_gre(const char *name, const struct shash *args, bool create)
+{
+ int error;
+ struct in_addr in_addr;
+ struct shash_node *node;
+ struct gre_config config;
+
+ memset(&config, 0, sizeof config);
+ config.in_csum = true;
+ config.out_csum = true;
+
+ SHASH_FOR_EACH (node, args) {
+ if (!strcmp(node->name, "remote_ip")) {
+ if (lookup_ip(node->data, &in_addr)) {
+ VLOG_WARN("bad 'remote_ip' for gre device %s ", name);
+ } else {
+ config.remote_ip = in_addr.s_addr;
+ }
+ } else if (!strcmp(node->name, "local_ip")) {
+ if (lookup_ip(node->data, &in_addr)) {
+ VLOG_WARN("bad 'local_ip' for gre device %s ", name);
+ } else {
+ config.local_ip = in_addr.s_addr;
+ }
+ } else if (!strcmp(node->name, "key")) {
+ config.have_in_key = true;
+ config.have_out_key = true;
+ config.in_key = htonl(atoi(node->data));
+ config.out_key = htonl(atoi(node->data));
+ } else if (!strcmp(node->name, "in_key")) {
+ config.have_in_key = true;
+ config.in_key = htonl(atoi(node->data));
+ } else if (!strcmp(node->name, "out_key")) {
+ config.have_out_key = true;
+ config.out_key = htonl(atoi(node->data));
+ } else if (!strcmp(node->name, "csum")) {
+ if (!strcmp(node->data, "false")) {
+ config.in_csum = false;
+ config.out_csum = false;
+ }
+ } else {
+ VLOG_WARN("unknown gre argument '%s'", node->name);
+ }
+ }
+
+ if (!config.remote_ip) {
+ VLOG_WARN("gre type requires valid 'remote_ip' argument");
+ error = EINVAL;
+ goto error;
+ }
+
+ if (!gre_descriptors.use_ioctl) {
+ error = setup_gre_netlink(name, &config, create);
+ if (error == EOPNOTSUPP) {
+ gre_descriptors.use_ioctl = true;
+ }
+ }
+ if (gre_descriptors.use_ioctl) {
+ error = setup_gre_ioctl(name, &config, create);
+ }
+
+error:
+ return error;
+}
+
+/* Creates the netdev device of 'type' with 'name'. */
+static int
+netdev_linux_create_system(const char *name, const char *type UNUSED,
+ const struct shash *args, struct netdev_dev **netdev_devp)
+{
+ struct netdev_dev_linux *netdev_dev;
+ int error;
+
+ if (!shash_is_empty(args)) {
+ VLOG_WARN("%s: arguments for system devices should be empty", name);
+ }
+
+ if (shash_is_empty(&cache_map)) {
+ error = rtnetlink_notifier_register(&netdev_linux_cache_notifier,
+ netdev_linux_cache_cb, NULL);
+ if (error) {
+ return error;
+ }
+ }
+
+ netdev_dev = xzalloc(sizeof *netdev_dev);
+ netdev_dev->shash_node = shash_add(&cache_map, name, &netdev_dev);
+
+ netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_linux_class);
+ *netdev_devp = &netdev_dev->netdev_dev;
+ return 0;
+}
+
+static int
+netdev_linux_create_tap(const char *name, const char *type UNUSED,
+ const struct shash *args, struct netdev_dev **netdev_devp)
+{
+ struct netdev_dev_linux *netdev_dev;
+ struct tap_state *state;
+ static const char tap_dev[] = "/dev/net/tun";
+ struct ifreq ifr;
+ int error;
+
+ if (!shash_is_empty(args)) {
+ VLOG_WARN("%s: arguments for TAP devices should be empty", name);
+ }
+
+ netdev_dev = xzalloc(sizeof *netdev_dev);
+ state = &netdev_dev->state.tap;
+
+ /* Open tap device. */
+ state->fd = open(tap_dev, O_RDWR);
+ if (state->fd < 0) {
+ error = errno;
+ VLOG_WARN("opening \"%s\" failed: %s", tap_dev, strerror(error));
+ goto error;
+ }
+
+ /* Create tap device. */
+ ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+ strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
+ if (ioctl(state->fd, TUNSETIFF, &ifr) == -1) {
+ VLOG_WARN("%s: creating tap device failed: %s", name,
+ strerror(errno));
+ error = errno;
+ goto error;
+ }
+
+ /* Make non-blocking. */
+ error = set_nonblocking(state->fd);
+ if (error) {
+ goto error;
+ }
+
+ netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_tap_class);
+ *netdev_devp = &netdev_dev->netdev_dev;
+ return 0;
+
+error:
+ free(netdev_dev);
+ return error;
+}
+
+static int
+if_up(const char *name)
+{
+ struct ifreq ifr;
+
+ strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
+ ifr.ifr_flags = IFF_UP;
+
+ if (ioctl(af_inet_sock, SIOCSIFFLAGS, &ifr) == -1) {
+ VLOG_DBG_RL(&rl, "%s: failed to bring device up: %s",
+ name, strerror(errno));
+ return errno;
+ }
+
+ return 0;
+}
+
+static int
+netdev_linux_create_gre(const char *name, const char *type UNUSED,
+ const struct shash *args, struct netdev_dev **netdev_devp)
+{
+ struct netdev_dev_linux *netdev_dev;
+ int error;
+
+ netdev_dev = xzalloc(sizeof *netdev_dev);
+
+ error = setup_gre(name, args, true);
+ if (error) {
+ goto error;
+ }
+
+ error = if_up(name);
+ if (error) {
+ goto error;
+ }
+
+ netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_gre_class);
+ *netdev_devp = &netdev_dev->netdev_dev;
+ return 0;
+
+error:
+ free(netdev_dev);
+ return error;
+}
+
+static int
+netdev_linux_reconfigure_gre(struct netdev_dev *netdev_dev_,
+ const struct shash *args)
+{
+ const char *name = netdev_dev_get_name(netdev_dev_);
+
+ return setup_gre(name, args, false);
+}
+
+/* The arguments are marked as unused to prevent warnings on platforms where
+ * the Netlink interface isn't supported. */
+static int
+destroy_gre_netlink(struct netdev_dev_linux *netdev_dev UNUSED)
+{
+#ifdef GRE_IOCTL_ONLY
+ return EOPNOTSUPP;
+#else
+ const char *name = netdev_dev_get_name(&netdev_dev->netdev_dev);
+ int error;
+ struct ofpbuf request, *reply;
+ struct ifinfomsg ifinfomsg;
+ int ifindex;
+
+ ofpbuf_init(&request, 0);
+
+ nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock, 0, RTM_DELLINK,
+ NLM_F_REQUEST);
+
+ memset(&ifinfomsg, 0, sizeof ifinfomsg);
+ ifinfomsg.ifi_family = AF_UNSPEC;
+ nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg);
+
+ ifindex = do_get_ifindex(name);
+ nl_msg_put_u32(&request, IFLA_LINK, ifindex);
+
+ nl_msg_put_string(&request, IFLA_IFNAME, name);
+
+ error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply);
+ ofpbuf_uninit(&request);
+ if (error) {
+ VLOG_WARN("couldn't transact netlink socket: %s\n", strerror(error));
+ goto error;
+ }
+ ofpbuf_delete(reply);
+
+error:
+ return 0;
+#endif
+}
+
+static int
+destroy_gre_ioctl(struct netdev_dev_linux *netdev_dev)
+{
+ const char *name = netdev_dev_get_name(&netdev_dev->netdev_dev);
+ struct ip_tunnel_parm p;
+ struct ifreq ifr;
+
+ memset(&p, 0, sizeof p);
+ strncpy(p.name, name, IFNAMSIZ);
+
+ strncpy(ifr.ifr_name, name, IFNAMSIZ);
+ ifr.ifr_ifru.ifru_data = (void *)&p;
+
+ if (ioctl(gre_descriptors.ioctl_fd, SIOCDELGRETAP, &ifr) < 0) {
+ VLOG_WARN("couldn't do gre ioctl: %s\n", strerror(errno));
+ return errno;
+ }
+
+ return 0;
+}
+
+static void
+destroy_tap(struct netdev_dev_linux *netdev_dev)
+{
+ struct tap_state *state = &netdev_dev->state.tap;
+
+ if (state->fd >= 0) {
+ close(state->fd);
+ }
+}
+
+/* Destroys the netdev device 'netdev_dev_'. */
+static void
+netdev_linux_destroy(struct netdev_dev *netdev_dev_)
+{
+ struct netdev_dev_linux *netdev_dev = netdev_dev_linux_cast(netdev_dev_);
+ const char *type = netdev_dev_get_type(netdev_dev_);
+
+ if (!strcmp(type, "system")) {
+ shash_delete(&cache_map, netdev_dev->shash_node);
+
+ if (shash_is_empty(&cache_map)) {
+ rtnetlink_notifier_unregister(&netdev_linux_cache_notifier);
+ }
+ } else if (!strcmp(type, "tap")) {
+ destroy_tap(netdev_dev);
+ } else if (!strcmp(type, "gre")) {
+ if (gre_descriptors.use_ioctl) {
+ destroy_gre_ioctl(netdev_dev);
+ } else {
+ destroy_gre_netlink(netdev_dev);
+ }
+ }
+
+ free(netdev_dev_);
+}
+
+static int
+netdev_linux_open(struct netdev_dev *netdev_dev, int ethertype,