Merge "Check that wifi, cell validates before tests" into main
diff --git a/Tethering/proguard.flags b/Tethering/proguard.flags
index 109bbda..47e2848 100644
--- a/Tethering/proguard.flags
+++ b/Tethering/proguard.flags
@@ -15,6 +15,10 @@
     native <methods>;
 }
 
+-keep class com.android.networkstack.tethering.util.TetheringUtils {
+    native <methods>;
+}
+
 # Ensure runtime-visible field annotations are kept when using R8 full mode.
 -keepattributes RuntimeVisibleAnnotations,AnnotationDefault
 -keep interface com.android.networkstack.tethering.util.Struct$Field {
diff --git a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java b/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java
new file mode 100644
index 0000000..5a984c7
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java
@@ -0,0 +1,191 @@
+/**
+ * Copyright (C) 2023 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.
+ */
+
+package com.android.networkstack.tethering.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.util.State;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * An implementation of a state machine, meant to be called synchronously.
+ *
+ * This class implements a finite state automaton based on the same State
+ * class as StateMachine.
+ * All methods of this class must be called on only one thread.
+ */
+public class SyncStateMachine {
+    @NonNull private final String mName;
+    @NonNull private final Thread mMyThread;
+    private final boolean mDbg;
+    private final ArrayMap<State, StateInfo> mStateInfo = new ArrayMap<>();
+
+    // mCurrentState is the current state. mDestState is the target state that mCurrentState will
+    // transition to. The value of mDestState can be changed when a state processes a message and
+    // calls #transitionTo, but it cannot be changed during the state transition. When the state
+    // transition is complete, mDestState will be set to mCurrentState. Both mCurrentState and
+    // mDestState only be null before state machine starts and must only be touched on mMyThread.
+    @Nullable private State mCurrentState;
+    @Nullable private State mDestState;
+
+    /**
+     * A information class about a state and its parent. Used to maintain the state hierarchy.
+     */
+    public static class StateInfo {
+        /** The state who owns this StateInfo. */
+        public final State state;
+        /** The parent state. */
+        public final State parent;
+        // True when the state has been entered and on the stack.
+        private boolean mActive;
+
+        public StateInfo(@NonNull final State child, @Nullable final State parent) {
+            this.state = child;
+            this.parent = parent;
+        }
+    }
+
+    /**
+     * The constructor.
+     *
+     * @param name of this machine.
+     * @param thread the running thread of this machine. It must either be the thread on which this
+     * constructor is called, or a thread that is not started yet.
+     */
+    public SyncStateMachine(@NonNull final String name, @NonNull final Thread thread) {
+        this(name, thread, false /* debug */);
+    }
+
+    /**
+     * The constructor.
+     *
+     * @param name of this machine.
+     * @param thread the running thread of this machine. It must either be the thread on which this
+     * constructor is called, or a thread that is not started yet.
+     * @param dbg whether to print debug logs.
+     */
+    public SyncStateMachine(@NonNull final String name, @NonNull final Thread thread,
+            final boolean dbg) {
+        mMyThread = thread;
+        // Machine can either be setup from machine thread or before machine thread started.
+        ensureCorrectOrNotStartedThread();
+
+        mName = name;
+        mDbg = dbg;
+    }
+
+    /**
+     * Add all of states to the state machine. Different StateInfos which have same state are not
+     * allowed. In other words, a state can not have multiple parent states. #addAllStates can
+     * only be called once either from mMyThread or before mMyThread started.
+     */
+    public final void addAllStates(@NonNull final List<StateInfo> stateInfos) {
+        ensureCorrectOrNotStartedThread();
+
+        if (mCurrentState != null) {
+            throw new IllegalStateException("State only can be added before started");
+        }
+
+        if (stateInfos.isEmpty()) throw new IllegalStateException("Empty state is not allowed");
+
+        if (!mStateInfo.isEmpty()) throw new IllegalStateException("States are already configured");
+
+        final Set<Class> usedClasses = new ArraySet<>();
+        for (final StateInfo info : stateInfos) {
+            Objects.requireNonNull(info.state);
+            if (!usedClasses.add(info.state.getClass())) {
+                throw new IllegalStateException("Adding the same state multiple times in a state "
+                        + "machine is forbidden because it tends to be confusing; it can be done "
+                        + "with anonymous subclasses but consider carefully whether you want to "
+                        + "use a single state or other alternatives instead.");
+            }
+
+            mStateInfo.put(info.state, info);
+        }
+
+        // Check whether all of parent states indicated from StateInfo are added.
+        for (final StateInfo info : stateInfos) {
+            if (info.parent != null) ensureExistingState(info.parent);
+        }
+    }
+
+    /**
+     * Start the state machine. The initial state can't be child state.
+     *
+     * @param initialState the first state of this machine. The state must be exact state object
+     * setting up by {@link #addAllStates}, not a copy of it.
+     */
+    public final void start(@NonNull final State initialState) {
+        ensureCorrectThread();
+        ensureExistingState(initialState);
+
+        mCurrentState = initialState;
+        mDestState = initialState;
+    }
+
+    /**
+     * Process the message synchronously then perform state transition.
+     */
+    public final void processMessage(int what, int arg1, int arg2, @Nullable Object obj) {
+        ensureCorrectThread();
+    }
+
+    /**
+     * Transition to destination state. Upon returning from processMessage the automaton will
+     * transition to the given destination state.
+     *
+     * This function can NOT be called inside the State enter and exit function. The transition
+     * target is always defined and can never be changed mid-way of state transition.
+     *
+     * @param destState will be the state to transition to. The state must be the same instance set
+     * up by {@link #addAllStates}, not a copy of it.
+     */
+    public final void transitionTo(@NonNull final State destState) {
+        if (mDbg) Log.d(mName, "transitionTo " + destState);
+        ensureCorrectThread();
+        ensureExistingState(destState);
+
+        if (mDestState == mCurrentState) {
+            mDestState = destState;
+        } else {
+            throw new IllegalStateException("Destination already specified");
+        }
+    }
+
+    private void ensureCorrectThread() {
+        if (!mMyThread.equals(Thread.currentThread())) {
+            throw new IllegalStateException("Called from wrong thread");
+        }
+    }
+
+    private void ensureCorrectOrNotStartedThread() {
+        if (!mMyThread.isAlive()) return;
+
+        ensureCorrectThread();
+    }
+
+    private void ensureExistingState(@NonNull final State state) {
+        if (!mStateInfo.containsKey(state)) throw new IllegalStateException("Invalid state");
+    }
+}
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index eed308c..076fde3 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -326,6 +326,14 @@
 
             waitForRouterAdvertisement(downstreamReader, iface, WAIT_RA_TIMEOUT_MS);
             expectLocalOnlyAddresses(iface);
+
+            // After testing the IPv6 local address, the DHCP server may still be in the process
+            // of being created. If the downstream interface is killed by the test while the
+            // DHCP server is starting, a DHCP server error may occur. To ensure that the DHCP
+            // server has started completely before finishing the test, also test the dhcp server
+            // by calling runDhcp.
+            final TetheringTester tester = new TetheringTester(downstreamReader);
+            tester.runDhcp(MacAddress.fromString("1:2:3:4:5:6").toByteArray());
         } finally {
             maybeStopTapPacketReader(downstreamReader);
             maybeCloseTestInterface(downstreamIface);
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 41a93f4..c3258e9 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -272,6 +272,16 @@
         (void)bpf_skb_load_bytes_net(skb, IP6_OFFSET(nexthdr), &proto, sizeof(proto), kver);
         L4_off = sizeof(struct ipv6hdr);
         ipVersion = 6;
+        // skip over a *single* HOPOPTS or DSTOPTS extension header (if present)
+        if (proto == IPPROTO_HOPOPTS || proto == IPPROTO_DSTOPTS) {
+            struct {
+                uint8_t proto, len;
+            } ext_hdr;
+            if (!bpf_skb_load_bytes_net(skb, L4_off, &ext_hdr, sizeof(ext_hdr), kver)) {
+                proto = ext_hdr.proto;
+                L4_off += (ext_hdr.len + 1) * 8;
+            }
+        }
     }
 
     uint8_t flags = 0;
