+
+ unixctl_command_reply(conn, result.string);
+ ds_destroy(&result);
+out:
+ if (error) {
+ unixctl_command_reply_error(conn, error);
+ free(error);
+ }
+ dp_packet_delete(packet);
+ free(l7);
+ ofpbuf_uninit(&odp_mask);
+ ofpbuf_uninit(&odp_key);
+}
+
static void
ofproto_unixctl_trace(struct unixctl_conn *conn, int argc, const char *argv[],
void *aux OVS_UNUSED)
@@ -876,6 +943,9 @@ ofproto_dpif_trace_init(void)
"[-consistent] {[dp_name] odp_flow | bridge br_flow} [OPTIONS...] "
"[-generate|packet] actions",
2, INT_MAX, ofproto_unixctl_trace_actions, NULL);
+ unixctl_command_register(
+ "ofproto/hexify", "odp_flow [payload]",
+ 1, 2, ofproto_unixctl_hexify, NULL);
}
void
diff --git a/ofproto/ofproto-unixctl.man b/ofproto/ofproto-unixctl.man
index 095afd57c..8dc68a38d 100644
--- a/ofproto/ofproto-unixctl.man
+++ b/ofproto/ofproto-unixctl.man
@@ -4,7 +4,7 @@ These commands manage the core OpenFlow switch implementation
(called
.
.IP "\fBofproto/list\fR"
Lists the names of the running ofproto instances. These are the names
-that may be used on \fBofproto/trace\fR.
+that may be used on \fBofproto/trace\fR and \fBofproto/hexify\fR.
.
.IP "\fBofproto/trace\fR [\fIoptions\fR] [\fIdpname\fR] \fIodp_flow\fR
[\fIpacket\fR]
.IQ "\fBofproto/trace\fR [\fIoptions\fR] \fIbridge\fR \fIbr_flow\fR
[\fIpacket\fR]]
@@ -230,3 +230,37 @@ ofproto/trace br in_port=1,arp,arp_op=2
.fi
.RE
.RE
+
+.IQ "\fBofproto/hexify\fR \fIodp_flow\fR [\fIpayload\fR]"
+Generates a hex digits representation of a frame matching
+\fIodp_flow\fR. When \fIpayload\fR is passed, the additional payload
+is attached to the end of the generated frame representation.
+.
+.RS
+.IP \(bu
+\fIodp_flow\fR is a flow in the form printed by \fBovs\-dpctl\fR(8)'s
+\fBdump\-flows\fR command.
+.
+.IP \(bu
+The command is agnostic to the datapath, that's why it doesn't support
+some standard \fIodp_flow\fR features like using port names.
+.RE
+.
+.IP "Usage examples:"
+.RS 4
+.PP
+\fBGenerate a unicast IPv4 TCP ACK packet\fR
+.RS 4
+.nf
+ofproto/hexify eth(src=50:54:00:00:00:05,dst=50:54:00:00:01:00),
+eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=6),
+tcp(src=8,dst=9),tcp_flags(ack)
+.RE
+.fi
+.PP
+\fBGenerate a unicast IPv4 TCP ACK packet with additional payload\fR
+.RS 4
+.nf
+ofproto/hexify eth(src=50:54:00:00:00:05,dst=50:54:00:00:01:00),
+eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=6),
+tcp(src=8,dst=9),tcp_flags(ack) 010203abcdef
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index a39d0d3ae..e51e0f412 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -12041,3 +12041,48 @@ AT_CHECK([test 1 = `ovs-ofctl parse-pcap p2-tx.pcap |
wc -l`])
OVS_VSWITCHD_STOP
AT_CLEANUP
+
+AT_SETUP([ofproto-dpif - ofproto/hexify])
+OVS_VSWITCHD_START
+
+# check that invalid syntax generates an error: ethernet() instead of eth()
+flow_s="\
+ethernet(src=50:54:00:00:00:05,dst=50:54:00:00:01:00),eth_type(0x0800),\
+ipv4(src=10.0.0.2,dst=10.0.0.1,proto=6,tos=0,ttl=64,frag=no),\
+tcp(src=8,dst=9),tcp_flags(ack)"
+AT_CHECK([ovs-appctl ofproto/hexify ${flow_s}], [2], [stdout], [stderr])
+AT_CHECK_UNQUOTED([tail -2 stderr], [0], [syntax error at ${flow_s}
+ovs-appctl: ovs-vswitchd: server returned an error
+])
+
+# check that a simpler flow string is successfully hexified
+flow_s="eth(src=50:54:00:00:00:05,dst=50:54:00:00:01:00),eth_type(0x0800)"
+expected=505400000100505400000005080045000014000000000000baeb0000000000000000
+actual=$(ovs-appctl ofproto/hexify ${flow_s})
+AT_CHECK([test ${expected} = ${actual}])
+
+# check that a longer flow string is successfully hexified
+flow_s="\
+eth(src=50:54:00:00:00:05,dst=50:54:00:00:01:00),eth_type(0x0800),\
+ipv4(src=10.0.0.2,dst=10.0.0.1,proto=6,tos=0,ttl=64,frag=no),\
+tcp(src=8,dst=9),tcp_flags(ack)"
+expected=50540000010050540000000508004500002800000000400666ce0a0000020a000001000800090000000000000000501000009bc10000
+actual=$(ovs-appctl ofproto/hexify ${flow_s})
+AT_CHECK([test ${expected} = ${actual}])
+
+# check that incomplete payload fails the command
+l7payload=$(printf 'd%.0s' {1..13})
+AT_CHECK([ovs-appctl ofproto/hexify ${flow_s} ${l7payload}], [2], [stdout],
[stderr])
+AT_CHECK([tail -2 stderr], [0], [dnl
+Trailing garbage in payload data
+ovs-appctl: ovs-vswitchd: server returned an error
+])
+
+# check that a properly sized payload is successfully appended
+l7payload=$(printf 'dead%.0s' {1..10})
+expected=50540000010050540000000508004500003c00000000400666ba0a0000020a00000100080009000000000000000050100000e8e20000deaddeaddeaddeaddeaddeaddeaddeaddeaddead
+actual=$(ovs-appctl ofproto/hexify ${flow_s} ${l7payload})
+AT_CHECK([test ${expected} = ${actual}])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP