Merge "[Feature sync] Fix flaky test in MdnsSocketClientTests"
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index ebc9d26..6a5089d 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -403,6 +403,18 @@
                 return null;
             }
         }
+
+        /** Get error BPF map. */
+        @Nullable public IBpfMap<S32, S32> getBpfErrorMap() {
+            if (!isAtLeastS()) return null;
+            try {
+                return new BpfMap<>(TETHER_ERROR_MAP_PATH,
+                    BpfMap.BPF_F_RDONLY, S32.class, S32.class);
+            } catch (ErrnoException e) {
+                Log.e(TAG, "Cannot create error map: " + e);
+                return null;
+            }
+        }
     }
 
     @VisibleForTesting
@@ -1287,13 +1299,15 @@
     }
 
     private void dumpCounters(@NonNull IndentingPrintWriter pw) {
-        if (!mDeps.isAtLeastS()) {
-            pw.println("No counter support");
-            return;
-        }
-        try (IBpfMap<S32, S32> map = new BpfMap<>(TETHER_ERROR_MAP_PATH, BpfMap.BPF_F_RDONLY,
-                S32.class, S32.class)) {
-
+        try (IBpfMap<S32, S32> map = mDeps.getBpfErrorMap()) {
+            if (map == null) {
+                pw.println("No error counter support");
+                return;
+            }
+            if (map.isEmpty()) {
+                pw.println("<empty>");
+                return;
+            }
             map.forEach((k, v) -> {
                 String counterName;
                 try {
@@ -1307,7 +1321,7 @@
                 if (v.val > 0) pw.println(String.format("%s: %d", counterName, v.val));
             });
         } catch (ErrnoException | IOException e) {
-            pw.println("Error dumping counter map: " + e);
+            pw.println("Error dumping error counter map: " + e);
         }
     }
 
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index b7eb92c..f0d9057 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -100,6 +100,7 @@
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.NetworkStackConstants;
 import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.Struct.S32;
 import com.android.net.module.util.bpf.Tether4Key;
 import com.android.net.module.util.bpf.Tether4Value;
 import com.android.net.module.util.bpf.TetherStatsKey;
@@ -199,6 +200,7 @@
     @Mock private BpfMap<TetherStatsKey, TetherStatsValue> mBpfStatsMap;
     @Mock private BpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap;
     @Mock private BpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
+    @Mock private BpfMap<S32, S32> mBpfErrorMap;
 
     @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
 
@@ -362,6 +364,11 @@
                     public BpfMap<TetherDevKey, TetherDevValue> getBpfDevMap() {
                         return mBpfDevMap;
                     }
+
+                    @Nullable
+                    public BpfMap<S32, S32> getBpfErrorMap() {
+                        return mBpfErrorMap;
+                    }
                 };
         mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps));
 
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index bbca565..0bd6380 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -94,11 +94,13 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.IBpfMap;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.NetworkStackConstants;
 import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.Struct.S32;
 import com.android.net.module.util.bpf.Tether4Key;
 import com.android.net.module.util.bpf.Tether4Value;
 import com.android.net.module.util.bpf.TetherStatsKey;
@@ -128,6 +130,7 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 
+import java.io.StringWriter;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -362,9 +365,6 @@
     @Mock private IpServer mIpServer2;
     @Mock private TetheringConfiguration mTetherConfig;
     @Mock private ConntrackMonitor mConntrackMonitor;
-    @Mock private IBpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
-    @Mock private IBpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
-    @Mock private IBpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
 
     // Late init since methods must be called by the thread that created this object.
     private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb;
@@ -383,10 +383,18 @@
             spy(new TestBpfMap<>(Tether4Key.class, Tether4Value.class));
     private final IBpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map =
             spy(new TestBpfMap<>(Tether4Key.class, Tether4Value.class));
+    private final IBpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map =
+            spy(new TestBpfMap<>(TetherDownstream6Key.class, Tether6Value.class));
+    private final IBpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map =
+            spy(new TestBpfMap<>(TetherUpstream6Key.class, Tether6Value.class));
     private final IBpfMap<TetherStatsKey, TetherStatsValue> mBpfStatsMap =
             spy(new TestBpfMap<>(TetherStatsKey.class, TetherStatsValue.class));
     private final IBpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap =
             spy(new TestBpfMap<>(TetherLimitKey.class, TetherLimitValue.class));
+    private final IBpfMap<TetherDevKey, TetherDevValue> mBpfDevMap =
+            spy(new TestBpfMap<>(TetherDevKey.class, TetherDevValue.class));
+    private final IBpfMap<S32, S32> mBpfErrorMap =
+            spy(new TestBpfMap<>(S32.class, S32.class));
     private BpfCoordinator.Dependencies mDeps =
             spy(new BpfCoordinator.Dependencies() {
                     @NonNull
@@ -457,6 +465,11 @@
                     public IBpfMap<TetherDevKey, TetherDevValue> getBpfDevMap() {
                         return mBpfDevMap;
                     }
+
+                    @Nullable
+                    public IBpfMap<S32, S32> getBpfErrorMap() {
+                        return mBpfErrorMap;
+                    }
             });
 
     @Before public void setUp() {
@@ -2099,4 +2112,88 @@
         assertEquals("upstreamIfindex: 1001, downstreamIfindex: 1003, address: 2001:db8::1, "
                 + "srcMac: 12:34:56:78:90:ab, dstMac: 00:00:00:00:00:0a", rule.toString());
     }
