odp-util: New function odp_flow_key_from_string().
authorBen Pfaff <blp@nicira.com>
Thu, 4 Aug 2011 23:20:34 +0000 (16:20 -0700)
committerBen Pfaff <blp@nicira.com>
Mon, 8 Aug 2011 20:14:01 +0000 (13:14 -0700)
This will be used in upcoming commits.

lib/odp-util.c
lib/odp-util.h
lib/packets.h
tests/automake.mk
tests/odp.at [new file with mode: 0644]
tests/test-odp.c [new file with mode: 0644]
tests/testsuite.at

index 62cb50469a6eaab682455a7da9fcfd0cd9d1fd54..d5523163efec7da173209d24ad2092eec2e99c23 100644 (file)
@@ -27,6 +27,7 @@
 #include "dynamic-string.h"
 #include "flow.h"
 #include "netlink.h"
+#include "ofpbuf.h"
 #include "openvswitch/tunnel.h"
 #include "packets.h"
 #include "timeval.h"
@@ -389,6 +390,312 @@ odp_flow_key_format(const struct nlattr *key, size_t key_len, struct ds *ds)
     }
 }
 
+static int
+put_nd_key(int n, const char *nd_target_s,
+           const uint8_t *nd_sll, const uint8_t *nd_tll, struct ofpbuf *key)
+{
+    struct odp_key_nd nd_key;
+
+    memset(&nd_key, 0, sizeof nd_key);
+    if (inet_pton(AF_INET6, nd_target_s, nd_key.nd_target) != 1) {
+        return -EINVAL;
+    }
+    if (nd_sll) {
+        memcpy(nd_key.nd_sll, nd_sll, ETH_ADDR_LEN);
+    }
+    if (nd_tll) {
+        memcpy(nd_key.nd_tll, nd_tll, ETH_ADDR_LEN);
+    }
+    nl_msg_put_unspec(key, ODP_KEY_ATTR_ND, &nd_key, sizeof nd_key);
+    return n;
+}
+
+static int
+parse_odp_key_attr(const char *s, struct ofpbuf *key)
+{
+    /* Many of the sscanf calls in this function use oversized destination
+     * fields because some sscanf() implementations truncate the range of %i
+     * directives, so that e.g. "%"SCNi16 interprets input of "0xfedc" as a
+     * value of 0x7fff.  The other alternatives are to allow only a single
+     * radix (e.g. decimal or hexadecimal) or to write more sophisticated
+     * parsers.
+     *
+     * The tun_id parser has to use an alternative approach because there is no
+     * type larger than 64 bits. */
+
+    {
+        char tun_id_s[32];
+        int n = -1;
+
+        if (sscanf(s, "tun_id(%31[x0123456789abcdefABCDEF])%n",
+                   tun_id_s, &n) > 0 && n > 0) {
+            uint64_t tun_id = strtoull(tun_id_s, NULL, 0);
+            nl_msg_put_be64(key, ODP_KEY_ATTR_TUN_ID, htonll(tun_id));
+            return n;
+        }
+    }
+
+    {
+        unsigned long long int in_port;
+        int n = -1;
+
+        if (sscanf(s, "in_port(%lli)%n", &in_port, &n) > 0 && n > 0) {
+            nl_msg_put_u32(key, ODP_KEY_ATTR_IN_PORT, in_port);
+            return n;
+        }
+    }
+
+    {
+        struct odp_key_ethernet eth_key;
+        int n = -1;
+
+        if (sscanf(s,
+                   "eth(src="ETH_ADDR_SCAN_FMT",dst="ETH_ADDR_SCAN_FMT")%n",
+                   ETH_ADDR_SCAN_ARGS(eth_key.eth_src),
+                   ETH_ADDR_SCAN_ARGS(eth_key.eth_dst), &n) > 0 && n > 0) {
+            nl_msg_put_unspec(key, ODP_KEY_ATTR_ETHERNET,
+                              &eth_key, sizeof eth_key);
+            return n;
+        }
+    }
+
+    {
+        uint16_t tpid = ETH_TYPE_VLAN;
+        uint16_t vid;
+        int pcp;
+        int n = -1;
+
+        if ((sscanf(s, "vlan(vid=%"SCNi16",pcp=%i)%n",
+                    &vid, &pcp, &n) > 0 && n > 0) ||
+            (sscanf(s, "vlan(tpid=%"SCNi16",vid=%"SCNi16",pcp=%i)%n",
+                    &tpid, &vid, &pcp, &n) > 0 && n > 0)) {
+            struct odp_key_8021q q_key;
+
+            q_key.q_tpid = htons(tpid);
+            q_key.q_tci = htons((vid << VLAN_VID_SHIFT) |
+                                (pcp << VLAN_PCP_SHIFT));
+            nl_msg_put_unspec(key, ODP_KEY_ATTR_8021Q, &q_key, sizeof q_key);
+            return n;
+        }
+    }
+
+    {
+        uint16_t eth_type;
+        int n = -1;
+
+        if (sscanf(s, "eth_type(%"SCNi16")%n", &eth_type, &n) > 0 && n > 0) {
+            nl_msg_put_be16(key, ODP_KEY_ATTR_ETHERTYPE, htons(eth_type));
+            return n;
+        }
+    }
+
+    {
+        ovs_be32 ipv4_src;
+        ovs_be32 ipv4_dst;
+        int ipv4_proto;
+        int ipv4_tos;
+        int n = -1;
+
+        if (sscanf(s, "ipv4(src="IP_SCAN_FMT",dst="IP_SCAN_FMT","
+                   "proto=%i,tos=%i)%n",
+                   IP_SCAN_ARGS(&ipv4_src),
+                   IP_SCAN_ARGS(&ipv4_dst), &ipv4_proto, &ipv4_tos, &n) > 0
+            && n > 0) {
+            struct odp_key_ipv4 ipv4_key;
+
+            memset(&ipv4_key, 0, sizeof ipv4_key);
+            ipv4_key.ipv4_src = ipv4_src;
+            ipv4_key.ipv4_dst = ipv4_dst;
+            ipv4_key.ipv4_proto = ipv4_proto;
+            ipv4_key.ipv4_tos = ipv4_tos;
+            nl_msg_put_unspec(key, ODP_KEY_ATTR_IPV4,
+                              &ipv4_key, sizeof ipv4_key);
+            return n;
+        }
+    }
+
+    {
+        char ipv6_src_s[IPV6_SCAN_LEN + 1];
+        char ipv6_dst_s[IPV6_SCAN_LEN + 1];
+        int ipv6_proto;
+        int ipv6_tos;
+        int n = -1;
+
+        if (sscanf(s, "ipv6(src="IPV6_SCAN_FMT",dst="IPV6_SCAN_FMT","
+                   "proto=%i,tos=%i)%n",
+                   ipv6_src_s, ipv6_dst_s,
+                   &ipv6_proto, &ipv6_tos, &n) > 0 && n > 0) {
+            struct odp_key_ipv6 ipv6_key;
+
+            memset(&ipv6_key, 0, sizeof ipv6_key);
+            if (inet_pton(AF_INET6, ipv6_src_s, &ipv6_key.ipv6_src) != 1 ||
+                inet_pton(AF_INET6, ipv6_dst_s, &ipv6_key.ipv6_dst) != 1) {
+                return -EINVAL;
+            }
+            ipv6_key.ipv6_proto = ipv6_proto;
+            ipv6_key.ipv6_tos = ipv6_tos;
+            nl_msg_put_unspec(key, ODP_KEY_ATTR_IPV6,
+                              &ipv6_key, sizeof ipv6_key);
+            return n;
+        }
+    }
+
+    {
+        int tcp_src;
+        int tcp_dst;
+        int n = -1;
+
+        if (sscanf(s, "tcp(src=%i,dst=%i)%n",&tcp_src, &tcp_dst, &n) > 0
+            && n > 0) {
+            struct odp_key_tcp tcp_key;
+
+            tcp_key.tcp_src = htons(tcp_src);
+            tcp_key.tcp_dst = htons(tcp_dst);
+            nl_msg_put_unspec(key, ODP_KEY_ATTR_TCP, &tcp_key, sizeof tcp_key);
+            return n;
+        }
+    }
+
+    {
+        int udp_src;
+        int udp_dst;
+        int n = -1;
+
+        if (sscanf(s, "udp(src=%i,dst=%i)%n", &udp_src, &udp_dst, &n) > 0
+            && n > 0) {
+            struct odp_key_udp udp_key;
+
+            udp_key.udp_src = htons(udp_src);
+            udp_key.udp_dst = htons(udp_dst);
+            nl_msg_put_unspec(key, ODP_KEY_ATTR_UDP, &udp_key, sizeof udp_key);
+            return n;
+        }
+    }
+
+    {
+        int icmp_type;
+        int icmp_code;
+        int n = -1;
+
+        if (sscanf(s, "icmp(type=%i,code=%i)%n",
+                   &icmp_type, &icmp_code, &n) > 0
+            && n > 0) {
+            struct odp_key_icmp icmp_key;
+
+            icmp_key.icmp_type = icmp_type;
+            icmp_key.icmp_code = icmp_code;
+            nl_msg_put_unspec(key, ODP_KEY_ATTR_ICMP,
+                              &icmp_key, sizeof icmp_key);
+            return n;
+        }
+    }
+
+    {
+        struct odp_key_icmpv6 icmpv6_key;
+        int n = -1;
+
+        if (sscanf(s, "icmpv6(type=%"SCNi8",code=%"SCNi8")%n",
+                   &icmpv6_key.icmpv6_type, &icmpv6_key.icmpv6_code,&n) > 0
+            && n > 0) {
+            nl_msg_put_unspec(key, ODP_KEY_ATTR_ICMPV6,
+                              &icmpv6_key, sizeof icmpv6_key);
+            return n;
+        }
+    }
+
+    {
+        ovs_be32 arp_sip;
+        ovs_be32 arp_tip;
+        int arp_op;
+        uint8_t arp_sha[ETH_ADDR_LEN];
+        uint8_t arp_tha[ETH_ADDR_LEN];
+        int n = -1;
+
+        if (sscanf(s, "arp(sip="IP_SCAN_FMT",tip="IP_SCAN_FMT","
+                   "op=%i,sha="ETH_ADDR_SCAN_FMT",tha="ETH_ADDR_SCAN_FMT")%n",
+                   IP_SCAN_ARGS(&arp_sip),
+                   IP_SCAN_ARGS(&arp_tip),
+                   &arp_op,
+                   ETH_ADDR_SCAN_ARGS(arp_sha),
+                   ETH_ADDR_SCAN_ARGS(arp_tha), &n) > 0 && n > 0) {
+            struct odp_key_arp arp_key;
+
+            memset(&arp_key, 0, sizeof arp_key);
+            arp_key.arp_sip = arp_sip;
+            arp_key.arp_tip = arp_tip;
+            arp_key.arp_op = htons(arp_op);
+            memcpy(arp_key.arp_sha, arp_sha, ETH_ADDR_LEN);
+            memcpy(arp_key.arp_tha, arp_tha, ETH_ADDR_LEN);
+            nl_msg_put_unspec(key, ODP_KEY_ATTR_ARP, &arp_key, sizeof arp_key);
+            return n;
+        }
+    }
+
+    {
+        char nd_target_s[IPV6_SCAN_LEN + 1];
+        uint8_t nd_sll[ETH_ADDR_LEN];
+        uint8_t nd_tll[ETH_ADDR_LEN];
+        int n = -1;
+
+        if (sscanf(s, "nd(target="IPV6_SCAN_FMT")%n",
+                   nd_target_s, &n) > 0 && n > 0) {
+            return put_nd_key(n, nd_target_s, NULL, NULL, key);
+        }
+        if (sscanf(s, "nd(target="IPV6_SCAN_FMT",sll="ETH_ADDR_SCAN_FMT")%n",
+                   nd_target_s, ETH_ADDR_SCAN_ARGS(nd_sll), &n) > 0
+            && n > 0) {
+            return put_nd_key(n, nd_target_s, nd_sll, NULL, key);
+        }
+        if (sscanf(s, "nd(target="IPV6_SCAN_FMT",tll="ETH_ADDR_SCAN_FMT")%n",
+                   nd_target_s, ETH_ADDR_SCAN_ARGS(nd_tll), &n) > 0
+            && n > 0) {
+            return put_nd_key(n, nd_target_s, NULL, nd_tll, key);
+        }
+        if (sscanf(s, "nd(target="IPV6_SCAN_FMT",sll="ETH_ADDR_SCAN_FMT","
+                   "tll="ETH_ADDR_SCAN_FMT")%n",
+                   nd_target_s, ETH_ADDR_SCAN_ARGS(nd_sll),
+                   ETH_ADDR_SCAN_ARGS(nd_tll), &n) > 0
+            && n > 0) {
+            return put_nd_key(n, nd_target_s, nd_sll, nd_tll, key);
+        }
+    }
+
+    return -EINVAL;
+}
+
+/* Parses the string representation of an ODP flow key, in the format output by
+ * odp_flow_key_format().  Returns 0 if successful, otherwise a positive errno
+ * value.  On success, the flow key is appended to 'key' as a series of Netlink
+ * attributes.  On failure, no data is appended to 'key'.  Either way, 'key''s
+ * data might be reallocated.
+ *
+ * On success, the attributes appended to 'key' are individually syntactically
+ * valid, but they may not be valid as a sequence.  'key' might, for example,
+ * be missing an "in_port" key, have duplicated keys, or have keys in the wrong
+ * order.  odp_flow_key_to_flow() will detect those errors. */
+int
+odp_flow_key_from_string(const char *s, struct ofpbuf *key)
+{
+    const size_t old_size = key->size;
+    for (;;) {
+        int retval;
+
+        s += strspn(s, ", \t\r\n");
+        if (!*s) {
+            return 0;
+        }
+
+        retval = parse_odp_key_attr(s, key);
+        if (retval < 0) {
+            key->size = old_size;
+            return -retval;
+        }
+        s += retval;
+    }
+
+    return 0;
+}
+
 /* Appends a representation of 'flow' as ODP_KEY_ATTR_* attributes to 'buf'. */
 void
 odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow)
