Refactor RouterAdvertisementDaemon with FdEventsReader

This patch changes the implementation of UnicastResponder in the
RouterAdvertisementDaemon with FdEventsReader. The change includes
replacing Thread with HandlerThread, create socket in non-blocking
mode (required by FdEventsReader) and use FdEventsReader to listen
to the solicited RS packets. Two commands (CMD_START & CMD_STOP)
were added to control the lifecycle of the FdEventsReader.

Bug: 246928127
Test: atest TetheringPrivilegedTests TetheringIntegrationTests \
      TetheringTests
Test: Enable WiFi hotspot and connected with another Pixel client:
      1. Tcpdump on the Pixel client, checking the RA packets
      2. Verify IPv6 addresses were configured
      3. ping6 2001:4860:4860::64
Change-Id: I6e89903df90864e3bb34d498e353d6f87193e9d6
diff --git a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
index 50d6c4b..d8779b8 100644
--- a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
+++ b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -18,6 +18,7 @@
 
 import static android.system.OsConstants.AF_INET6;
 import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.SOCK_NONBLOCK;
 import static android.system.OsConstants.SOCK_RAW;
 import static android.system.OsConstants.SOL_SOCKET;
 import static android.system.OsConstants.SO_SNDTIMEO;
@@ -38,12 +39,19 @@
 import android.net.MacAddress;
 import android.net.TrafficStats;
 import android.net.util.SocketUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.StructTimeval;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.GuardedBy;
+import com.android.net.module.util.FdEventsReader;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.structs.Icmpv6Header;
 import com.android.net.module.util.structs.LlaOption;
@@ -103,6 +111,10 @@
 
     private static final int DAY_IN_SECONDS = 86_400;
 
+    // Commands for IpServer to control RouterAdvertisementDaemon
+    private static final int CMD_START = 1;
+    private static final int CMD_STOP  = 2;
+
     private final InterfaceParams mInterface;
     private final InetSocketAddress mAllNodes;
 
@@ -120,9 +132,13 @@
     @GuardedBy("mLock")
     private RaParams mRaParams;
 
+    // To be accessed only from RaMessageHandler
+    private RsPacketListener mRsPacketListener;
+
     private volatile FileDescriptor mSocket;
     private volatile MulticastTransmitter mMulticastTransmitter;
-    private volatile UnicastResponder mUnicastResponder;
+    private volatile RaMessageHandler mRaMessageHandler;
+    private volatile HandlerThread mRaHandlerThread;
 
     /** Encapsulate the RA parameters for RouterAdvertisementDaemon.*/
     public static class RaParams {
@@ -244,6 +260,74 @@
         }
     }
 
+    private class RaMessageHandler extends Handler {
+        RaMessageHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_START:
+                    mRsPacketListener = new RsPacketListener(this);
+                    mRsPacketListener.start();
+                    break;
+                case CMD_STOP:
+                    if (mRsPacketListener != null) {
+                        mRsPacketListener.stop();
+                        mRsPacketListener = null;
+                    }
+                    break;
+                default:
+                    Log.e(TAG, "Unknown message, cmd = " + String.valueOf(msg.what));
+                    break;
+            }
+        }
+    }
+
+    private class RsPacketListener extends FdEventsReader<RsPacketListener.RecvBuffer> {
+        private static final class RecvBuffer {
+            // The recycled buffer for receiving Router Solicitations from clients.
+            // If the RS is larger than IPV6_MIN_MTU the packets are truncated.
+            // This is fine since currently only byte 0 is examined anyway.
+            final byte[] mBytes = new byte[IPV6_MIN_MTU];
+            final InetSocketAddress mSrcAddr = new InetSocketAddress(0);
+        }
+
+        RsPacketListener(@NonNull Handler handler) {
+            super(handler, new RecvBuffer());
+        }
+
+        @Override
+        protected int recvBufSize(@NonNull RecvBuffer buffer) {
+            return buffer.mBytes.length;
+        }
+
+        @Override
+        protected FileDescriptor createFd() {
+            return mSocket;
+        }
+
+        @Override
+        protected int readPacket(@NonNull FileDescriptor fd, @NonNull RecvBuffer buffer)
+                throws Exception {
+            return Os.recvfrom(
+                    fd, buffer.mBytes, 0, buffer.mBytes.length, 0 /* flags */, buffer.mSrcAddr);
+        }
+
+        @Override
+        protected final void handlePacket(@NonNull RecvBuffer buffer, int length) {
+            // Do the least possible amount of validations.
+            if (buffer.mSrcAddr == null
+                    || length <= 0
+                    || buffer.mBytes[0] != asByte(ICMPV6_ROUTER_SOLICITATION)) {
+                return;
+            }
+
+            maybeSendRA(buffer.mSrcAddr);
+        }
+    }
+
     public RouterAdvertisementDaemon(InterfaceParams ifParams) {
         mInterface = ifParams;
         mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mInterface.index), 0);
@@ -274,26 +358,36 @@
     /** Start router advertisement daemon. */
     public boolean start() {
         if (!createSocket()) {
+            Log.e(TAG, "Failed to start RouterAdvertisementDaemon.");
             return false;
         }
 
         mMulticastTransmitter = new MulticastTransmitter();
         mMulticastTransmitter.start();
 
-        mUnicastResponder = new UnicastResponder();
-        mUnicastResponder.start();
+        mRaHandlerThread = new HandlerThread(TAG);
+        mRaHandlerThread.start();
+        mRaMessageHandler = new RaMessageHandler(mRaHandlerThread.getLooper());
 
-        return true;
+        return sendMessage(CMD_START);
     }
 
     /** Stop router advertisement daemon. */
     public void stop() {
+        if (!sendMessage(CMD_STOP)) {
+            Log.e(TAG, "RouterAdvertisementDaemon has been stopped or was never started.");
+            return;
+        }
+
+        mRaHandlerThread.quitSafely();
+        mRaHandlerThread = null;
+        mRaMessageHandler = null;
+
         closeSocket();
         // Wake up mMulticastTransmitter thread to interrupt a potential 1 day sleep before
         // the thread's termination.
         maybeNotifyMulticastTransmitter();
         mMulticastTransmitter = null;
-        mUnicastResponder = null;
     }
 
     @GuardedBy("mLock")
@@ -503,7 +597,7 @@
 
         final int oldTag = TrafficStats.getAndSetThreadStatsTag(TAG_SYSTEM_NEIGHBOR);
         try {
-            mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+            mSocket = Os.socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_ICMPV6);
             // Setting SNDTIMEO is purely for defensive purposes.
             Os.setsockoptTimeval(
                     mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(send_timout_ms));
@@ -565,34 +659,12 @@
         }
     }
 
-    private final class UnicastResponder extends Thread {
-        private final InetSocketAddress mSolicitor = new InetSocketAddress(0);
-        // The recycled buffer for receiving Router Solicitations from clients.
-        // If the RS is larger than IPV6_MIN_MTU the packets are truncated.
-        // This is fine since currently only byte 0 is examined anyway.
-        private final byte[] mSolicitation = new byte[IPV6_MIN_MTU];
-
-        @Override
-        public void run() {
-            while (isSocketValid()) {
-                try {
-                    // Blocking receive.
-                    final int rval = Os.recvfrom(
-                            mSocket, mSolicitation, 0, mSolicitation.length, 0, mSolicitor);
-                    // Do the least possible amount of validation.
-                    if (rval < 1 || mSolicitation[0] != asByte(ICMPV6_ROUTER_SOLICITATION)) {
-                        continue;
-                    }
-                } catch (ErrnoException | SocketException e) {
-                    if (isSocketValid()) {
-                        Log.e(TAG, "recvfrom error: " + e);
-                    }
-                    continue;
-                }
-
-                maybeSendRA(mSolicitor);
-            }
+    private boolean sendMessage(int cmd) {
+        if (mRaMessageHandler == null) {
+            return false;
         }
+
+        return mRaMessageHandler.sendMessage(Message.obtain(mRaMessageHandler, cmd));
     }
 
     // TODO: Consider moving this to run on a provided Looper as a Handler,