BpfCoordinator: dump IPv6 BPF downstream rules

Test: adb shell dumpsys tethering
Forwarding rules:
  IPv6 Upstream: iif(iface) [inDstMac] -> oif(iface) etherType [outSrcMac] [outDstMac]
    54(54) [7a:2e:e8:f1:e3:54] -> 15(rmnet1) 86dd [00:00:00:00:00:00] [00:00:00:00:00:00]
  IPv6 Downstream: iif(iface) [inDstMac] neigh6 -> oif(iface) etherType [outSrcMac] [outDstMac]
    15(rmnet1) [00:00:00:00:00:00] 2401:e180:88e1:7aef:55fb:14d3:7ded:71cd -> 54(54) 86dd [7a:2e:e8:f1:e3:54] [52:5c:cf:fc:c6:ff]

Change-Id: I95352711fad48e5e7779e8bc2c27aa415d98758f
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index ce0d6db..ac0bbd4 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -1112,6 +1112,36 @@
         }
     }
 
+    private String ipv6DownstreamRuleToString(TetherDownstream6Key key, Tether6Value value) {
+        final String neigh6;
+        try {
+            neigh6 = InetAddress.getByAddress(key.neigh6).getHostAddress();
+        } catch (UnknownHostException impossible) {
+            throw new AssertionError("IP address array not valid IPv6 address!");
+        }
+        return String.format("%d(%s) [%s] %s -> %d(%s) %04x [%s] [%s]",
+                key.iif, getIfName(key.iif), key.dstMac, neigh6, value.oif, getIfName(value.oif),
+                value.ethProto, value.ethSrcMac, value.ethDstMac);
+    }
+
+    private void dumpIpv6DownstreamRules(IndentingPrintWriter pw) {
+        try (BpfMap<TetherDownstream6Key, Tether6Value> map = mDeps.getBpfDownstream6Map()) {
+            if (map == null) {
+                pw.println("No IPv6 downstream");
+                return;
+            }
+            if (map.isEmpty()) {
+                pw.println("No IPv6 downstream rules");
+                return;
+            }
+            map.forEach((k, v) -> pw.println(ipv6DownstreamRuleToString(k, v)));
+        } catch (ErrnoException | IOException e) {
+            pw.println("Error dumping IPv6 downstream map: " + e);
+        }
+    }
+
+    // TODO: use dump utils with headerline and lambda which prints key and value to reduce
+    // duplicate bpf map dump code.
     private void dumpBpfForwardingRulesIpv6(IndentingPrintWriter pw) {
         pw.println("IPv6 Upstream: iif(iface) [inDstMac] -> oif(iface) etherType [outSrcMac] "
                 + "[outDstMac]");
@@ -1119,7 +1149,11 @@
         dumpIpv6UpstreamRules(pw);
         pw.decreaseIndent();
 
-        // TODO: dump downstream map.
+        pw.println("IPv6 Downstream: iif(iface) [inDstMac] neigh6 -> oif(iface) etherType "
+                + "[outSrcMac] [outDstMac]");
+        pw.increaseIndent();
+        dumpIpv6DownstreamRules(pw);
+        pw.decreaseIndent();
     }
 
     private <K extends Struct, V extends Struct> void dumpRawMap(BpfMap<K, V> map,