+
+    private void verifyDump(@NonNull final BpfCoordinator coordinator) {
+        final StringWriter stringWriter = new StringWriter();
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(stringWriter, " ");
+        coordinator.dump(ipw);
+        assertFalse(stringWriter.toString().isEmpty());
+    }
+
+    @Test
+    public void testDumpDoesNotCrash() throws Exception {
+        // This dump test only used to for improving mainline module test coverage and doesn't
+        // really do any meaningful tests.
+        // TODO: consider verifying the dump content and separate tests into testDumpXXX().
+        final BpfCoordinator coordinator = makeBpfCoordinator();
+
+        // [1] Dump mostly empty content.
+        verifyDump(coordinator);
+
+        // [2] Dump mostly non-empty content.
+        // Test the following dump function and fill the corresponding content to execute
+        // code as more as possible for test coverage.
+        // - dumpBpfForwardingRulesIpv4
+        //   * mBpfDownstream4Map
+        //   * mBpfUpstream4Map
+        // - dumpBpfForwardingRulesIpv6
+        //   * mBpfDownstream6Map
+        //   * mBpfUpstream6Map
+        // - dumpStats
+        //   * mBpfStatsMap
+        // - dumpDevmap
+        //   * mBpfDevMap
+        // - dumpCounters
+        //   * mBpfErrorMap
+        // - dumpIpv6ForwardingRulesByDownstream
+        //   * mIpv6ForwardingRules
+
+        // dumpBpfForwardingRulesIpv4
+        mBpfDownstream4Map.insertEntry(
+                new TestDownstream4Key.Builder().build(),
+                new TestDownstream4Value.Builder().build());
+        mBpfUpstream4Map.insertEntry(
+                new TestUpstream4Key.Builder().build(),
+                new TestUpstream4Value.Builder().build());
+
+        // dumpBpfForwardingRulesIpv6
+        final Ipv6ForwardingRule rule = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A);
+        mBpfDownstream6Map.insertEntry(rule.makeTetherDownstream6Key(), rule.makeTether6Value());
+
+        final TetherUpstream6Key upstream6Key = new TetherUpstream6Key(DOWNSTREAM_IFINDEX,
+                DOWNSTREAM_MAC);
+        final Tether6Value upstream6Value = new Tether6Value(UPSTREAM_IFINDEX,
+                MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS,
+                ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
+        mBpfUpstream6Map.insertEntry(upstream6Key, upstream6Value);
+
+        // dumpStats
+        mBpfStatsMap.insertEntry(
+                new TetherStatsKey(UPSTREAM_IFINDEX),
+                new TetherStatsValue(
+                        0L /* rxPackets */, 0L /* rxBytes */, 0L /* rxErrors */,
+                        0L /* txPackets */, 0L /* txBytes */, 0L /* txErrors */));
+
+        // dumpDevmap
+        coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
+        mBpfDevMap.insertEntry(
+                new TetherDevKey(UPSTREAM_IFINDEX),
+                new TetherDevValue(UPSTREAM_IFINDEX));
+
+        // dumpCounters
+        // The error code is defined in packages/modules/Connectivity/bpf_progs/bpf_tethering.h.
+        mBpfErrorMap.insertEntry(
+                new S32(0 /* INVALID_IPV4_VERSION */),
+                new S32(1000 /* count */));
+
+        // dumpIpv6ForwardingRulesByDownstream
+        final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>>
+                ipv6ForwardingRules = coordinator.getForwardingRulesForTesting();
+        final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> addressRuleMap =
+                new LinkedHashMap<>();
+        addressRuleMap.put(rule.address, rule);
+        ipv6ForwardingRules.put(mIpServer, addressRuleMap);
+
+        verifyDump(coordinator);
+    }
 }
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index e382713..c39269e 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -28,9 +28,12 @@
 
 static int (*bpf_skb_pull_data)(struct __sk_buff* skb, __u32 len) = (void*)BPF_FUNC_skb_pull_data;
 
-static int (*bpf_skb_load_bytes)(struct __sk_buff* skb, int off, void* to,
+static int (*bpf_skb_load_bytes)(const struct __sk_buff* skb, int off, void* to,
                                  int len) = (void*)BPF_FUNC_skb_load_bytes;
 
+static int (*bpf_skb_load_bytes_relative)(const struct __sk_buff* skb, int off, void* to, int len,
+                                          int start_hdr) = (void*)BPF_FUNC_skb_load_bytes_relative;
+
 static int (*bpf_skb_store_bytes)(struct __sk_buff* skb, __u32 offset, const void* from, __u32 len,
                                   __u64 flags) = (void*)BPF_FUNC_skb_store_bytes;
 
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 10559dd..f9484fc 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -47,9 +47,16 @@
 
 #define IP_PROTO_OFF offsetof(struct iphdr, protocol)
 #define IPV6_PROTO_OFF offsetof(struct ipv6hdr, nexthdr)
+
+// offsetof(struct iphdr, ihl) -- but that's a bitfield
 #define IPPROTO_IHL_OFF 0
-#define TCP_FLAG_OFF 13
-#define RST_OFFSET 2
+
+// This is offsetof(struct tcphdr, "32 bit tcp flag field")
+// The tcp flags are after be16 source, dest & be32 seq, ack_seq, hence 12 bytes in.
+//
+// Note that TCP_FLAG_{ACK,PSH,RST,SYN,FIN} are htonl(0x00{10,08,04,02,01}0000)
+// see include/uapi/linux/tcp.h
+#define TCP_FLAG32_OFF 12
 
 // For maps netd does not need to access
 #define DEFINE_BPF_MAP_NO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
@@ -97,9 +104,15 @@
 // programs that need to be usable by netd, but not by netutils_wrappers
 // (this is because these are currently attached by the mainline provided libnetd_updatable .so
 // which is loaded into netd and thus runs as netd uid/gid/selinux context)
-#define DEFINE_NETD_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
+#define DEFINE_NETD_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, minKV, maxKV) \
     DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, \
-                        KVER_NONE, KVER_INF, false, "fs_bpf_netd_readonly", "")
+                        minKV, maxKV, false, "fs_bpf_netd_readonly", "")
+
+#define DEFINE_NETD_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv) \
+    DEFINE_NETD_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF)
+
+#define DEFINE_NETD_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
+    DEFINE_NETD_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, KVER_NONE)
 
 // programs that only need to be usable by the system server
 #define DEFINE_SYS_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
@@ -176,46 +189,49 @@
 DEFINE_UPDATE_STATS(stats_map_A, StatsKey)
 DEFINE_UPDATE_STATS(stats_map_B, StatsKey)
 
-static inline bool skip_owner_match(struct __sk_buff* skb) {
-    int offset = -1;
-    int ret = 0;
+// both of these return 0 on success or -EFAULT on failure (and zero out the buffer)
+static __always_inline inline int bpf_skb_load_bytes_net(const struct __sk_buff* skb, int off,
+                                                         void* to, int len, bool is_4_19) {
+    return is_4_19
+        ? bpf_skb_load_bytes_relative(skb, off, to, len, BPF_HDR_START_NET)
+        : bpf_skb_load_bytes(skb, off, to, len);
+}
+
+static __always_inline inline bool skip_owner_match(struct __sk_buff* skb, bool is_4_19) {
     if (skb->protocol == htons(ETH_P_IP)) {
-        offset = IP_PROTO_OFF;
-        uint8_t proto, ihl;
-        uint8_t flag;
-        ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
-        if (!ret) {
-            if (proto == IPPROTO_ESP) {
-                return true;
-            } else if (proto == IPPROTO_TCP) {
-                ret = bpf_skb_load_bytes(skb, IPPROTO_IHL_OFF, &ihl, 1);
-                ihl = ihl & 0x0F;
-                ret = bpf_skb_load_bytes(skb, ihl * 4 + TCP_FLAG_OFF, &flag, 1);
-                if (ret == 0 && (flag >> RST_OFFSET & 1)) {
-                    return true;
-                }
-            }
-        }
-    } else if (skb->protocol == htons(ETH_P_IPV6)) {
-        offset = IPV6_PROTO_OFF;
         uint8_t proto;
-        ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
-        if (!ret) {
-            if (proto == IPPROTO_ESP) {
-                return true;
-            } else if (proto == IPPROTO_TCP) {
-                uint8_t flag;
-                ret = bpf_skb_load_bytes(skb, sizeof(struct ipv6hdr) + TCP_FLAG_OFF, &flag, 1);
-                if (ret == 0 && (flag >> RST_OFFSET & 1)) {
-                    return true;
-                }
-            }
-        }
+        // no need to check for success, proto will be zeroed if bpf_skb_load_bytes_net() fails
+        (void)bpf_skb_load_bytes_net(skb, IP_PROTO_OFF, &proto, sizeof(proto), is_4_19);
+        if (proto == IPPROTO_ESP) return true;
+        if (proto != IPPROTO_TCP) return false;  // handles read failure above
+        uint8_t ihl;
+        // we don't check for success, as this cannot fail, as it is earlier in the packet than
+        // proto, the reading of which must have succeeded, additionally the next read
+        // (a little bit deeper in the packet in spite of ihl being zeroed) of the tcp flags
+        // field will also fail, and that failure we already handle correctly
+        // (we also don't check that ihl in [0x45,0x4F] nor that ipv4 header checksum is correct)
+        (void)bpf_skb_load_bytes_net(skb, IPPROTO_IHL_OFF, &ihl, sizeof(ihl), is_4_19);
+        uint32_t flag;
+        // if the read below fails, we'll just assume no TCP flags are set, which is fine.
+        (void)bpf_skb_load_bytes_net(skb, (ihl & 0xF) * 4 + TCP_FLAG32_OFF,
+                                     &flag, sizeof(flag), is_4_19);
+        return flag & TCP_FLAG_RST;  // false on read failure
+    } else if (skb->protocol == htons(ETH_P_IPV6)) {
+        uint8_t proto;
+        // no need to check for success, proto will be zeroed if bpf_skb_load_bytes_net() fails
+        (void)bpf_skb_load_bytes_net(skb, IPV6_PROTO_OFF, &proto, sizeof(proto), is_4_19);
+        if (proto == IPPROTO_ESP) return true;
+        if (proto != IPPROTO_TCP) return false;  // handles read failure above
+        uint32_t flag;
+        // if the read below fails, we'll just assume no TCP flags are set, which is fine.
+        (void)bpf_skb_load_bytes_net(skb, sizeof(struct ipv6hdr) + TCP_FLAG32_OFF,
+                                     &flag, sizeof(flag), is_4_19);
+        return flag & TCP_FLAG_RST;  // false on read failure
     }
     return false;
 }
 
-static __always_inline BpfConfig getConfig(uint32_t configKey) {
+static __always_inline inline BpfConfig getConfig(uint32_t configKey) {
     uint32_t mapSettingKey = configKey;
     BpfConfig* config = bpf_configuration_map_lookup_elem(&mapSettingKey);
     if (!config) {
@@ -230,8 +246,9 @@
 // DROP_IF_UNSET is set of rules that should DROP if globally enabled, and per-uid bit is NOT set
 #define DROP_IF_UNSET (DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH | LOW_POWER_STANDBY_MATCH)
 
-static inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid, int direction) {
-    if (skip_owner_match(skb)) return BPF_PASS;
+static __always_inline inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid,
+                                                  int direction, bool is_4_19) {
+    if (skip_owner_match(skb, is_4_19)) return BPF_PASS;
 
     if (is_system_uid(uid)) return BPF_PASS;
 
@@ -273,7 +290,8 @@
     }
 }
 
-static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, int direction) {
+static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, int direction,
+                                                      bool is_4_19) {
     uint32_t sock_uid = bpf_get_socket_uid(skb);
     uint64_t cookie = bpf_get_socket_cookie(skb);
     UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie);
@@ -293,7 +311,7 @@
         return BPF_PASS;
     }
 
-    int match = bpf_owner_match(skb, sock_uid, direction);
+    int match = bpf_owner_match(skb, sock_uid, direction, is_4_19);
     if ((direction == BPF_EGRESS) && (match == BPF_DROP)) {
         // If an outbound packet is going to be dropped, we do not count that
         // traffic.
@@ -338,14 +356,28 @@
     return match;
 }
 
-DEFINE_NETD_BPF_PROG("cgroupskb/ingress/stats", AID_ROOT, AID_SYSTEM, bpf_cgroup_ingress)
+DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_19", AID_ROOT, AID_SYSTEM,
+                                bpf_cgroup_ingress_4_19, KVER(4, 19, 0), KVER_INF)
 (struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, BPF_INGRESS);
+    return bpf_traffic_account(skb, BPF_INGRESS, /* is_4_19 */ true);
 }
 
-DEFINE_NETD_BPF_PROG("cgroupskb/egress/stats", AID_ROOT, AID_SYSTEM, bpf_cgroup_egress)
+DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_14", AID_ROOT, AID_SYSTEM,
+                                bpf_cgroup_ingress_4_14, KVER_NONE, KVER(4, 19, 0))
 (struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, BPF_EGRESS);
+    return bpf_traffic_account(skb, BPF_INGRESS, /* is_4_19 */ false);
+}
+
+DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_19", AID_ROOT, AID_SYSTEM,
+                                bpf_cgroup_egress_4_19, KVER(4, 19, 0), KVER_INF)
+(struct __sk_buff* skb) {
+    return bpf_traffic_account(skb, BPF_EGRESS, /* is_4_19 */ true);
+}
+
+DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_14", AID_ROOT, AID_SYSTEM,
+                                bpf_cgroup_egress_4_14, KVER_NONE, KVER(4, 19, 0))
+(struct __sk_buff* skb) {
+    return bpf_traffic_account(skb, BPF_EGRESS, /* is_4_19 */ false);
 }
 
 // WARNING: Android T's non-updatable netd depends on the name of this program.
@@ -419,7 +451,8 @@
     return BPF_NOMATCH;
 }
 
-DEFINE_NETD_BPF_PROG("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create)
+DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
+                          KVER(4, 14, 0))
 (struct bpf_sock* sk) {
     uint64_t gid_uid = bpf_get_current_uid_gid();
     /*
diff --git a/nearby/halfsheet/Android.bp b/nearby/halfsheet/Android.bp
index 486a3ff..c84caa6 100644
--- a/nearby/halfsheet/Android.bp
+++ b/nearby/halfsheet/Android.bp
@@ -23,7 +23,6 @@
     sdk_version: "module_current",
     // This is included in tethering apex, which uses min SDK 30
     min_sdk_version: "30",
-    target_sdk_version: "current",
     updatable: true,
     certificate: ":com.android.nearby.halfsheetcertificate",
     libs: [
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 3f7ed2a..7950ff7 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -87,7 +87,16 @@
     RETURN_IF_NOT_OK(checkProgramAccessible(XT_BPF_INGRESS_PROG_PATH));
     RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_EGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_EGRESS));
     RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_INGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_INGRESS));
-    RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+
+    // For the devices that support cgroup socket filter, the socket filter
+    // should be loaded successfully by bpfloader. So we attach the filter to
+    // cgroup if the program is pinned properly.
+    // TODO: delete the if statement once all devices should support cgroup
+    // socket filter (ie. the minimum kernel version required is 4.14).
+    if (!access(CGROUP_SOCKET_PROG_PATH, F_OK)) {
+        RETURN_IF_NOT_OK(
+                attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+    }
     return netdutils::status::ok;
 }
 
diff --git a/service/ServiceConnectivityResources/Android.bp b/service/ServiceConnectivityResources/Android.bp
index 02b2875..2260596 100644
--- a/service/ServiceConnectivityResources/Android.bp
+++ b/service/ServiceConnectivityResources/Android.bp
@@ -21,9 +21,8 @@
 
 android_app {
     name: "ServiceConnectivityResources",
-    sdk_version: "module_30",
+    sdk_version: "module_current",
     min_sdk_version: "30",
-    target_sdk_version: "33",
     resource_dirs: [
         "res",
     ],
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 195f046..6ef239d 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -7500,7 +7500,7 @@
     }
 
     private void updateLinkProperties(NetworkAgentInfo networkAgent, @NonNull LinkProperties newLp,
-            @NonNull LinkProperties oldLp) {
+            @Nullable LinkProperties oldLp) {
         int netId = networkAgent.network.getNetId();
 
         // The NetworkAgent does not know whether clatd is running on its network or not, or whether
@@ -7552,7 +7552,13 @@
             }
             // Start or stop DNS64 detection and 464xlat according to network state.
             networkAgent.clatd.update();
-            notifyIfacesChangedForNetworkStats();
+            // Notify NSS when relevant events happened. Currently, NSS only cares about
+            // interface changed to update clat interfaces accounting.
+            final boolean interfacesChanged = oldLp == null
+                    || !Objects.equals(newLp.getAllInterfaceNames(), oldLp.getAllInterfaceNames());
+            if (interfacesChanged) {
+                notifyIfacesChangedForNetworkStats();
+            }
             networkAgent.networkMonitor().notifyLinkPropertiesChanged(
                     new LinkProperties(newLp, true /* parcelSensitiveFields */));
             notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
@@ -8315,7 +8321,8 @@
         }
     }
 
-    public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) {
+    public void handleUpdateLinkProperties(@NonNull NetworkAgentInfo nai,
+            @NonNull LinkProperties newLp) {
         ensureRunningOnConnectivityServiceThread();
 
         if (!mNetworkAgentInfos.contains(nai)) {
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index 4e19781..2ac2ad3 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -22,6 +22,7 @@
 import static com.android.net.module.util.CollectionUtils.contains;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.ConnectivityManager;
 import android.net.IDnsResolver;
 import android.net.INetd;
@@ -420,7 +421,7 @@
      * This is necessary because the LinkProperties in mNetwork come from the transport layer, which
      * has no idea that 464xlat is running on top of it.
      */
-    public void fixupLinkProperties(@NonNull LinkProperties oldLp, @NonNull LinkProperties lp) {
+    public void fixupLinkProperties(@Nullable LinkProperties oldLp, @NonNull LinkProperties lp) {
         // This must be done even if clatd is not running, because otherwise shouldStartClat would
         // never return true.
         lp.setNat64Prefix(selectNat64Prefix());
@@ -433,6 +434,8 @@
         }
 
         Log.d(TAG, "clatd running, updating NAI for " + mIface);
+        // oldLp can't be null here since shouldStartClat checks null LinkProperties to start clat.
+        // Thus, the status won't pass isRunning check if the oldLp is null.
         for (LinkProperties stacked: oldLp.getStackedLinks()) {
             if (Objects.equals(mIface, stacked.getInterfaceName())) {
                 lp.addStackedLink(stacked);
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index bb07a98..aa5654a 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -105,7 +105,6 @@
     SHARED "prog_clatd_schedcls_ingress6_clat_rawip",
     NETD "prog_netd_cgroupskb_egress_stats",
     NETD "prog_netd_cgroupskb_ingress_stats",
-    NETD "prog_netd_cgroupsock_inet_create",
     NETD "prog_netd_schedact_ingress_account",
     NETD "prog_netd_skfilter_allowlist_xtbpf",
     NETD "prog_netd_skfilter_denylist_xtbpf",
@@ -113,6 +112,11 @@
     NETD "prog_netd_skfilter_ingress_xtbpf",
 };
 
+// Provided by *current* mainline module for T+ devices with 4.14+ kernels
+static const set<string> MAINLINE_FOR_T_4_14_PLUS = {
+    NETD "prog_netd_cgroupsock_inet_create",
+};
+
 // Provided by *current* mainline module for T+ devices with 5.4+ kernels
 static const set<string> MAINLINE_FOR_T_5_4_PLUS = {
     SHARED "prog_block_bind4_block_port",
@@ -151,6 +155,7 @@
     // Nothing added or removed in SCv2.
 
     DO_EXPECT(IsAtLeastT(), MAINLINE_FOR_T_PLUS);
+    DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(4, 14, 0), MAINLINE_FOR_T_4_14_PLUS);
     DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 4, 0), MAINLINE_FOR_T_5_4_PLUS);
     DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 15, 0), MAINLINE_FOR_T_5_15_PLUS);
 }
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 2d8cf80..65e0b10 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -7236,6 +7236,15 @@
         expectNotifyNetworkStatus(onlyCell(), onlyCell(), MOBILE_IFNAME);
         reset(mStatsManager);
 
+        // Verify change fields other than interfaces does not trigger a notification to NSS.
+        cellLp.addLinkAddress(new LinkAddress("192.0.2.4/24"));
+        cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("192.0.2.4"),
+                MOBILE_IFNAME));
+        cellLp.setDnsServers(List.of(InetAddress.getAllByName("8.8.8.8")));
+        mCellNetworkAgent.sendLinkProperties(cellLp);
+        verifyNoMoreInteractions(mStatsManager);
+        reset(mStatsManager);
+
         // Default network switch should update ifaces.
         mWiFiNetworkAgent.connect(false);
         mWiFiNetworkAgent.sendLinkProperties(wifiLp);
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index 49e3514..b651c33 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -222,7 +222,7 @@
         public String generateIpv6Address(@NonNull String iface, @NonNull String v4,
                 @NonNull String prefix64, int mark) throws IOException {
             if (BASE_IFACE.equals(iface) && XLAT_LOCAL_IPV4ADDR_STRING.equals(v4)
-                    && NAT64_PREFIX_STRING.equals(prefix64)) {
+                    && NAT64_PREFIX_STRING.equals(prefix64) && MARK == mark) {
                 return XLAT_LOCAL_IPV6ADDR_STRING;
             }
             fail("unsupported args: " + iface + ", " + v4 + ", " + prefix64 + ", " + mark);
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 39fd780..ff771f6 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -93,6 +93,7 @@
 import android.net.LinkProperties;
 import android.net.LocalSocket;
 import android.net.Network;
+import android.net.NetworkAgent;
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo.DetailedState;
@@ -1744,7 +1745,7 @@
             throws Exception {
         doReturn(mMockNetworkAgent).when(mTestDeps)
                 .newNetworkAgent(
-                        any(), any(), anyString(), any(), any(), any(), any(), any());
+                        any(), any(), anyString(), any(), any(), any(), any(), any(), any());
 
         final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
         when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
@@ -1774,7 +1775,7 @@
                 ArgumentCaptor.forClass(NetworkAgentConfig.class);
         verify(mTestDeps).newNetworkAgent(
                 any(), any(), anyString(), ncCaptor.capture(), lpCaptor.capture(),
-                any(), nacCaptor.capture(), any());
+                any(), nacCaptor.capture(), any(), any());
 
         // Check LinkProperties
         final LinkProperties lp = lpCaptor.getValue();
@@ -1968,7 +1969,7 @@
     }
 
     @Test
-    public void testDataStallInIkev2VpnMobikeEnabled() throws Exception {
+    public void testDataStallInIkev2VpnRecoveredByMobike() throws Exception {
         final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
                 createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
 
@@ -1980,6 +1981,64 @@
 
         // Verify MOBIKE is triggered
         verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
+
+        // Expect to skip other data stall event if MOBIKE was started.
+        reset(mIkeSessionWrapper);
+        connectivityDiagCallback.onDataStallSuspected(report);
+        verify(mIkeSessionWrapper, never()).setNetwork(any());
+
+        reset(mIkev2SessionCreator);
+
+        // Send validation status update.
+        // Recovered and get network validated. It should not trigger the ike session reset.
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_VALID);
+        verify(mIkev2SessionCreator, never()).createIkeSession(
+                any(), any(), any(), any(), any(), any());
+
+        // Send invalid result to verify no ike session reset since the data stall suspected
+        // variables(timer counter and boolean) was reset.
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mExecutor).schedule(runnableCaptor.capture(), anyLong(), any());
+        runnableCaptor.getValue().run();
+        verify(mIkev2SessionCreator, never()).createIkeSession(
+                any(), any(), any(), any(), any(), any());
+    }
+
+    @Test
+    public void testDataStallInIkev2VpnNotRecoveredByMobike() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
+
+        final ConnectivityDiagnosticsCallback connectivityDiagCallback =
+                getConnectivityDiagCallback();
+
+        doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork();
+        final DataStallReport report = createDataStallReport();
+        connectivityDiagCallback.onDataStallSuspected(report);
+
+        verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
+
+        reset(mIkev2SessionCreator);
+
+        // Send validation status update should result in ike session reset.
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+
+        // Verify reset is scheduled and run.
+        final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mExecutor).schedule(runnableCaptor.capture(), anyLong(), any());
+
+        // Another invalid status reported should not trigger other scheduled recovery.
+        reset(mExecutor);
+        ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+                NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+        verify(mExecutor, never()).schedule(runnableCaptor.capture(), anyLong(), any());
+
+        runnableCaptor.getValue().run();
+        verify(mIkev2SessionCreator).createIkeSession(any(), any(), any(), any(), any(), any());
     }
 
     @Test
diff --git a/tools/gn2bp/Android.bp.swp b/tools/gn2bp/Android.bp.swp
new file mode 100644
index 0000000..9a394e7
--- /dev/null
+++ b/tools/gn2bp/Android.bp.swp
@@ -0,0 +1,131 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// 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.
+//
+// This file is automatically generated by gen_android_bp. Do not edit.
+
+// GN: //gn:default_deps
+cc_defaults {
+    name: "cronet_aml_defaults",
+    cflags: [
+        "-O2",
+        "-Wno-error=return-type",
+        "-Wno-sign-compare",
+        "-Wno-sign-promo",
+        "-Wno-unused-parameter",
+        "-fvisibility=hidden",
+    ],
+}
+
+// GN: //third_party/android_ndk:cpu_features
+filegroup {
+    name: "cronet_aml_third_party_android_ndk_cpu_features",
+    srcs: [
+        "third_party/android_ndk/sources/android/cpufeatures/cpu-features.c",
+    ],
+}
+
+// GN: //third_party/libevent:libevent
+cc_library_static {
+    name: "cronet_aml_third_party_libevent_libevent",
+    srcs: [
+        "third_party/libevent/buffer.c",
+        "third_party/libevent/epoll.c",
+        "third_party/libevent/evbuffer.c",
+        "third_party/libevent/evdns.c",
+        "third_party/libevent/event.c",
+        "third_party/libevent/event_tagging.c",
+        "third_party/libevent/evrpc.c",
+        "third_party/libevent/evutil.c",
+        "third_party/libevent/http.c",
+        "third_party/libevent/log.c",
+        "third_party/libevent/poll.c",
+        "third_party/libevent/select.c",
+        "third_party/libevent/signal.c",
+        "third_party/libevent/strlcpy.c",
+    ],
+    defaults: [
+        "cronet_aml_defaults",
+    ],
+    cflags: [
+        "-DANDROID",
+        "-DANDROID_NDK_VERSION_ROLL=r23_1",
+        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8189-g97196a2d-1\"",
+        "-DCR_LIBCXX_REVISION=166132d607a5b33487d523bd9cc2ba9176ddffef",
+        "-DDCHECK_ALWAYS_ON=1",
+        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
+        "-DHAVE_CONFIG_H",
+        "-DHAVE_SYS_UIO_H",
+        "-D_DEBUG",
+        "-D_GNU_SOURCE",
+        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
+    ],
+    local_include_dirs: [
+        "./",
+        "buildtools/third_party/libc++/",
+        "out/test/gen/",
+        "third_party/libevent/android/",
+    ],
+}
+
+// GN: //third_party/zlib:zlib
+cc_library_static {
+    name: "cronet_aml_third_party_zlib_zlib",
+    srcs: [
+        ":cronet_aml_third_party_android_ndk_cpu_features",
+        "third_party/zlib/adler32.c",
+        "third_party/zlib/compress.c",
+        "third_party/zlib/cpu_features.c",
+        "third_party/zlib/crc32.c",
+        "third_party/zlib/deflate.c",
+        "third_party/zlib/gzclose.c",
+        "third_party/zlib/gzlib.c",
+        "third_party/zlib/gzread.c",
+        "third_party/zlib/gzwrite.c",
+        "third_party/zlib/infback.c",
+        "third_party/zlib/inffast.c",
+        "third_party/zlib/inflate.c",
+        "third_party/zlib/inftrees.c",
+        "third_party/zlib/trees.c",
+        "third_party/zlib/uncompr.c",
+        "third_party/zlib/zutil.c",
+    ],
+    defaults: [
+        "cronet_aml_defaults",
+    ],
+    cflags: [
+        "-DANDROID",
+        "-DANDROID_NDK_VERSION_ROLL=r23_1",
+        "-DCPU_NO_SIMD",
+        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8189-g97196a2d-1\"",
+        "-DCR_LIBCXX_REVISION=166132d607a5b33487d523bd9cc2ba9176ddffef",
+        "-DDCHECK_ALWAYS_ON=1",
+        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
+        "-DHAVE_SYS_UIO_H",
+        "-DZLIB_DEBUG",
+        "-DZLIB_IMPLEMENTATION",
+        "-D_DEBUG",
+        "-D_GNU_SOURCE",
+        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
+        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
+    ],
+    local_include_dirs: [
+        "./",
+        "buildtools/third_party/libc++/",
+        "out/test/gen/",
+        "third_party/android_ndk/sources/android/cpufeatures/",
+        "third_party/zlib/",
+    ],
+}
+
diff --git a/tools/gn2bp/gen_android_bp b/tools/gn2bp/gen_android_bp
index cacb051..ee092db 100755
--- a/tools/gn2bp/gen_android_bp
+++ b/tools/gn2bp/gen_android_bp
@@ -28,6 +28,7 @@
 import argparse
 import collections
 import json
+import logging as log
 import os
 import re
 import sys
@@ -98,9 +99,6 @@
 # Compiler flags which are passed through to the blueprint.
 cflag_allowlist = r'^-DPERFETTO.*$'
 
-# Compiler defines which are passed through to the blueprint.
-define_allowlist = r'^(GOOGLE_PROTO.*)|(ZLIB_.*)|(USE_MMAP)|(HAVE_HIDDEN)$'
-
 # Additional arguments to apply to Android.bp rules.
 additional_args = {
     # TODO: remove if this is not useful for the cronet build.
@@ -341,7 +339,7 @@
     self.local_include_dirs = set()
     self.header_libs = set()
     self.required = set()
-    self.tool_files = None
+    self.tool_files = set()
     self.android = Target('android')
     self.host = Target('host')
     self.stl = None
@@ -469,7 +467,7 @@
 
 def is_supported_source_file(name):
   """Returns True if |name| can appear in a 'srcs' list."""
-  return os.path.splitext(name)[1] in ['.c', '.cc', '.proto']
+  return os.path.splitext(name)[1] in ['.c', '.cc', '.java', '.proto']
 
 
 def create_proto_modules(blueprint, gn, target):
@@ -600,9 +598,7 @@
 def create_amalgamated_sql_metrics_module(blueprint, target):
   bp_module_name = label_to_module_name(target.name)
   module = Module('genrule', bp_module_name, target.name)
-  module.tool_files = [
-      'tools/gen_amalgamated_sql_metrics.py',
-  ]
+  module.tool_files.add('tools/gen_amalgamated_sql_metrics.py')
   module.cmd = ' '.join([
       '$(location tools/gen_amalgamated_sql_metrics.py)',
       '--cpp_out=$(out)',
@@ -618,9 +614,7 @@
 def create_cc_proto_descriptor_module(blueprint, target):
   bp_module_name = label_to_module_name(target.name)
   module = Module('genrule', bp_module_name, target.name)
-  module.tool_files = [
-      'tools/gen_cc_proto_descriptor.py',
-  ]
+  module.tool_files.add('tools/gen_cc_proto_descriptor.py')
   module.cmd = ' '.join([
       '$(location tools/gen_cc_proto_descriptor.py)', '--gen_dir=$(genDir)',
       '--cpp_out=$(out)', '$(in)'
@@ -641,7 +635,7 @@
   module = Module('genrule', bp_module_name, gn_utils.GEN_VERSION_TARGET)
   script_path = gn_utils.label_to_path(target.script)
   module.genrule_headers.add(bp_module_name)
-  module.tool_files = [script_path]
+  module.tool_files.add(script_path)
   module.out.update(target.outputs)
   module.srcs.update(gn_utils.label_to_path(src) for src in target.inputs)
   module.cmd = ' '.join([
@@ -671,11 +665,54 @@
   blueprint.add_module(module)
 
 
+def create_action_module(blueprint, target):
+  bp_module_name = label_to_module_name(target.name)
+  module = Module('genrule', bp_module_name, target.name)
+  script = gn_utils.label_to_path(target.script)
+  module.tool_files.add(script)
+
+  # Handle passing parameters via response file by piping them into the script
+  # and reading them from /dev/stdin.
+  response_file = '{{response_file_name}}'
+  use_response_file = response_file in target.args
+  if use_response_file:
+    # Replace {{response_file_contents}} with /dev/stdin
+    target.args = ['/dev/stdin' if it == response_file else it for it in target.args]
+
+  arg_string = ' '.join(target.args)
+  module.cmd = '$(location %s) %s' % (script, arg_string)
+
+  if use_response_file:
+    # Pipe response file contents into script
+    module.cmd = 'echo \'%s\' | %s' % (target.response_file_contents, module.cmd)
+
+  if all(os.path.splitext(it)[1] == '.h' for it in target.outputs):
+    module.genrule_headers.add(bp_module_name)
+
+  # gn treats inputs and sources for actions equally.
+  # soong only supports source files inside srcs, non-source files are added as
+  # tool_files dependency.
+  for it in target.sources or target.inputs:
+    if is_supported_source_file(it):
+      module.srcs.add(gn_utils.label_to_path(it))
+    else:
+      module.tool_files.add(gn_utils.label_to_path(it))
+
+  # Actions using template "action_with_pydeps" also put script inside inputs.
+  # TODO: it might make sense to filter inputs inside GnParser.
+  if script in module.srcs:
+    module.srcs.remove(script)
+
+  module.out.update(target.outputs)
+  blueprint.add_module(module)
+  return module
+
+
+
 def _get_cflags(target):
   cflags = {flag for flag in target.cflags if re.match(cflag_allowlist, flag)}
-  cflags |= set("-D%s" % define
-                for define in target.defines
-                if re.match(define_allowlist, define))
+  # Consider proper allowlist or denylist if needed
+  cflags |= set("-D%s" % define.replace("\"", "\\\"") for define in target.defines)
   return cflags
 
 
@@ -694,6 +731,7 @@
   if bp_module_name in blueprint.modules:
     return blueprint.modules[bp_module_name]
   target = gn.get_target(gn_target_name)
+  log.info('create modules for %s (%s)', target.name, target.type)
 
   name_without_toolchain = gn_utils.label_without_toolchain(target.name)
   if target.type == 'executable':
@@ -727,7 +765,13 @@
         name_without_toolchain == gn_utils.GEN_VERSION_TARGET:
       module = create_gen_version_module(blueprint, target, bp_module_name)
     else:
-      raise Error('Unhandled action: {}'.format(target.name))
+      module = create_action_module(blueprint, target)
+  elif target.type == 'copy':
+    # TODO: careful now! copy targets are not supported yet, but this will stop
+    # traversing the dependency tree. For //base:base, this is not a big
+    # problem as libicu contains the only copy target which happens to be a
+    # leaf node.
+    return None
   else:
     raise Error('Unknown target %s (%s)' % (target.name, target.type))
 
@@ -783,33 +827,16 @@
   # dep_name is an unmangled GN target name (e.g. //foo:bar(toolchain)).
   all_deps = target.deps | target.source_set_deps | target.transitive_proto_deps
   for dep_name in all_deps:
-    # If the dependency refers to a library which we can replace with an
-    # Android equivalent, stop recursing and patch the dependency in.
-    # Don't recurse into //buildtools, builtin_deps are intercepted at
-    # the //gn:xxx level.
-    if dep_name.startswith('//buildtools'):
-      continue
-
-    # Ignore the dependency on the gen_buildflags genrule. That is run
-    # separately in this generator and the generated file is copied over
-    # into the repo (see usage of |buildflags_dir| in this script).
-    if dep_name.startswith(gn_utils.BUILDFLAGS_TARGET):
-      continue
-
-    dep_module = create_modules_from_target(blueprint, gn, dep_name)
-
-    # For filegroups and genrule, recurse but don't apply the deps.
-    if not module_is_compiled:
-      continue
-
     # |builtin_deps| override GN deps with Android-specific ones. See the
     # config in the top of this file.
     if gn_utils.label_without_toolchain(dep_name) in builtin_deps:
       builtin_deps[gn_utils.label_without_toolchain(dep_name)](module)
       continue
 
-    # Don't recurse in any other //gn dep if not handled by builtin_deps.
-    if dep_name.startswith('//gn:'):
+    dep_module = create_modules_from_target(blueprint, gn, dep_name)
+
+    # For filegroups and genrule, recurse but don't apply the deps.
+    if not module_is_compiled:
       continue
 
     if dep_module is None:
@@ -873,12 +900,21 @@
       default=os.path.join(gn_utils.repo_root(), 'Android.bp'),
   )
   parser.add_argument(
+      '-v',
+      '--verbose',
+      help='Print debug logs.',
+      action='store_true',
+  )
+  parser.add_argument(
       'targets',
       nargs=argparse.REMAINDER,
       help='Targets to include in the blueprint (e.g., "//:perfetto_tests")'
   )
   args = parser.parse_args()
 
+  if args.verbose:
+    log.basicConfig(format='%(levelname)s:%(funcName)s:%(message)s', level=log.DEBUG)
+
   with open(args.desc) as f:
     desc = json.load(f)
 
diff --git a/tools/gn2bp/gn_utils.py b/tools/gn2bp/gn_utils.py
index 95f677a..ef3bf55 100644
--- a/tools/gn2bp/gn_utils.py
+++ b/tools/gn2bp/gn_utils.py
@@ -39,114 +39,12 @@
 }
 
 
-def _check_command_output(cmd, cwd):
-  try:
-    output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, cwd=cwd)
-  except subprocess.CalledProcessError as e:
-    print(
-        'Command "{}" failed in {}:'.format(' '.join(cmd), cwd),
-        file=sys.stderr)
-    print(e.output.decode(), file=sys.stderr)
-    sys.exit(1)
-  else:
-    return output.decode()
-
-
 def repo_root():
   """Returns an absolute path to the repository root."""
   return os.path.join(
       os.path.realpath(os.path.dirname(__file__)), os.path.pardir)
 
 
-def _tool_path(name, system_buildtools=False):
-  # Pass-through to use name if the caller requests to use the system
-  # toolchain.
-  if system_buildtools:
-    return [name]
-  wrapper = os.path.abspath(
-      os.path.join(repo_root(), 'tools', 'run_buildtools_binary.py'))
-  return ['python3', wrapper, name]
-
-
-def prepare_out_directory(gn_args,
-                          name,
-                          root=repo_root(),
-                          system_buildtools=False):
-  """Creates the JSON build description by running GN.
-
-    Returns (path, desc) where |path| is the location of the output directory
-    and |desc| is the JSON build description.
-    """
-  out = os.path.join(root, 'out', name)
-  try:
-    os.makedirs(out)
-  except OSError as e:
-    if e.errno != errno.EEXIST:
-      raise
-  _check_command_output(
-      _tool_path('gn', system_buildtools) +
-      ['gen', out, '--args=%s' % gn_args],
-      cwd=repo_root())
-  return out
-
-
-def load_build_description(out, system_buildtools=False):
-  """Creates the JSON build description by running GN."""
-  desc = _check_command_output(
-      _tool_path('gn', system_buildtools) +
-      ['desc', out, '--format=json', '--all-toolchains', '//*'],
-      cwd=repo_root())
-  return json.loads(desc)
-
-
-def create_build_description(gn_args, root=repo_root()):
-  """Prepares a GN out directory and loads the build description from it.
-
-    The temporary out directory is automatically deleted.
-    """
-  out = prepare_out_directory(gn_args, 'tmp.gn_utils', root=root)
-  try:
-    return load_build_description(out)
-  finally:
-    shutil.rmtree(out)
-
-
-def build_targets(out, targets, quiet=False, system_buildtools=False):
-  """Runs ninja to build a list of GN targets in the given out directory.
-
-    Compiling these targets is required so that we can include any generated
-    source files in the amalgamated result.
-    """
-  targets = [t.replace('//', '') for t in targets]
-  with open(os.devnull, 'w') as devnull:
-    stdout = devnull if quiet else None
-    cmd = _tool_path('ninja', system_buildtools) + targets
-    subprocess.check_call(cmd, cwd=os.path.abspath(out), stdout=stdout)
-
-
-def compute_source_dependencies(out, system_buildtools=False):
-  """For each source file, computes a set of headers it depends on."""
-  ninja_deps = _check_command_output(
-      _tool_path('ninja', system_buildtools) + ['-t', 'deps'], cwd=out)
-  deps = {}
-  current_source = None
-  for line in ninja_deps.split('\n'):
-    filename = os.path.relpath(os.path.join(out, line.strip()), repo_root())
-    if not line or line[0] != ' ':
-      current_source = None
-      continue
-    elif not current_source:
-      # We're assuming the source file is always listed before the
-      # headers.
-      assert os.path.splitext(line)[1] in ['.c', '.cc', '.cpp', '.S']
-      current_source = filename
-      deps[current_source] = []
-    else:
-      assert current_source
-      deps[current_source].append(filename)
-  return deps
-
-
 def label_to_path(label):
   """Turn a GN output label (e.g., //some_dir/file.cc) into a path."""
   assert label.startswith('//')
@@ -172,115 +70,6 @@
   return name
 
 
-def gen_buildflags(gn_args, target_file):
-  """Generates the perfetto_build_flags.h for the given config.
-
-    target_file: the path, relative to the repo root, where the generated
-        buildflag header will be copied into.
-    """
-  tmp_out = prepare_out_directory(gn_args, 'tmp.gen_buildflags')
-  build_targets(tmp_out, [BUILDFLAGS_TARGET], quiet=True)
-  src = os.path.join(tmp_out, 'gen', 'build_config', 'perfetto_build_flags.h')
-  shutil.copy(src, os.path.join(repo_root(), target_file))
-  shutil.rmtree(tmp_out)
-
-
-def check_or_commit_generated_files(tmp_files, check):
-  """Checks that gen files are unchanged or renames them to the final location
-
-    Takes in input a list of 'xxx.swp' files that have been written.
-    If check == False, it renames xxx.swp -> xxx.
-    If check == True, it just checks that the contents of 'xxx.swp' == 'xxx'.
-    Returns 0 if no diff was detected, 1 otherwise (to be used as exit code).
-    """
-  res = 0
-  for tmp_file in tmp_files:
-    assert (tmp_file.endswith('.swp'))
-    target_file = os.path.relpath(tmp_file[:-4])
-    if check:
-      if not filecmp.cmp(tmp_file, target_file):
-        sys.stderr.write('%s needs to be regenerated\n' % target_file)
-        res = 1
-      os.unlink(tmp_file)
-    else:
-      os.rename(tmp_file, target_file)
-  return res
-
-
-class ODRChecker(object):
-  """Detects ODR violations in linker units
-
-  When we turn GN source sets into Soong & Bazel file groups, there is the risk
-  to create ODR violations by including the same file group into different
-  linker unit (this is because other build systems don't have a concept
-  equivalent to GN's source_set). This class navigates the transitive
-  dependencies (mostly static libraries) of a target and detects if multiple
-  paths end up including the same file group. This is to avoid situations like:
-
-  traced.exe -> base(file group)
-  traced.exe -> libperfetto(static lib) -> base(file group)
-  """
-
-  def __init__(self, gn, target_name):
-    self.gn = gn
-    self.root = gn.get_target(target_name)
-    self.source_sets = collections.defaultdict(set)
-    self.deps_visited = set()
-    self.source_set_hdr_only = {}
-
-    self._visit(target_name)
-    num_violations = 0
-    if target_name in ODR_VIOLATION_IGNORE_TARGETS:
-      return
-    for sset, paths in self.source_sets.items():
-      if self.is_header_only(sset):
-        continue
-      if len(paths) != 1:
-        num_violations += 1
-        print(
-            'ODR violation in target %s, multiple paths include %s:\n  %s' %
-            (target_name, sset, '\n  '.join(paths)),
-            file=sys.stderr)
-    if num_violations > 0:
-      raise Exception('%d ODR violations detected. Build generation aborted' %
-                      num_violations)
-
-  def _visit(self, target_name, parent_path=''):
-    target = self.gn.get_target(target_name)
-    path = ((parent_path + ' > ') if parent_path else '') + target_name
-    if not target:
-      raise Exception('Cannot find target %s' % target_name)
-    for ssdep in target.source_set_deps:
-      name_and_path = '%s (via %s)' % (target_name, path)
-      self.source_sets[ssdep].add(name_and_path)
-    deps = set(target.deps).union(
-        target.transitive_proto_deps) - self.deps_visited
-    for dep_name in deps:
-      dep = self.gn.get_target(dep_name)
-      if dep.type == 'executable':
-        continue  # Execs are strong boundaries and don't cause ODR violations.
-      # static_library dependencies should reset the path. It doesn't matter if
-      # we get to a source file via:
-      # source_set1 > static_lib > source.cc OR
-      # source_set1 > source_set2 > static_lib > source.cc
-      # This is NOT an ODR violation because source.cc is linked from the same
-      # static library
-      next_parent_path = path if dep.type != 'static_library' else ''
-      self.deps_visited.add(dep_name)
-      self._visit(dep_name, next_parent_path)
-
-  def is_header_only(self, source_set_name):
-    cached = self.source_set_hdr_only.get(source_set_name)
-    if cached is not None:
-      return cached
-    target = self.gn.get_target(source_set_name)
-    if target.type != 'source_set':
-      raise TypeError('%s is not a source_set' % source_set_name)
-    res = all(src.endswith('.h') for src in target.sources)
-    self.source_set_hdr_only[source_set_name] = res
-    return res
-
-
 class GnParser(object):
   """A parser with some cleverness for GN json desc files
 
@@ -309,7 +98,7 @@
       self.name = name  # e.g. //src/ipc:ipc
 
       VALID_TYPES = ('static_library', 'shared_library', 'executable', 'group',
-                     'action', 'source_set', 'proto_library')
+                     'action', 'source_set', 'proto_library', 'copy', 'action_foreach')
       assert (type in VALID_TYPES)
       self.type = type
       self.testonly = False
@@ -331,6 +120,7 @@
       self.outputs = set()
       self.script = None
       self.args = []
+      self.response_file_contents = None
 
       # These variables are propagated up when encountering a dependency
       # on a source_set target.
@@ -380,6 +170,13 @@
     self.actions = {}
     self.proto_libs = {}
 
+  def _get_response_file_contents(self, action_desc):
+    contents = ' '.join(action_desc.get('response_file_contents', []))
+    # escape both single and double quotes.
+    contents.replace('"', '\"')
+    contents.replace("'", '\'')
+    return contents
+
   def get_target(self, gn_target_name):
     """Returns a Target object from the fully qualified GN target name.
 
@@ -396,10 +193,14 @@
     target.toolchain = desc.get('toolchain', None)
     self.all_targets[gn_target_name] = target
 
+    # TODO: determine if below comment should apply for cronet builds in Android.
     # We should never have GN targets directly depend on buidtools. They
     # should hop via //gn:xxx, so we can give generators an opportunity to
     # override them.
-    assert (not gn_target_name.startswith('//buildtools'))
+    # Specifically allow targets to depend on libc++ and libunwind.
+    if not any(match in gn_target_name for match in ['libc++', 'libunwind']):
+      assert (not gn_target_name.startswith('//buildtools'))
+
 
     # Don't descend further into third_party targets. Genrators are supposed
     # to either ignore them or route to other externally-provided targets.
@@ -422,7 +223,7 @@
     elif target.type in LINKER_UNIT_TYPES:
       self.linker_units[gn_target_name] = target
       target.sources.update(desc.get('sources', []))
-    elif target.type == 'action':
+    elif target.type in ['action', 'action_foreach']:
       self.actions[gn_target_name] = target
       target.inputs.update(desc.get('inputs', []))
       target.sources.update(desc.get('sources', []))
@@ -432,6 +233,10 @@
       # Args are typically relative to the root build dir (../../xxx)
       # because root build dir is typically out/xxx/).
       target.args = [re.sub('^../../', '//', x) for x in desc['args']]
+      target.response_file_contents = self._get_response_file_contents(desc)
+    elif target.type == 'copy':
+      # TODO: copy rules are not currently implemented.
+      self.actions[gn_target_name] = target
 
     # Default for 'public' is //* - all headers in 'sources' are public.
     # TODO(primiano): if a 'public' section is specified (even if empty), then
@@ -462,7 +267,7 @@
         target.update(dep)  # Bubble up source set's cflags/ldflags etc.
       elif dep.type == 'group':
         target.update(dep)  # Bubble up groups's cflags/ldflags etc.
-      elif dep.type == 'action':
+      elif dep.type in ['action', 'action_foreach', 'copy']:
         if proto_target_type is None:
           target.deps.add(dep_name)
       elif dep.type in LINKER_UNIT_TYPES:
diff --git a/tools/gn2bp/run_buildtools_binary.py b/tools/gn2bp/run_buildtools_binary.py
deleted file mode 100755
index 7a6fe81..0000000
--- a/tools/gn2bp/run_buildtools_binary.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (C) 2022 The Android Open Source Project
-#
-# 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.
-""" A wrapper to run gn, ninja and other buildtools/ for all platforms. """
-
-from __future__ import print_function
-
-import os
-import subprocess
-import sys
-
-from platform import system, machine
-
-ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
-
-def run_buildtools_binary(args):
-  if len(args) < 1:
-    print('Usage %s command [args]\n' % sys.argv[0])
-    return 1
-
-  sys_name = system().lower()
-  os_dir = None
-  ext = ''
-  if sys_name == 'windows':
-    os_dir = 'win'
-    ext = '.exe'
-  elif sys_name == 'darwin':
-    os_dir = 'mac'
-  elif sys_name == 'linux':
-    os_dir = 'linux64'
-  else:
-    print('OS not supported: %s\n' % sys_name)
-    return 1
-
-  cmd = args[0]
-  args = args[1:]
-  exe_path = os.path.join(ROOT_DIR, 'buildtools', os_dir, cmd) + ext
-  if sys_name == 'windows':
-    # execl() behaves oddly on Windows: the spawned process doesn't seem to
-    # receive CTRL+C. Use subprocess instead.
-    return subprocess.call([exe_path] + args)
-  else:
-    os.execl(exe_path, os.path.basename(exe_path), *args)
-
-
-if __name__ == '__main__':
-  sys.exit(run_buildtools_binary(sys.argv[1:]))
diff --git a/tools/gn2bp/update_results.sh b/tools/gn2bp/update_results.sh
new file mode 100755
index 0000000..31f6605
--- /dev/null
+++ b/tools/gn2bp/update_results.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# This script is expected to run after gen_android_bp is modified.
+#
+#   ./update_result.sh desc.json
+#
+# TARGETS contains targets which are supported by gen_android_bp and
+# this script generates Android.bp.swp from TARGETS.
+# This makes it easy to realize unintended impact/degression on
+# previously supported targets.
+
+set -eux
+
+TARGETS=(
+  "//third_party/zlib:zlib"
+  "//third_party/libevent:libevent"
+)
+
+BASEDIR=$(dirname "$0")
+$BASEDIR/gen_android_bp --desc $1 --out $BASEDIR/Android.bp ${TARGETS[@]}