diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/android/net/apf/ApfFilter.java | 269 | ||||
-rw-r--r-- | src/android/net/dhcp/DhcpClient.java | 160 | ||||
-rw-r--r-- | src/android/net/util/FdEventsReader.java | 32 | ||||
-rw-r--r-- | src/android/net/util/PacketReader.java | 2 | ||||
-rw-r--r-- | src/com/android/server/connectivity/NetworkMonitor.java | 78 |
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 |