@@ -309,6 +319,7 @@
     pkt->dport = dport;
 
     pkt->egress = egress;
+    pkt->wakeup = !egress && (skb->mark & 0x80000000);  // Fwmark.ingress_cpu_wakeup
     pkt->ipProto = proto;
     pkt->tcpFlags = flags;
     pkt->ipVersion = ipVersion;
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index 836e998..6e9acaa 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -81,7 +81,8 @@
   __be16 sport;
   __be16 dport;
 
-  bool egress;
+  bool egress:1,
+       wakeup:1;
   uint8_t ipProto;
   uint8_t tcpFlags;
   uint8_t ipVersion; // 4=IPv4, 6=IPv6, 0=unknown
diff --git a/service/libconnectivity/include/connectivity_native.h b/service/libconnectivity/include/connectivity_native.h
index 5a2509a..f4676a9 100644
--- a/service/libconnectivity/include/connectivity_native.h
+++ b/service/libconnectivity/include/connectivity_native.h
@@ -78,7 +78,7 @@
  * @param count Pointer to the size of the ports array; the value will be set to the total number of
  *              blocked ports, which may be larger than the ports array that was filled.
  */
-int AConnectivityNative_getPortsBlockedForBind(in_port_t *ports, size_t *count)
+int AConnectivityNative_getPortsBlockedForBind(in_port_t* _Nonnull ports, size_t* _Nonnull count)
     __INTRODUCED_IN(__ANDROID_API_U__);
 
 __END_DECLS
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 3a76cc2..59aefa5 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -2908,7 +2908,6 @@
 
     @AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
     @Test
-    @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
     public void testRejectPartialConnectivity_TearDownNetwork() throws Exception {
         assumeTrue(TestUtils.shouldTestSApis());
         assumeTrue("testAcceptPartialConnectivity_validatedNetwork cannot execute"