diff --git a/lib/netdev-tc-offloads.c b/lib/netdev-tc-offloads.c
index f470aa3..7729cfc 100644
--- a/lib/netdev-tc-offloads.c
+++ b/lib/netdev-tc-offloads.c
@@ -184,28 +184,152 @@ netdev_tc_flow_dump_create(struct netdev *netdev)
 {
     struct netdev_flow_dump *dump = xzalloc(sizeof *dump);
 
+    memset(dump, 0, sizeof *dump);
+    dump->nl_dump = xzalloc(sizeof *dump->nl_dump);
     dump->netdev = netdev_ref(netdev);
+    tc_dump_flower_start(netdev_get_ifindex(netdev), dump->nl_dump);
     return dump;
 }
 
 int
 netdev_tc_flow_dump_destroy(struct netdev_flow_dump *dump)
 {
+    nl_dump_done(dump->nl_dump);
     netdev_close(dump->netdev);
+    free(dump->nl_dump);
     free(dump);
+    return 0;
+}
+
+static int
+parse_tc_flow_to_match(struct tc_flow *tc_flow,
+                       struct match *match,
+                       struct nlattr **actions,
+                       struct dpif_flow_stats *stats,
+                       struct ofpbuf *buf) {
+    size_t act_off;
+    struct tc_flow_key *key = &tc_flow->key;
+    struct tc_flow_key *mask = &tc_flow->mask;
+
+    match_init_catchall(match);
+    match_set_dl_type(match, key->eth_type);
+    match_set_dl_src_masked(match, key->src_mac, mask->src_mac);
+    match_set_dl_dst_masked(match, key->dst_mac, mask->dst_mac);
+    if (key->vlan_id || key->vlan_prio) {
+        match_set_dl_vlan(match, ntohs(key->vlan_id));
+        match_set_dl_vlan_pcp(match, ntohs(key->vlan_prio));
+        match_set_dl_type(match, key->encap_eth_type);
+    }
+
+    if (key->ip_proto &&
+        (key->eth_type == htons(ETH_P_IP)
+         || key->eth_type == htons(ETH_P_IPV6))) {
+        match_set_nw_proto(match, key->ip_proto);
+    }
+    match_set_nw_src_masked(match, key->ipv4.ipv4_src, mask->ipv4.ipv4_src);
+    match_set_nw_dst_masked(match, key->ipv4.ipv4_dst, mask->ipv4.ipv4_dst);
+
+    match_set_tp_dst_masked(match, key->dst_port, mask->dst_port);
+    match_set_tp_src_masked(match, key->src_port, mask->src_port);
+
+    if (tc_flow->tunnel.tunnel) {
+        match_set_tun_id(match, tc_flow->tunnel.id);
+        match_set_tun_src(match, tc_flow->tunnel.ipv4_src);
+        match_set_tun_dst(match, tc_flow->tunnel.ipv4_dst);
+        match_set_tp_dst(match, tc_flow->tunnel.tp_dst);
+    }
+
+    act_off = nl_msg_start_nested(buf, OVS_FLOW_ATTR_ACTIONS);
+    {
+        if (tc_flow->vlan_pop)
+            nl_msg_put_flag(buf, OVS_ACTION_ATTR_POP_VLAN);
+
+        if (tc_flow->vlan_push_id || tc_flow->vlan_push_prio) {
+            struct ovs_action_push_vlan *push;
+            push = nl_msg_put_unspec_zero(buf, OVS_ACTION_ATTR_PUSH_VLAN,
+                                          sizeof *push);
+
+            push->vlan_tpid = ntohs(ETH_TYPE_VLAN);
+            push->vlan_tci = ntohs(tc_flow->vlan_push_id
+                                   | (tc_flow->vlan_push_prio << 13)
+                                   | VLAN_CFI);
+        }
+
+        if (tc_flow->ifindex_out > 0) {
+            int ifx = netdev_hmap_port_get_byifidx(tc_flow->ifindex_out);
+
+            nl_msg_put_u32(buf, OVS_ACTION_ATTR_OUTPUT, ifx ? ifx : 0xFF);
+        }
+
+        if (tc_flow->set.set) {
+            size_t set_offset = nl_msg_start_nested(buf, OVS_ACTION_ATTR_SET);
+            size_t tunnel_offset = nl_msg_start_nested(buf, OVS_KEY_ATTR_TUNNEL);
+
+            nl_msg_put_be64(buf, OVS_TUNNEL_KEY_ATTR_ID, tc_flow->set.id);
+            nl_msg_put_be32(buf, OVS_TUNNEL_KEY_ATTR_IPV4_SRC, tc_flow->set.ipv4_src);
+            nl_msg_put_be32(buf, OVS_TUNNEL_KEY_ATTR_IPV4_DST, tc_flow->set.ipv4_dst);
+            nl_msg_put_be16(buf, OVS_TUNNEL_KEY_ATTR_TP_DST, tc_flow->set.tp_dst);
+
+            nl_msg_end_nested(buf, tunnel_offset);
+            nl_msg_end_nested(buf, set_offset);
+        }
+    }
+    nl_msg_end_nested(buf, act_off);
+
+    *actions = ofpbuf_at_assert(buf, act_off, sizeof(struct nlattr));
+
+    if (stats) {
+        memset(stats, 0, sizeof *stats);
+        stats->n_packets = get_32aligned_u64(&tc_flow->stats.n_packets);
+        stats->n_bytes = get_32aligned_u64(&tc_flow->stats.n_bytes);
+        stats->used = tc_flow->lastused;
+    }
 
     return 0;
 }
 
 bool
-netdev_tc_flow_dump_next(struct netdev_flow_dump *dump OVS_UNUSED,
-                            struct match *match OVS_UNUSED,
-                            struct nlattr **actions OVS_UNUSED,
-                            struct dpif_flow_stats *stats OVS_UNUSED,
-                            ovs_u128 *ufid OVS_UNUSED,
-                            struct ofpbuf *rbuffer OVS_UNUSED,
-                            struct ofpbuf *wbuffer OVS_UNUSED)
+netdev_tc_flow_dump_next(struct netdev_flow_dump *dump,
+                            struct match *match,
+                            struct nlattr **actions,
+                            struct dpif_flow_stats *stats,
+                            ovs_u128 *ufid,
+                            struct ofpbuf *rbuffer,
+                            struct ofpbuf *wbuffer)
 {
+    struct ofpbuf nl_flow;
+
+    for (;;) {
+        if (nl_dump_next(dump->nl_dump, &nl_flow, rbuffer)) {
+            struct tc_flow tc_flow;
+            struct flow *mask = &match->wc.masks;
+            ovs_u128 *uf;
+
+            if (parse_tc_flow(&nl_flow, &tc_flow) == EAGAIN) {
+                continue;
+            }
+
+            parse_tc_flow_to_match(&tc_flow, match, actions, stats, wbuffer);
+
+            uf = find_ufid(tc_flow.prio, tc_flow.handle, dump->netdev);
+            if (!uf) {
+                VLOG_DBG("unmatched flow dumped: %d, %d %d, creating ufid",
+                         tc_flow.prio, tc_flow.handle,
+                         netdev_get_ifindex(dump->netdev));
+               dpif_flow_hash(NULL, &match->flow, sizeof match->flow, ufid);
+               add_ufid_tc_mapping(ufid, tc_flow.prio, tc_flow.handle,
+                                   dump->netdev);
+            } else {
+                *ufid = *uf;
+            }
+
+            match_set_in_port(match, dump->port);
+            memset(&mask->in_port, 0xFF, sizeof mask->in_port);
+
+            return true;
+        }
+        break;
+    }
     return false;
 }
 