index a88c7eec8709d0b6c5ae2ae75861991126488fda..dd320aca523b5f8b63aee6e3edfe5c35584d5106 100644 (file)
@@ -94,6 +94,7 @@ struct odputil_keybuf {
 };
 
 void odp_flow_key_format(const struct nlattr *, size_t, struct ds *);
+int odp_flow_key_from_string(const char *s, struct ofpbuf *);
 
 void odp_flow_key_from_flow(struct ofpbuf *, const struct flow *);
 int odp_flow_key_to_flow(const struct nlattr *, size_t, struct flow *);
index 8e13a25ba4fb9681afe5cb06419abda3febc21a7..a389e6af74ff500a21612c6c98f47ddecc5bc1a8 100644 (file)
@@ -264,6 +264,25 @@ BUILD_ASSERT_DECL(VLAN_ETH_HEADER_LEN == sizeof(struct vlan_eth_header));
         ((uint8_t *) ip)[2],                    \
         ((uint8_t *) ip)[3]
 
+/* Example:
+ *
+ * char *string = "1 33.44.55.66 2";
+ * ovs_be32 ip;
+ * int a, b;
+ *
+ * if (sscanf(string, "%d"IP_SCAN_FMT"%d",
+ *     &a, IP_SCAN_ARGS(&ip), &b) == 1 + IP_SCAN_COUNT + 1) {
+ *     ...
+ * }
+ */
+#define IP_SCAN_FMT "%"SCNu8".%"SCNu8".%"SCNu8".%"SCNu8
+#define IP_SCAN_ARGS(ip)                                    \
+        ((void) (ovs_be32) *(ip), &((uint8_t *) ip)[0]),    \
+        &((uint8_t *) ip)[1],                               \
+        &((uint8_t *) ip)[2],                               \
+        &((uint8_t *) ip)[3]
+#define IP_SCAN_COUNT 4
+
 /* Returns true if 'netmask' is a CIDR netmask, that is, if it consists of N
  * high-order 1-bits and 32-N low-order 0-bits. */
 static inline bool
@@ -366,6 +385,20 @@ struct arp_eth_header {
 } __attribute__((packed));
 BUILD_ASSERT_DECL(ARP_ETH_HEADER_LEN == sizeof(struct arp_eth_header));
 
+/* Example:
+ *
+ * char *string = "1 ::1 2";
+ * char ipv6_s[IPV6_SCAN_LEN + 1];
+ * struct in6_addr ipv6;
+ *
+ * if (sscanf(string, "%d"IPV6_SCAN_FMT"%d", &a, ipv6_s, &b) == 3
+ *     && inet_pton(AF_INET6, ipv6_s, &ipv6) == 1) {
+ *     ...
+ * }
+ */
+#define IPV6_SCAN_FMT "%46[0123456789abcdefABCDEF:.]"
+#define IPV6_SCAN_LEN 46
+
 extern const struct in6_addr in6addr_exact;
 #define IN6ADDR_EXACT_INIT { { { 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, \
                                  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff } } }
index 33307ccc36c3a04a41d8d96b5bfdcf00f2dc957c..eb6351f627675ea7d47ce929350e01e0cc7da79a 100644 (file)
@@ -15,6 +15,7 @@ TESTSUITE_AT = \
        tests/daemon-py.at \
        tests/ofp-print.at \
        tests/ovs-ofctl.at \
