From bdcd7db9d851b2951d63dd1ad3768a8d667f5c7e Mon Sep 17 00:00:00 2001 From: KH Shi Date: Thu, 16 Mar 2023 15:23:13 +0800 Subject: [PATCH] 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 --- .../net/ip/RouterAdvertisementDaemon.java | 138 +++++++++++++----- 1 file changed, 105 insertions(+), 33 deletions(-) diff --git a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java index 50d6c4b18d..d8779b83c0 100644 --- a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java +++ b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java @@ -18,6 +18,7 @@ package android.net.ip; 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.LinkAddress; 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 @@ public class RouterAdvertisementDaemon { 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 @@ public class RouterAdvertisementDaemon { @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 @@ public class RouterAdvertisementDaemon { } } + 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 { + 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 @@ public class RouterAdvertisementDaemon { /** 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 @@ public class RouterAdvertisementDaemon { 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 @@ public class RouterAdvertisementDaemon { } } - 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,