summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/net/apf/ApfFilter.java269
-rw-r--r--src/android/net/dhcp/DhcpClient.java160
-rw-r--r--src/android/net/util/FdEventsReader.java32
-rw-r--r--src/android/net/util/PacketReader.java2
-rw-r--r--src/com/android/server/connectivity/NetworkMonitor.java78
5 files changed, 307 insertions, 234 deletions
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 1d3421c..2f74ad6 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -57,7 +57,6 @@ import android.system.ErrnoException;
import android.system.Os;
import android.text.format.DateUtils;
import android.util.Log;
-import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -500,6 +499,44 @@ public class ApfFilter {
}
}
+ /**
+ * Class to keep track of a section in a packet.
+ */
+ private static class PacketSection {
+ public enum Type {
+ MATCH, // A field that should be matched (e.g., the router IP address).
+ IGNORE, // An ignored field such as the checksum of the flow label. Not matched.
+ LIFETIME, // A lifetime. Not matched, and generally counts toward minimum RA lifetime.
+ }
+
+ /** The type of section. */
+ public final Type type;
+ /** Offset into the packet at which this section begins. */
+ public final int start;
+ /** Length of this section. */
+ public final int length;
+ /** If this is a lifetime, the ICMP option that the defined it. 0 for router lifetime. */
+ public final int option;
+ /** If this is a lifetime, the lifetime value. */
+ public final long lifetime;
+
+ PacketSection(int start, int length, Type type, int option, long lifetime) {
+ this.start = start;
+ this.length = length;
+ this.type = type;
+ this.option = option;
+ this.lifetime = lifetime;
+ }
+
+ public String toString() {
+ if (type == Type.LIFETIME) {
+ return String.format("%s: (%d, %d) %d %d", type, start, length, option, lifetime);
+ } else {
+ return String.format("%s: (%d, %d)", type, start, length);
+ }
+ }
+ }
+
// A class to hold information about an RA.
@VisibleForTesting
class Ra {
@@ -534,10 +571,10 @@ public class ApfFilter {
// Note: mPacket's position() cannot be assumed to be reset.
private final ByteBuffer mPacket;
- // List of binary ranges that include the whole packet except the lifetimes.
- // Pairs consist of offset and length.
- private final ArrayList<Pair<Integer, Integer>> mNonLifetimes =
- new ArrayList<Pair<Integer, Integer>>();
+
+ // List of sections in the packet.
+ private final ArrayList<PacketSection> mPacketSections = new ArrayList<>();
+
// Minimum lifetime in packet
long mMinLifetime;
// When the packet was last captured, in seconds since Unix Epoch
@@ -649,27 +686,65 @@ public class ApfFilter {
}
/**
- * Add a binary range of the packet that does not include a lifetime to mNonLifetimes.
- * Assumes mPacket.position() is as far as we've parsed the packet.
- * @param lastNonLifetimeStart offset within packet of where the last binary range of
- * data not including a lifetime.
- * @param lifetimeOffset offset from mPacket.position() to the next lifetime data.
- * @param lifetimeLength length of the next lifetime data.
- * @return offset within packet of where the next binary range of data not including
- * a lifetime. This can be passed into the next invocation of this function
- * via {@code lastNonLifetimeStart}.
+ * Add a packet section that should be matched, starting from the current position.
+ * @param length the length of the section
+ */
+ private void addMatchSection(int length) {
+ // Don't generate JNEBS instruction for 0 bytes as they will fail the
+ // ASSERT_FORWARD_IN_PROGRAM(pc + cmp_imm - 1) check (where cmp_imm is
+ // the number of bytes to compare) and immediately pass the packet.
+ // The code does not attempt to generate such matches, but add a safety
+ // check to prevent doing so in the presence of bugs or malformed or
+ // truncated packets.
+ if (length == 0) return;
+ mPacketSections.add(
+ new PacketSection(mPacket.position(), length, PacketSection.Type.MATCH, 0, 0));
+ mPacket.position(mPacket.position() + length);
+ }
+
+ /**
+ * Add a packet section that should be matched, starting from the current position.
+ * @param end the offset in the packet before which the section ends
+ */
+ private void addMatchUntil(int end) {
+ addMatchSection(end - mPacket.position());
+ }
+
+ /**
+ * Add a packet section that should be ignored, starting from the current position.
+ * @param length the length of the section in bytes
*/
- private int addNonLifetime(int lastNonLifetimeStart, int lifetimeOffset,
- int lifetimeLength) {
- lifetimeOffset += mPacket.position();
- mNonLifetimes.add(new Pair<Integer, Integer>(lastNonLifetimeStart,
- lifetimeOffset - lastNonLifetimeStart));
- return lifetimeOffset + lifetimeLength;
+ private void addIgnoreSection(int length) {
+ mPacketSections.add(
+ new PacketSection(mPacket.position(), length, PacketSection.Type.IGNORE, 0, 0));
+ mPacket.position(mPacket.position() + length);
}
- private int addNonLifetimeU32(int lastNonLifetimeStart) {
- return addNonLifetime(lastNonLifetimeStart,
- ICMP6_4_BYTE_LIFETIME_OFFSET, ICMP6_4_BYTE_LIFETIME_LEN);
+ /**
+ * Add a packet section that represents a lifetime, starting from the current position.
+ * @param length the length of the section in bytes
+ * @param optionType the RA option containing this lifetime, or 0 for router lifetime
+ * @param lifetime the lifetime
+ */
+ private void addLifetimeSection(int length, int optionType, long lifetime) {
+ mPacketSections.add(
+ new PacketSection(mPacket.position(), length, PacketSection.Type.LIFETIME,
+ optionType, lifetime));
+ mPacket.position(mPacket.position() + length);
+ }
+
+ /**
+ * Adds packet sections for an RA option with a 4-byte lifetime 4 bytes into the option
+ * @param optionType the RA option that is being added
+ * @param optionLength the length of the option in bytes
+ */
+ private long add4ByteLifetimeOption(int optionType, int optionLength) {
+ addMatchSection(ICMP6_4_BYTE_LIFETIME_OFFSET);
+ final long lifetime = getUint32(mPacket, mPacket.position());
+ addLifetimeSection(ICMP6_4_BYTE_LIFETIME_LEN, optionType, lifetime);
+ addMatchSection(optionLength - ICMP6_4_BYTE_LIFETIME_OFFSET
+ - ICMP6_4_BYTE_LIFETIME_LEN);
+ return lifetime;
}
// Note that this parses RA and may throw InvalidRaException (from
@@ -696,20 +771,18 @@ public class ApfFilter {
RaEvent.Builder builder = new RaEvent.Builder();
// Ignore the flow label and low 4 bits of traffic class.
- int lastNonLifetimeStart = addNonLifetime(0,
- IPV6_FLOW_LABEL_OFFSET,
- IPV6_FLOW_LABEL_LEN);
+ addMatchUntil(IPV6_FLOW_LABEL_OFFSET);
+ addIgnoreSection(IPV6_FLOW_LABEL_LEN);
- // Ignore the checksum.
- lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
- ICMP6_RA_CHECKSUM_OFFSET,
- ICMP6_RA_CHECKSUM_LEN);
+ // Ignore checksum.
+ addMatchUntil(ICMP6_RA_CHECKSUM_OFFSET);
+ addIgnoreSection(ICMP6_RA_CHECKSUM_LEN);
// Parse router lifetime
- lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
- ICMP6_RA_ROUTER_LIFETIME_OFFSET,
- ICMP6_RA_ROUTER_LIFETIME_LEN);
- builder.updateRouterLifetime(getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET));
+ addMatchUntil(ICMP6_RA_ROUTER_LIFETIME_OFFSET);
+ final long routerLifetime = getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET);
+ addLifetimeSection(ICMP6_RA_ROUTER_LIFETIME_LEN, 0, routerLifetime);
+ builder.updateRouterLifetime(routerLifetime);
// Ensures that the RA is not truncated.
mPacket.position(ICMP6_RA_OPTION_OFFSET);
@@ -720,64 +793,62 @@ public class ApfFilter {
long lifetime;
switch (optionType) {
case ICMP6_PREFIX_OPTION_TYPE:
+ mPrefixOptionOffsets.add(position);
+
// Parse valid lifetime
- lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
- ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET,
- ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN);
- lifetime = getUint32(mPacket,
- position + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET);
+ addMatchSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN);
+ lifetime = getUint32(mPacket, mPacket.position());
+ addLifetimeSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN,
+ ICMP6_PREFIX_OPTION_TYPE, lifetime);
builder.updatePrefixValidLifetime(lifetime);
+
// Parse preferred lifetime
- lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
- ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
- ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN);
- lifetime = getUint32(mPacket,
- position + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET);
+ lifetime = getUint32(mPacket, mPacket.position());
+ addLifetimeSection(ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN,
+ ICMP6_PREFIX_OPTION_TYPE, lifetime);
builder.updatePrefixPreferredLifetime(lifetime);
- mPrefixOptionOffsets.add(position);
+
+ addMatchSection(4); // Reserved bytes
+ addMatchSection(IPV6_ADDR_LEN); // The prefix itself
break;
// These three options have the same lifetime offset and size, and
- // are processed with the same specialized addNonLifetimeU32:
+ // are processed with the same specialized add4ByteLifetimeOption:
case ICMP6_RDNSS_OPTION_TYPE:
mRdnssOptionOffsets.add(position);
- lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
- lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET);
+ lifetime = add4ByteLifetimeOption(optionType, optionLength);
builder.updateRdnssLifetime(lifetime);
break;
case ICMP6_ROUTE_INFO_OPTION_TYPE:
mRioOptionOffsets.add(position);
- lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
- lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET);
+ lifetime = add4ByteLifetimeOption(optionType, optionLength);
builder.updateRouteInfoLifetime(lifetime);
break;
case ICMP6_DNSSL_OPTION_TYPE:
- lastNonLifetimeStart = addNonLifetimeU32(lastNonLifetimeStart);
- lifetime = getUint32(mPacket, position + ICMP6_4_BYTE_LIFETIME_OFFSET);
+ lifetime = add4ByteLifetimeOption(optionType, optionLength);
builder.updateDnsslLifetime(lifetime);
break;
default:
- // RFC4861 section 4.2 dictates we ignore unknown options for fowards
+ // RFC4861 section 4.2 dictates we ignore unknown options for forwards
// compatibility.
+ mPacket.position(position + optionLength);
break;
}
if (optionLength <= 0) {
throw new InvalidRaException(String.format(
"Invalid option length opt=%d len=%d", optionType, optionLength));
}
- mPacket.position(position + optionLength);
}
- // Mark non-lifetime bytes since last lifetime.
- addNonLifetime(lastNonLifetimeStart, 0, 0);
- mMinLifetime = minLifetime(packet, length);
+ mMinLifetime = minLifetime();
mMetricsLog.log(builder.build());
}
- // Ignoring lifetimes (which may change) does {@code packet} match this RA?
+ // Considering only the MATCH sections, does {@code packet} match this RA?
boolean matches(byte[] packet, int length) {
if (length != mPacket.capacity()) return false;
byte[] referencePacket = mPacket.array();
- for (Pair<Integer, Integer> nonLifetime : mNonLifetimes) {
- for (int i = nonLifetime.first; i < (nonLifetime.first + nonLifetime.second); i++) {
+ for (PacketSection section : mPacketSections) {
+ if (section.type != PacketSection.Type.MATCH) continue;
+ for (int i = section.start; i < (section.start + section.length); i++) {
if (packet[i] != referencePacket[i]) return false;
}
}
@@ -786,36 +857,12 @@ public class ApfFilter {
// What is the minimum of all lifetimes within {@code packet} in seconds?
// Precondition: matches(packet, length) already returned true.
- long minLifetime(byte[] packet, int length) {
+ long minLifetime() {
long minLifetime = Long.MAX_VALUE;
- // Wrap packet in ByteBuffer so we can read big-endian values easily
- ByteBuffer byteBuffer = ByteBuffer.wrap(packet);
- for (int i = 0; (i + 1) < mNonLifetimes.size(); i++) {
- int offset = mNonLifetimes.get(i).first + mNonLifetimes.get(i).second;
-
- // The flow label is in mNonLifetimes, but it's not a lifetime.
- if (offset == IPV6_FLOW_LABEL_OFFSET) {
- continue;
- }
-
- // The checksum is in mNonLifetimes, but it's not a lifetime.
- if (offset == ICMP6_RA_CHECKSUM_OFFSET) {
- continue;
- }
-
- final int lifetimeLength = mNonLifetimes.get(i+1).first - offset;
- final long optionLifetime;
- switch (lifetimeLength) {
- case 2:
- optionLifetime = getUint16(byteBuffer, offset);
- break;
- case 4:
- optionLifetime = getUint32(byteBuffer, offset);
- break;
- default:
- throw new IllegalStateException("bogus lifetime size " + lifetimeLength);
+ for (PacketSection section : mPacketSections) {
+ if (section.type == PacketSection.Type.LIFETIME) {
+ minLifetime = Math.min(minLifetime, section.lifetime);
}
- minLifetime = Math.min(minLifetime, optionLifetime);
}
return minLifetime;
}
@@ -844,38 +891,24 @@ public class ApfFilter {
// Skip filter if expired
gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
gen.addJumpIfR0GreaterThan(filterLifetime, nextFilterLabel);
- for (int i = 0; i < mNonLifetimes.size(); i++) {
- // Generate code to match the packet bytes
- Pair<Integer, Integer> nonLifetime = mNonLifetimes.get(i);
- // Don't generate JNEBS instruction for 0 bytes as it always fails the
- // ASSERT_FORWARD_IN_PROGRAM(pc + cmp_imm - 1) check where cmp_imm is
- // the number of bytes to compare. nonLifetime is zero between the
- // valid and preferred lifetimes in the prefix option.
- if (nonLifetime.second != 0) {
- gen.addLoadImmediate(Register.R0, nonLifetime.first);
+ for (PacketSection section : mPacketSections) {
+ // Generate code to match the packet bytes.
+ if (section.type == PacketSection.Type.MATCH) {
+ gen.addLoadImmediate(Register.R0, section.start);
gen.addJumpIfBytesNotEqual(Register.R0,
- Arrays.copyOfRange(mPacket.array(), nonLifetime.first,
- nonLifetime.first + nonLifetime.second),
+ Arrays.copyOfRange(mPacket.array(), section.start,
+ section.start + section.length),
nextFilterLabel);
}
- // Generate code to test the lifetimes haven't gone down too far
- if ((i + 1) < mNonLifetimes.size()) {
- Pair<Integer, Integer> nextNonLifetime = mNonLifetimes.get(i + 1);
- int offset = nonLifetime.first + nonLifetime.second;
-
- // Skip the Flow label.
- if (offset == IPV6_FLOW_LABEL_OFFSET) {
- continue;
- }
- // Skip the checksum.
- if (offset == ICMP6_RA_CHECKSUM_OFFSET) {
- continue;
- }
- int length = nextNonLifetime.first - offset;
- switch (length) {
- case 4: gen.addLoad32(Register.R0, offset); break;
- case 2: gen.addLoad16(Register.R0, offset); break;
- default: throw new IllegalStateException("bogus lifetime size " + length);
+ // Generate code to test the lifetimes haven't gone down too far.
+ // The packet is accepted if any of its lifetimes are lower than filterLifetime.
+ if (section.type == PacketSection.Type.LIFETIME) {
+ switch (section.length) {
+ case 4: gen.addLoad32(Register.R0, section.start); break;
+ case 2: gen.addLoad16(Register.R0, section.start); break;
+ default:
+ throw new IllegalStateException(
+ "bogus lifetime size " + section.length);
}
gen.addJumpIfR0LessThan(filterLifetime, nextFilterLabel);
}
@@ -1682,7 +1715,7 @@ public class ApfFilter {
if (VDBG) log("matched RA " + ra);
// Update lifetimes.
ra.mLastSeen = currentTimeSeconds();
- ra.mMinLifetime = ra.minLifetime(packet, length);
+ ra.mMinLifetime = ra.minLifetime();
ra.seenCount++;
// Keep mRas in LRU order so as to prioritize generating filters for recently seen
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index bf63c1c..e88c7cc 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -37,6 +37,7 @@ import static android.system.OsConstants.AF_PACKET;
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_DGRAM;
+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_BROADCAST;
@@ -46,6 +47,7 @@ import static android.system.OsConstants.SO_REUSEADDR;
import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.net.DhcpResults;
import android.net.InetAddresses;
@@ -60,7 +62,9 @@ import android.net.metrics.DhcpErrorEvent;
import android.net.metrics.IpConnectivityLog;
import android.net.util.InterfaceParams;
import android.net.util.NetworkStackUtils;
+import android.net.util.PacketReader;
import android.net.util.SocketUtils;
+import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.system.ErrnoException;
@@ -210,14 +214,9 @@ public class DhcpClient extends StateMachine {
private final Random mRandom;
private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
- // Sockets.
- // - We use a packet socket to receive, because servers send us packets bound for IP addresses
- // which we have not yet configured, and the kernel protocol stack drops these.
- // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
- // be off-link as well as on-link).
- private FileDescriptor mPacketSock;
+ // We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
+ // be off-link as well as on-link).
private FileDescriptor mUdpSock;
- private ReceiveThread mReceiveThread;
// State variables.
private final StateMachine mController;
@@ -244,6 +243,8 @@ public class DhcpClient extends StateMachine {
private Dependencies mDependencies;
@NonNull
private final NetworkStackIpMemoryStore mIpMemoryStore;
+ @Nullable
+ private DhcpPacketHandler mDhcpPacketHandler;
// Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState.
private long mLastInitEnterTime;
@@ -396,23 +397,6 @@ public class DhcpClient extends StateMachine {
mTransactionStartMillis = SystemClock.elapsedRealtime();
}
- private boolean initSockets() {
- return initPacketSocket() && initUdpSocket();
- }
-
- private boolean initPacketSocket() {
- try {
- mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
- SocketAddress addr = makePacketSocketAddress(ETH_P_IP, mIface.index);
- Os.bind(mPacketSock, addr);
- NetworkStackUtils.attachDhcpFilter(mPacketSock);
- } catch(SocketException|ErrnoException e) {
- Log.e(TAG, "Error creating packet socket", e);
- return false;
- }
- return true;
- }
-
private boolean initUdpSocket() {
final int oldTag = TrafficStats.getAndSetThreadStatsTag(
TrafficStatsConstants.TAG_SYSTEM_DHCP);
@@ -423,7 +407,7 @@ public class DhcpClient extends StateMachine {
Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);
Os.bind(mUdpSock, IPV4_ADDR_ANY, DhcpPacket.DHCP_CLIENT);
- } catch(SocketException|ErrnoException e) {
+ } catch (SocketException | ErrnoException e) {
Log.e(TAG, "Error creating UDP socket", e);
return false;
} finally {
@@ -436,59 +420,76 @@ public class DhcpClient extends StateMachine {
try {
Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER);
return true;
- } catch (SocketException|ErrnoException e) {
+ } catch (SocketException | ErrnoException e) {
Log.e(TAG, "Error connecting UDP socket", e);
return false;
}
}
- private void closeSockets() {
- closeSocketQuietly(mUdpSock);
- closeSocketQuietly(mPacketSock);
- }
+ private class DhcpPacketHandler extends PacketReader {
+ private FileDescriptor mPacketSock;
- class ReceiveThread extends Thread {
+ DhcpPacketHandler(Handler handler) {
+ super(handler);
+ }
- private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
- private volatile boolean mStopped = false;
+ @Override
+ protected void handlePacket(byte[] recvbuf, int length) {
+ try {
+ final DhcpPacket packet = DhcpPacket.decodeFullPacket(recvbuf, length,
+ DhcpPacket.ENCAP_L2);
+ if (DBG) Log.d(TAG, "Received packet: " + packet);
+ sendMessage(CMD_RECEIVED_PACKET, packet);
+ } catch (DhcpPacket.ParseException e) {
+ Log.e(TAG, "Can't parse packet: " + e.getMessage());
+ if (PACKET_DBG) {
+ Log.d(TAG, HexDump.dumpHexString(recvbuf, 0, length));
+ }
+ if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) {
+ final int snetTagId = 0x534e4554;
+ final String bugId = "31850211";
+ final int uid = -1;
+ final String data = DhcpPacket.ParseException.class.getName();
+ EventLog.writeEvent(snetTagId, bugId, uid, data);
+ }
+ mMetricsLog.log(mIfaceName, new DhcpErrorEvent(e.errorCode));
+ }
+ }
- public void halt() {
- mStopped = true;
- closeSockets(); // Interrupts the read() call the thread is blocked in.
+ @Override
+ protected FileDescriptor createFd() {
+ try {
+ mPacketSock = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0 /* protocol */);
+ NetworkStackUtils.attachDhcpFilter(mPacketSock);
+ final SocketAddress addr = makePacketSocketAddress(ETH_P_IP, mIface.index);
+ Os.bind(mPacketSock, addr);
+ } catch (SocketException | ErrnoException e) {
+ logError("Error creating packet socket", e);
+ closeFd(mPacketSock);
+ mPacketSock = null;
+ return null;
+ }
+ return mPacketSock;
}
@Override
- public void run() {
- if (DBG) Log.d(TAG, "Receive thread started");
- while (!mStopped) {
- int length = 0; // Or compiler can't tell it's initialized if a parse error occurs.
- try {
- length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
- DhcpPacket packet = null;
- packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
- if (DBG) Log.d(TAG, "Received packet: " + packet);
- sendMessage(CMD_RECEIVED_PACKET, packet);
- } catch (IOException|ErrnoException e) {
- if (!mStopped) {
- Log.e(TAG, "Read error", e);
- logError(DhcpErrorEvent.RECEIVE_ERROR);
- }
- } catch (DhcpPacket.ParseException e) {
- Log.e(TAG, "Can't parse packet: " + e.getMessage());
- if (PACKET_DBG) {
- Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
- }
- if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) {
- int snetTagId = 0x534e4554;
- String bugId = "31850211";
- int uid = -1;
- String data = DhcpPacket.ParseException.class.getName();
- EventLog.writeEvent(snetTagId, bugId, uid, data);
- }
- logError(e.errorCode);
- }
+ protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception {
+ try {
+ return Os.read(fd, packetBuffer, 0, packetBuffer.length);
+ } catch (IOException | ErrnoException e) {
+ mMetricsLog.log(mIfaceName, new DhcpErrorEvent(DhcpErrorEvent.RECEIVE_ERROR));
+ throw e;
}
- if (DBG) Log.d(TAG, "Receive thread stopped");
+ }
+
+ @Override
+ protected void logError(@NonNull String msg, @Nullable Exception e) {
+ Log.e(TAG, msg, e);
+ }
+
+ public int transmitPacket(final ByteBuffer buf, final SocketAddress socketAddress)
+ throws ErrnoException, SocketException {
+ return Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, socketAddress);
}
}
@@ -500,7 +501,7 @@ public class DhcpClient extends StateMachine {
try {
if (encap == DhcpPacket.ENCAP_L2) {
if (DBG) Log.d(TAG, "Broadcasting " + description);
- Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
+ mDhcpPacketHandler.transmitPacket(buf, mInterfaceBroadcastAddr);
} else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) {
if (DBG) Log.d(TAG, "Broadcasting " + description);
// We only send L3-encapped broadcasts in DhcpRebindingState,
@@ -517,7 +518,7 @@ public class DhcpClient extends StateMachine {
description, Os.getpeername(mUdpSock)));
Os.write(mUdpSock, buf);
}
- } catch(ErrnoException|IOException e) {
+ } catch (ErrnoException | IOException e) {
Log.e(TAG, "Can't send packet: ", e);
return false;
}
@@ -774,21 +775,22 @@ public class DhcpClient extends StateMachine {
@Override
public void enter() {
clearDhcpState();
- if (initInterface() && initSockets()) {
- mReceiveThread = new ReceiveThread();
- mReceiveThread.start();
- } else {
- notifyFailure();
- transitionTo(mStoppedState);
+ if (initInterface() && initUdpSocket()) {
+ mDhcpPacketHandler = new DhcpPacketHandler(getHandler());
+ if (mDhcpPacketHandler.start()) return;
+ Log.e(TAG, "Fail to start DHCP Packet Handler");
}
+ notifyFailure();
+ transitionTo(mStoppedState);
}
@Override
public void exit() {
- if (mReceiveThread != null) {
- mReceiveThread.halt(); // Also closes sockets.
- mReceiveThread = null;
+ if (mDhcpPacketHandler != null) {
+ mDhcpPacketHandler.stop();
+ if (DBG) Log.d(TAG, "DHCP Packet Handler stopped");
}
+ closeSocketQuietly(mUdpSock);
clearDhcpState();
}
@@ -1289,10 +1291,6 @@ public class DhcpClient extends StateMachine {
class DhcpRebootingState extends LoggingState {
}
- private void logError(int errorCode) {
- mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode));
- }
-
private void logState(String name, int durationMs) {
final DhcpClientEvent event = new DhcpClientEvent.Builder()
.setMsg(name)
diff --git a/src/android/net/util/FdEventsReader.java b/src/android/net/util/FdEventsReader.java
index 1380ea7..e82c69b 100644
--- a/src/android/net/util/FdEventsReader.java
+++ b/src/android/net/util/FdEventsReader.java
@@ -63,7 +63,6 @@ import java.io.IOException;
* the Handler constructor argument is associated.
*
* @param <BufferType> the type of the buffer used to read data.
- * @hide
*/
public abstract class FdEventsReader<BufferType> {
private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
@@ -93,27 +92,21 @@ public abstract class FdEventsReader<BufferType> {
}
/** Start this FdEventsReader. */
- public void start() {
- if (onCorrectThread()) {
- createAndRegisterFd();
- } else {
- mHandler.post(() -> {
- logError("start() called from off-thread", null);
- createAndRegisterFd();
- });
+ public boolean start() {
+ if (!onCorrectThread()) {
+ throw new IllegalStateException("start() called from off-thread");
}
+
+ return createAndRegisterFd();
}
/** Stop this FdEventsReader and destroy the file descriptor. */
public void stop() {
- if (onCorrectThread()) {
- unregisterAndDestroyFd();
- } else {
- mHandler.post(() -> {
- logError("stop() called from off-thread", null);
- unregisterAndDestroyFd();
- });
+ if (!onCorrectThread()) {
+ throw new IllegalStateException("stop() called from off-thread");
}
+
+ unregisterAndDestroyFd();
}
@NonNull
@@ -178,8 +171,8 @@ public abstract class FdEventsReader<BufferType> {
*/
protected void onStop() {}
- private void createAndRegisterFd() {
- if (mFd != null) return;
+ private boolean createAndRegisterFd() {
+ if (mFd != null) return true;
try {
mFd = createFd();
@@ -189,7 +182,7 @@ public abstract class FdEventsReader<BufferType> {
mFd = null;
}
- if (mFd == null) return;
+ if (mFd == null) return false;
mQueue.addOnFileDescriptorEventListener(
mFd,
@@ -205,6 +198,7 @@ public abstract class FdEventsReader<BufferType> {
return FD_EVENTS;
});
onStart();
+ return true;
}
private boolean isRunning() {
diff --git a/src/android/net/util/PacketReader.java b/src/android/net/util/PacketReader.java
index 4aec6b6..0be7187 100644
--- a/src/android/net/util/PacketReader.java
+++ b/src/android/net/util/PacketReader.java
@@ -28,8 +28,6 @@ import java.io.FileDescriptor;
*
* TODO: rename this class to something more correctly descriptive (something
* like [or less horrible than] FdReadEventsHandler?).
- *
- * @hide
*/
public abstract class PacketReader extends FdEventsReader<byte[]> {
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 585e38e..e08170a 100644
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -368,7 +368,13 @@ public class NetworkMonitor extends StateMachine {
} catch (RemoteException e) {
version = 0;
}
- if (version == Build.VERSION_CODES.CUR_DEVELOPMENT) version = 0;
+ // The AIDL was freezed from Q beta 5 but it's unfreezing from R before releasing. In order
+ // to distinguish the behavior between R and Q beta 5 and before Q beta 5, add SDK and
+ // CODENAME check here. Basically, it's only expected to return 0 for Q beta 4 and below
+ // because the test result has changed.
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q
+ && Build.VERSION.CODENAME.equals("REL")
+ && version == Build.VERSION_CODES.CUR_DEVELOPMENT) version = 0;
return version;
}
@@ -555,6 +561,14 @@ public class NetworkMonitor extends StateMachine {
}
}
+ private void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) {
+ try {
+ mCallback.notifyProbeStatusChanged(probesCompleted, probesSucceeded);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending probe status", e);
+ }
+ }
+
private void showProvisioningNotification(String action) {
try {
mCallback.showProvisioningNotification(action, mContext.getPackageName());
@@ -667,6 +681,8 @@ public class NetworkMonitor extends StateMachine {
// no resolved IP addresses, IPs unreachable,
// port 853 unreachable, port 853 is not running a
// DNS-over-TLS server, et cetera).
+ // Cancel any outstanding CMD_EVALUATE_PRIVATE_DNS.
+ removeMessages(CMD_EVALUATE_PRIVATE_DNS);
sendMessage(CMD_EVALUATE_PRIVATE_DNS);
break;
}
@@ -1020,11 +1036,19 @@ public class NetworkMonitor extends StateMachine {
handlePrivateDnsEvaluationFailure();
break;
}
+ handlePrivateDnsEvaluationSuccess();
+ } else {
+ mEvaluationState.removeProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS);
}
// All good!
transitionTo(mValidatedState);
break;
+ case CMD_PRIVATE_DNS_SETTINGS_CHANGED:
+ // When settings change the reevaluation timer must be reset.
+ mPrivateDnsReevalDelayMs = INITIAL_REEVALUATE_DELAY_MS;
+ // Let the message bubble up and be handled by parent states as usual.
+ return NOT_HANDLED;
default:
return NOT_HANDLED;
}
@@ -1051,8 +1075,6 @@ public class NetworkMonitor extends StateMachine {
} catch (UnknownHostException uhe) {
mPrivateDnsConfig = null;
}
- mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS,
- (mPrivateDnsConfig != null) /* succeeded */);
}
private void notifyPrivateDnsConfigResolved() {
@@ -1063,7 +1085,14 @@ public class NetworkMonitor extends StateMachine {
}
}
+ private void handlePrivateDnsEvaluationSuccess() {
+ mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS,
+ true /* succeeded */);
+ }
+
private void handlePrivateDnsEvaluationFailure() {
+ mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS,
+ false /* succeeded */);
mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
null /* redirectUrl */);
// Queue up a re-evaluation with backoff.
@@ -1072,10 +1101,6 @@ public class NetworkMonitor extends StateMachine {
// transitioning back to EvaluatingState, to perhaps give ourselves
// the opportunity to (re)detect a captive portal or something.
//
- // TODO: distinguish between CMD_EVALUATE_PRIVATE_DNS messages that are caused by server
- // lookup failures (which should continue to do exponential backoff) and
- // CMD_EVALUATE_PRIVATE_DNS messages that are caused by user reconfiguration (which
- // should be processed immediately.
sendMessageDelayed(CMD_EVALUATE_PRIVATE_DNS, mPrivateDnsReevalDelayMs);
mPrivateDnsReevalDelayMs *= 2;
if (mPrivateDnsReevalDelayMs > MAX_REEVALUATE_DELAY_MS) {
@@ -1101,7 +1126,6 @@ public class NetworkMonitor extends StateMachine {
String.format("%dms - Error: %s", time, uhe.getMessage()));
}
logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
- mEvaluationState.noteProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, success);
return success;
}
}
@@ -2105,22 +2129,43 @@ public class NetworkMonitor extends StateMachine {
// Indicates which probes have completed since clearProbeResults was called.
// This is a bitmask of INetworkMonitor.NETWORK_VALIDATION_PROBE_* constants.
private int mProbeResults = 0;
+ // A bitmask to record which probes are completed.
+ private int mProbeCompleted = 0;
// The latest redirect URL.
private String mRedirectUrl;
protected void clearProbeResults() {
mProbeResults = 0;
+ mProbeCompleted = 0;
}
- // Probe result for http probe should be updated from reportHttpProbeResult().
- protected void noteProbeResult(int probeResult, boolean succeeded) {
- if (succeeded) {
- mProbeResults |= probeResult;
- } else {
- mProbeResults &= ~probeResult;
+ private void maybeNotifyProbeResults(@NonNull final Runnable modif) {
+ final int oldCompleted = mProbeCompleted;
+ final int oldResults = mProbeResults;
+ modif.run();
+ if (oldCompleted != mProbeCompleted || oldResults != mProbeResults) {
+ notifyProbeStatusChanged(mProbeCompleted, mProbeResults);
}
}
+ protected void removeProbeResult(final int probeResult) {
+ maybeNotifyProbeResults(() -> {
+ mProbeCompleted &= ~probeResult;
+ mProbeResults &= ~probeResult;
+ });
+ }
+
+ protected void noteProbeResult(final int probeResult, final boolean succeeded) {
+ maybeNotifyProbeResults(() -> {
+ mProbeCompleted |= probeResult;
+ if (succeeded) {
+ mProbeResults |= probeResult;
+ } else {
+ mProbeResults &= ~probeResult;
+ }
+ });
+ }
+
protected void reportEvaluationResult(int result, @Nullable String redirectUrl) {
mEvaluationResult = result;
mRedirectUrl = redirectUrl;
@@ -2139,6 +2184,11 @@ public class NetworkMonitor extends StateMachine {
}
return mEvaluationResult | mProbeResults;
}
+
+ @VisibleForTesting
+ protected int getProbeCompletedResult() {
+ return mProbeCompleted;
+ }
}
@VisibleForTesting