Skip to content
Snippets Groups Projects
Commit bdcd7db9 authored by KH Shi's avatar KH Shi
Browse files

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
parent e903b817
No related branches found
No related tags found
No related merge requests found
......@@ -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<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 @@ 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,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment