Merge "Dump BPF offload rules for upstream IPv4 and IPv6."
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index 2bdddc1..4dc1c51 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -324,20 +324,20 @@
         return true;
     }
 
+    private String mapStatus(BpfMap m, String name) {
+        return name + "{" + (m != null ? "OK" : "ERROR") + "}";
+    }
+
     @Override
     public String toString() {
-        return "mBpfDownstream4Map{"
-                + (mBpfDownstream4Map != null ? "initialized" : "not initialized") + "}, "
-                + "mBpfUpstream4Map{"
-                + (mBpfUpstream4Map != null ? "initialized" : "not initialized") + "}, "
-                + "mBpfUpstream6Map{"
-                + (mBpfUpstream6Map != null ? "initialized" : "not initialized") + "}, "
-                + "mBpfDownstream6Map{"
-                + (mBpfDownstream6Map != null ? "initialized" : "not initialized") + "}, "
-                + "mBpfStatsMap{"
-                + (mBpfStatsMap != null ? "initialized" : "not initialized") + "}, "
-                + "mBpfLimitMap{"
-                + (mBpfLimitMap != null ? "initialized" : "not initialized") + "} ";
+        return String.join(", ", new String[] {
+                mapStatus(mBpfDownstream6Map, "mBpfDownstream6Map"),
+                mapStatus(mBpfUpstream6Map, "mBpfUpstream6Map"),
+                mapStatus(mBpfDownstream4Map, "mBpfDownstream4Map"),
+                mapStatus(mBpfUpstream4Map, "mBpfUpstream4Map"),
+                mapStatus(mBpfStatsMap, "mBpfStatsMap"),
+                mapStatus(mBpfLimitMap, "mBpfLimitMap")
+        });
     }
 
     /**
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 35de400..b17bfcf 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -64,6 +64,7 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -677,6 +678,12 @@
         }
     }
 
+
+    // TODO: make mInterfaceNames accessible to the shim and move this code to there.
+    private String getIfName(long ifindex) {
+        return mInterfaceNames.get((int) ifindex, Long.toString(ifindex));
+    }
+
     /**
      * Dump information.
      * Block the function until all the data are dumped on the handler thread or timed-out. The
@@ -705,11 +712,9 @@
 
             pw.println("Forwarding rules:");
             pw.increaseIndent();
-            if (mIpv6ForwardingRules.size() == 0) {
-                pw.println("<empty>");
-            } else {
-                dumpIpv6ForwardingRules(pw);
-            }
+            dumpIpv6UpstreamRules(pw);
+            dumpIpv6ForwardingRules(pw);
+            dumpIpv4ForwardingRules(pw);
             pw.decreaseIndent();
 
             dumpDone.open();
@@ -729,6 +734,11 @@
     }
 
     private void dumpIpv6ForwardingRules(@NonNull IndentingPrintWriter pw) {
+        if (mIpv6ForwardingRules.size() == 0) {
+            pw.println("No IPv6 rules");
+            return;
+        }
+
         for (Map.Entry<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> entry :
                 mIpv6ForwardingRules.entrySet()) {
             IpServer ipServer = entry.getKey();
@@ -749,6 +759,63 @@
         }
     }
 
+    private String ipv6UpstreamRuletoString(TetherUpstream6Key key, Tether6Value value) {
+        return String.format("%d(%s) -> %d(%s) %04x %s %s",
+                key.iif, getIfName(key.iif), value.oif, getIfName(value.oif),
+                value.ethProto, value.ethSrcMac, value.ethDstMac);
+    }
+
+    private void dumpIpv6UpstreamRules(IndentingPrintWriter pw) {
+        final BpfMap<TetherUpstream6Key, Tether6Value> ipv6UpstreamMap = mDeps.getBpfUpstream6Map();
+        if (ipv6UpstreamMap == null) {
+            pw.println("No IPv6 upstream");
+            return;
+        }
+        try {
+            if (ipv6UpstreamMap.isEmpty()) {
+                pw.println("No IPv6 upstream rules");
+                return;
+            }
+            ipv6UpstreamMap.forEach((k, v) -> pw.println(ipv6UpstreamRuletoString(k, v)));
+        } catch (ErrnoException e) {
+            pw.println("Error dumping IPv4 map: " + e);
+        }
+    }
+
+    private String ipv4RuleToString(Tether4Key key, Tether4Value value) {
+        final String private4, public4, dst4;
+        try {
+            private4 = InetAddress.getByAddress(key.src4).getHostAddress();
+            dst4 = InetAddress.getByAddress(key.dst4).getHostAddress();
+            public4 = InetAddress.getByAddress(value.src46).getHostAddress();
+        } catch (UnknownHostException impossible) {
+            throw new AssertionError("4-byte array not valid IPv4 address!");
+        }
+        return String.format("%d(%s) %d(%s) %s:%d -> %s:%d -> %s:%d",
+                key.iif, getIfName(key.iif), value.oif, getIfName(value.oif),
+                private4, key.srcPort, public4, value.srcPort, dst4, key.dstPort);
+    }
+
+    private void dumpIpv4ForwardingRules(IndentingPrintWriter pw) {
+        final BpfMap<Tether4Key, Tether4Value> ipv4UpstreamMap = mDeps.getBpfUpstream4Map();
+        if (ipv4UpstreamMap == null) {
+            pw.println("No IPv4 support");
+            return;
+        }
+        try {
+            if (ipv4UpstreamMap.isEmpty()) {
+                pw.println("No IPv4 rules");
+                return;
+            }
+            pw.println("[IPv4]: iif(iface) oif(iface) src nat dst");
+            pw.increaseIndent();
+            ipv4UpstreamMap.forEach((k, v) -> pw.println(ipv4RuleToString(k, v)));
+        } catch (ErrnoException e) {
+            pw.println("Error dumping IPv4 map: " + e);
+        }
+        pw.decreaseIndent();
+    }
+
     /** IPv6 forwarding rule class. */
     public static class Ipv6ForwardingRule {
         public final int upstreamIfindex;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 2354c2d..385c691 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -2225,6 +2225,13 @@
                 && !isProvisioningNeededButUnavailable();
     }
 
+    private void dumpBpf(IndentingPrintWriter pw) {
+        pw.println("BPF offload:");
+        pw.increaseIndent();
+        mBpfCoordinator.dump(pw);
+        pw.decreaseIndent();
+    }
+
     void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
         // Binder.java closes the resource for us.
         @SuppressWarnings("resource")
@@ -2235,6 +2242,11 @@
             return;
         }
 
+        if (argsContain(args, "bpf")) {
+            dumpBpf(pw);
+            return;
+        }
+
         pw.println("Tethering:");
         pw.increaseIndent();
 
@@ -2286,10 +2298,7 @@
         mOffloadController.dump(pw);
         pw.decreaseIndent();
 
-        pw.println("BPF offload:");
-        pw.increaseIndent();
-        mBpfCoordinator.dump(pw);
-        pw.decreaseIndent();
+        dumpBpf(pw);
 
         pw.println("Private address coordinator:");
         pw.increaseIndent();