+       tests/odp.at \
        tests/multipath.at \
        tests/autopath.at \
        tests/vconn.at \
@@ -80,6 +81,7 @@ lcov_wrappers = \
        tests/lcov/test-list \
        tests/lcov/test-lockfile \
        tests/lcov/test-multipath \
+       tests/lcov/test-odp \
        tests/lcov/test-ovsdb \
        tests/lcov/test-packets \
        tests/lcov/test-random \
@@ -133,6 +135,7 @@ valgrind_wrappers = \
        tests/valgrind/test-list \
        tests/valgrind/test-lockfile \
        tests/valgrind/test-multipath \
+       tests/valgrind/test-odp \
        tests/valgrind/test-openflowd \
        tests/valgrind/test-ovsdb \
        tests/valgrind/test-packets \
@@ -261,6 +264,10 @@ noinst_PROGRAMS += tests/test-unix-socket
 tests_test_unix_socket_SOURCES = tests/test-unix-socket.c
 tests_test_unix_socket_LDADD = lib/libopenvswitch.a
 
+noinst_PROGRAMS += tests/test-odp
+tests_test_odp_SOURCES = tests/test-odp.c
+tests_test_odp_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-ovsdb
 tests_test_ovsdb_SOURCES = \
        tests/test-ovsdb.c \
diff --git a/tests/odp.at b/tests/odp.at
new file mode 100644 (file)
index 0000000..53eb593
--- /dev/null
@@ -0,0 +1,40 @@
+AT_SETUP([ODP parsing and formatting - valid forms])
+AT_DATA([odp-base.txt], [dnl
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x1234)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=5,tos=128)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=6,tos=0),tcp(src=80,dst=8080)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=17,tos=0),udp(src=81,dst=6632)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=1,tos=0),icmp(type=1,code=2)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,proto=10,tos=112)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,proto=6,tos=0),tcp(src=80,dst=8080)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,proto=17,tos=0),udp(src=6630,dst=22)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,proto=58,tos=0),icmpv6(type=1,code=2)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,proto=58,tos=0),icmpv6(type=135,code=0),nd(target=::3)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,proto=58,tos=0),icmpv6(type=135,code=0),nd(target=::3,sll=00:05:06:07:08:09)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,proto=58,tos=0),icmpv6(type=136,code=0),nd(target=::3,tll=00:0a:0b:0c:0d:0e)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,proto=58,tos=0),icmpv6(type=136,code=0),nd(target=::3,sll=00:05:06:07:08:09,tll=00:0a:0b:0c:0d:0e)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0806),arp(sip=1.2.3.4,tip=5.6.7.8,op=1,sha=00:0f:10:11:12:13,tha=00:14:15:16:17:18)
+])
+
+(echo '# Valid forms without tun_id or VLAN header.'
+ cat odp-base.txt
+
+ echo
+ echo '# Valid forms with tun_id header.'
+ sed 's/^/tun_id(0x7f10354),/' odp-base.txt
+
+ echo
+ echo '# Valid forms with VLAN header.'
+ sed 's/eth([[^)]]*)/&,vlan(vid=99,pcp=7)/' odp-base.txt
+
+ echo
+ echo '# Valid forms with tun_id and VLAN headers.'
+ sed 's/^/tun_id(0xfedcba9876543210),/
+s/eth([[^)]]*)/&,vlan(vid=99,pcp=7)/' odp-base.txt) > odp.txt
+AT_CAPTURE_FILE([odp.txt])
+AT_CHECK_UNQUOTED([test-odp < odp.txt], [0], [`cat odp.txt`
+])
+AT_CLEANUP
+
+dnl We could add a test for invalid forms, but that's less important.
diff --git a/tests/test-odp.c b/tests/test-odp.c
new file mode 100644 (file)
index 0000000..9db27bf
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2011 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include "dynamic-string.h"
+#include "flow.h"
+#include "odp-util.h"
+#include "ofpbuf.h"
+
+int
+main(void)
+{
+    struct ds in;
+
+    ds_init(&in);
+    while (!ds_get_line(&in, stdin)) {
+        struct ofpbuf odp_key;
+        struct flow flow;
+        struct ds out;
+        int error;
+        char *s;
+
+        /* Delete comments, skip blank lines. */
+        s = ds_cstr(&in);
+        if (*s == '#') {
+            puts(s);
+            continue;
+        }
+        if (strchr(s, '#')) {
+            *strchr(s, '#') = '\0';
+        }
+        if (s[strspn(s, " ")] == '\0') {
+            putchar('\n');
+            continue;
+        }
+
+        /* Convert string to ODP key. */
+        ofpbuf_init(&odp_key, 0);
+        error = odp_flow_key_from_string(ds_cstr(&in), &odp_key);
+        if (error) {
+            printf("odp_flow_key_from_string: error\n");
+            goto next;
+        }
+
+        /* Convert odp_key to flow. */
+        error = odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow);
+        if (error) {
+            printf("odp_flow_key_to_flow: error\n");
+            goto next;
+        }
+
+        /* Convert cls_rule back to odp_key. */
+        ofpbuf_uninit(&odp_key);
+        ofpbuf_init(&odp_key, 0);
+        odp_flow_key_from_flow(&odp_key, &flow);
+
+        /* Convert odp_key to string. */
+        ds_init(&out);
+        odp_flow_key_format(odp_key.data, odp_key.size, &out);
+        puts(ds_cstr(&out));
+        ds_destroy(&out);
+
+    next:
+        ofpbuf_uninit(&odp_key);
+    }
+    ds_destroy(&in);
+
+    return 0;
+}
index 6ec77f8cf2fac45b2e70ac5b52d2416347600318..c22ece71ae90ec79ebb12e4b6a6a67f02affbd1d 100644 (file)
@@ -46,6 +46,7 @@ m4_include([tests/daemon.at])
 m4_include([tests/daemon-py.at])
 m4_include([tests/ofp-print.at])
 m4_include([tests/ovs-ofctl.at])
+m4_include([tests/odp.at])
 m4_include([tests/multipath.at])
 m4_include([tests/autopath.at])
 m4_include([tests/vconn.at])