+static void
+pkt_list_delete(struct ovs_list *l)
+{
+ struct pkt_list_node *pkt;
+
+ LIST_FOR_EACH_POP(pkt, list_node, l) {
+ dp_packet_delete(pkt->pkt);
+ free(pkt);
+ }
+}
+
+static struct dp_packet *
+eth_from_packet_or_flow(const char *s)
+{
+ enum odp_key_fitness fitness;
+ struct dp_packet *packet;
+ struct ofpbuf odp_key;
+ struct flow flow;
+ int error;
+
+ if (!eth_from_hex(s, &packet)) {
+ return packet;
+ }
+
+ /* Convert string to datapath key.
+ *
+ * It would actually be nicer to parse an OpenFlow-like flow key here, but
+ * the code for that currently calls exit() on parse error. We have to
+ * settle for parsing a datapath key for now.
+ */
+ ofpbuf_init(&odp_key, 0);
+ error = odp_flow_from_string(s, NULL, &odp_key, NULL);
+ if (error) {
+ ofpbuf_uninit(&odp_key);
+ return NULL;
+ }
+
+ /* Convert odp_key to flow. */
+ fitness = odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow);
+ if (fitness == ODP_FIT_ERROR) {
+ ofpbuf_uninit(&odp_key);
+ return NULL;
+ }
+
+ packet = dp_packet_new(0);
+ flow_compose(packet, &flow);
+
+ ofpbuf_uninit(&odp_key);
+ return packet;
+}
+
+static void
+netdev_dummy_queue_packet__(struct netdev_rxq_dummy *rx, struct dp_packet *packet)
+{
+ struct pkt_list_node *pkt_node = xmalloc(sizeof *pkt_node);
+
+ pkt_node->pkt = packet;
+ list_push_back(&rx->recv_queue, &pkt_node->list_node);
+ rx->recv_queue_len++;
+ seq_change(rx->seq);
+}
+
+static void
+netdev_dummy_queue_packet(struct netdev_dummy *dummy, struct dp_packet *packet)
+ OVS_REQUIRES(dummy->mutex)
+{
+ struct netdev_rxq_dummy *rx, *prev;
+
+ if (dummy->rxq_pcap) {
+ ovs_pcap_write(dummy->rxq_pcap, packet);
+ fflush(dummy->rxq_pcap);
+ }
+ prev = NULL;
+ LIST_FOR_EACH (rx, node, &dummy->rxes) {
+ if (rx->recv_queue_len < NETDEV_DUMMY_MAX_QUEUE) {
+ if (prev) {
+ netdev_dummy_queue_packet__(prev, dp_packet_clone(packet));
+ }
+ prev = rx;
+ }
+ }
+ if (prev) {
+ netdev_dummy_queue_packet__(prev, packet);
+ } else {
+ dp_packet_delete(packet);
+ }
+}
+
+static void
+netdev_dummy_receive(struct unixctl_conn *conn,
+ int argc, const char *argv[], void *aux OVS_UNUSED)
+{
+ struct netdev_dummy *dummy_dev;
+ struct netdev *netdev;
+ int i;
+
+ netdev = netdev_from_name(argv[1]);
+ if (!netdev || !is_dummy_class(netdev->netdev_class)) {
+ unixctl_command_reply_error(conn, "no such dummy netdev");
+ goto exit;
+ }
+ dummy_dev = netdev_dummy_cast(netdev);
+
+ for (i = 2; i < argc; i++) {
+ struct dp_packet *packet;
+
+ packet = eth_from_packet_or_flow(argv[i]);
+ if (!packet) {
+ unixctl_command_reply_error(conn, "bad packet syntax");
+ goto exit;
+ }
+
+ ovs_mutex_lock(&dummy_dev->mutex);
+ netdev_dummy_queue_packet(dummy_dev, packet);
+ ovs_mutex_unlock(&dummy_dev->mutex);
+ }
+
+ unixctl_command_reply(conn, NULL);
+
+exit:
+ netdev_close(netdev);
+}
+
+static void
+netdev_dummy_set_admin_state__(struct netdev_dummy *dev, bool admin_state)
+ OVS_REQUIRES(dev->mutex)
+{
+ enum netdev_flags old_flags;
+
+ if (admin_state) {
+ netdev_dummy_update_flags__(dev, 0, NETDEV_UP, &old_flags);
+ } else {
+ netdev_dummy_update_flags__(dev, NETDEV_UP, 0, &old_flags);
+ }
+}
+
+static void
+netdev_dummy_set_admin_state(struct unixctl_conn *conn, int argc,
+ const char *argv[], void *aux OVS_UNUSED)
+{
+ bool up;
+
+ if (!strcasecmp(argv[argc - 1], "up")) {
+ up = true;
+ } else if ( !strcasecmp(argv[argc - 1], "down")) {
+ up = false;
+ } else {
+ unixctl_command_reply_error(conn, "Invalid Admin State");
+ return;
+ }
+
+ if (argc > 2) {
+ struct netdev *netdev = netdev_from_name(argv[1]);
+ if (netdev && is_dummy_class(netdev->netdev_class)) {
+ struct netdev_dummy *dummy_dev = netdev_dummy_cast(netdev);
+
+ ovs_mutex_lock(&dummy_dev->mutex);
+ netdev_dummy_set_admin_state__(dummy_dev, up);
+ ovs_mutex_unlock(&dummy_dev->mutex);
+
+ netdev_close(netdev);
+ } else {
+ unixctl_command_reply_error(conn, "Unknown Dummy Interface");
+ netdev_close(netdev);
+ return;
+ }
+ } else {
+ struct netdev_dummy *netdev;
+
+ ovs_mutex_lock(&dummy_list_mutex);
+ LIST_FOR_EACH (netdev, list_node, &dummy_list) {
+ ovs_mutex_lock(&netdev->mutex);
+ netdev_dummy_set_admin_state__(netdev, up);
+ ovs_mutex_unlock(&netdev->mutex);
+ }
+ ovs_mutex_unlock(&dummy_list_mutex);
+ }
+ unixctl_command_reply(conn, "OK");
+}
+
+static void
+display_conn_state__(struct ds *s, const char *name,
+ enum dummy_netdev_conn_state state)
+{
+ ds_put_format(s, "%s: ", name);
+
+ switch (state) {
+ case CONN_STATE_CONNECTED:
+ ds_put_cstr(s, "connected\n");
+ break;
+
+ case CONN_STATE_NOT_CONNECTED:
+ ds_put_cstr(s, "disconnected\n");
+ break;
+
+ case CONN_STATE_UNKNOWN:
+ default:
+ ds_put_cstr(s, "unknown\n");
+ break;
+ };
+}
+
+static void
+netdev_dummy_conn_state(struct unixctl_conn *conn, int argc,
+ const char *argv[], void *aux OVS_UNUSED)
+{
+ enum dummy_netdev_conn_state state = CONN_STATE_UNKNOWN;
+ struct ds s;
+
+ ds_init(&s);
+
+ if (argc > 1) {
+ const char *dev_name = argv[1];
+ struct netdev *netdev = netdev_from_name(dev_name);
+
+ if (netdev && is_dummy_class(netdev->netdev_class)) {
+ struct netdev_dummy *dummy_dev = netdev_dummy_cast(netdev);
+
+ ovs_mutex_lock(&dummy_dev->mutex);
+ state = dummy_netdev_get_conn_state(&dummy_dev->conn);
+ ovs_mutex_unlock(&dummy_dev->mutex);
+
+ netdev_close(netdev);
+ }
+ display_conn_state__(&s, dev_name, state);
+ } else {
+ struct netdev_dummy *netdev;
+
+ ovs_mutex_lock(&dummy_list_mutex);
+ LIST_FOR_EACH (netdev, list_node, &dummy_list) {
+ ovs_mutex_lock(&netdev->mutex);
+ state = dummy_netdev_get_conn_state(&netdev->conn);
+ ovs_mutex_unlock(&netdev->mutex);
+ if (state != CONN_STATE_UNKNOWN) {
+ display_conn_state__(&s, netdev->up.name, state);
+ }
+ }
+ ovs_mutex_unlock(&dummy_list_mutex);
+ }
+
+ unixctl_command_reply(conn, ds_cstr(&s));
+ ds_destroy(&s);
+}
+
+static void
+netdev_dummy_ip4addr(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ const char *argv[], void *aux OVS_UNUSED)
+{
+ struct netdev *netdev = netdev_from_name(argv[1]);
+
+ if (netdev && is_dummy_class(netdev->netdev_class)) {
+ struct in_addr ip, mask;
+ char *error;
+
+ error = ip_parse_masked(argv[2], &ip.s_addr, &mask.s_addr);
+ if (!error) {
+ netdev_dummy_set_in4(netdev, ip, mask);
+ unixctl_command_reply(conn, "OK");
+ } else {
+ unixctl_command_reply_error(conn, error);
+ free(error);
+ }
+ } else {
+ unixctl_command_reply_error(conn, "Unknown Dummy Interface");
+ }
+
+ netdev_close(netdev);
+}
+
+static void
+netdev_dummy_ip6addr(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ const char *argv[], void *aux OVS_UNUSED)
+{
+ struct netdev *netdev = netdev_from_name(argv[1]);
+
+ if (netdev && is_dummy_class(netdev->netdev_class)) {
+ char ip6_s[IPV6_SCAN_LEN + 1];
+ struct in6_addr ip6;
+
+ if (ovs_scan(argv[2], IPV6_SCAN_FMT, ip6_s) &&
+ inet_pton(AF_INET6, ip6_s, &ip6) == 1) {
+ netdev_dummy_set_in6(netdev, &ip6);
+ unixctl_command_reply(conn, "OK");
+ } else {
+ unixctl_command_reply_error(conn, "Invalid parameters");
+ }
+ netdev_close(netdev);
+ } else {
+ unixctl_command_reply_error(conn, "Unknown Dummy Interface");
+ }
+
+ netdev_close(netdev);
+}
+
+
+static void
+netdev_dummy_override(const char *type)
+{
+ if (!netdev_unregister_provider(type)) {
+ struct netdev_class *class;
+ int error;
+
+ class = xmemdup(&dummy_class, sizeof dummy_class);
+ class->type = xstrdup(type);
+ error = netdev_register_provider(class);
+ if (error) {
+ VLOG_ERR("%s: failed to register netdev provider (%s)",
+ type, ovs_strerror(error));
+ free(CONST_CAST(char *, class->type));
+ free(class);
+ }
+ }
+}
+