+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_key_from_string(s, NULL, &odp_key);
+ 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 = ofpbuf_new(0);
+ flow_compose(packet, &flow);
+
+ ofpbuf_uninit(&odp_key);
+ return packet;
+}
+
+static void
+netdev_dummy_receive(struct unixctl_conn *conn,
+ int argc, const char *argv[], void *aux OVS_UNUSED)
+{
+ struct netdev_dev_dummy *dummy_dev;
+ int n_listeners;
+ int i;
+
+ dummy_dev = shash_find_data(&dummy_netdev_devs, argv[1]);
+ if (!dummy_dev) {
+ unixctl_command_reply_error(conn, "no such dummy netdev");
+ return;
+ }
+
+ n_listeners = 0;
+ for (i = 2; i < argc; i++) {
+ struct netdev_dummy *dev;
+ struct ofpbuf *packet;
+
+ packet = eth_from_packet_or_flow(argv[i]);
+ if (!packet) {
+ unixctl_command_reply_error(conn, "bad packet syntax");
+ return;
+ }
+
+ n_listeners = 0;
+ LIST_FOR_EACH (dev, node, &dummy_dev->devs) {
+ if (dev->listening) {
+ struct ofpbuf *copy = ofpbuf_clone(packet);
+ list_push_back(&dev->recv_queue, ©->list_node);
+ n_listeners++;
+ }
+ }
+ ofpbuf_delete(packet);
+ }
+
+ if (!n_listeners) {
+ unixctl_command_reply(conn, "packets queued but nobody listened");
+ } else {
+ unixctl_command_reply(conn, "success");
+ }
+}
+