+static struct ofpbuf *
+eth_from_packet_or_flow(const char *s)
+{
+ enum odp_key_fitness fitness;
+ struct ofpbuf *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(ofpbuf_data(&odp_key), ofpbuf_size(&odp_key), &flow);
+ if (fitness == ODP_FIT_ERROR) {
+ ofpbuf_uninit(&odp_key);
+ return NULL;
+ }
+
+ packet = ofpbuf_new(0);
+ flow_compose(packet, &flow);
+
+ ofpbuf_uninit(&odp_key);
+ return packet;
+}
+
+static void
+netdev_dummy_queue_packet__(struct netdev_rxq_dummy *rx, struct ofpbuf *packet)
+{
+ list_push_back(&rx->recv_queue, &packet->list_node);
+ rx->recv_queue_len++;
+ seq_change(rx->seq);
+}
+
+static void
+netdev_dummy_queue_packet(struct netdev_dummy *dummy, struct ofpbuf *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, ofpbuf_clone(packet));
+ }
+ prev = rx;
+ }
+ }
+ if (prev) {
+ netdev_dummy_queue_packet__(prev, packet);
+ } else {
+ ofpbuf_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 ofpbuf *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");
+}
+