Add DHCP error event class and record DHCP errors.

This patch adds an event class derived from IpConnectivityEvent that
records parsing errors of DHCP response packets.

Change-Id: I19516cf05e3419c4262e3236899e52987e5f2264
diff --git a/core/java/android/net/metrics/DhcpErrorEvent.java b/core/java/android/net/metrics/DhcpErrorEvent.java
new file mode 100644
index 0000000..3869823
--- /dev/null
+++ b/core/java/android/net/metrics/DhcpErrorEvent.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.metrics;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * {@hide} Event class used to record error events when parsing DHCP response packets.
+ */
+public class DhcpErrorEvent extends IpConnectivityEvent implements Parcelable {
+    public static final String TAG = "DhcpErrorEvent";
+
+    public static final int L2_ERROR   = 1;
+    public static final int L3_ERROR   = 2;
+    public static final int L4_ERROR   = 3;
+    public static final int DHCP_ERROR = 4;
+    public static final int MISC_ERROR = 5;
+
+    public static final int L2_TOO_SHORT               = makeErrorCode(L2_ERROR, 1);
+    public static final int L2_WRONG_ETH_TYPE          = makeErrorCode(L2_ERROR, 2);
+
+    public static final int L3_TOO_SHORT               = makeErrorCode(L3_ERROR, 1);
+    public static final int L3_NOT_IPV4                = makeErrorCode(L3_ERROR, 2);
+    public static final int L3_INVALID_IP              = makeErrorCode(L3_ERROR, 3);
+
+    public static final int L4_NOT_UDP                 = makeErrorCode(L4_ERROR, 1);
+    public static final int L4_WRONG_PORT              = makeErrorCode(L4_ERROR, 2);
+
+    public static final int BOOTP_TOO_SHORT            = makeErrorCode(DHCP_ERROR, 1);
+    public static final int DHCP_BAD_MAGIC_COOKIE      = makeErrorCode(DHCP_ERROR, 2);
+    public static final int DHCP_INVALID_OPTION_LENGTH = makeErrorCode(DHCP_ERROR, 3);
+    public static final int DHCP_NO_MSG_TYPE           = makeErrorCode(DHCP_ERROR, 4);
+    public static final int DHCP_UNKNOWN_MSG_TYPE      = makeErrorCode(DHCP_ERROR, 5);
+
+    public static final int BUFFER_UNDERFLOW           = makeErrorCode(MISC_ERROR, 1);
+
+    // error code byte format (MSB to LSB):
+    // byte 0: error type
+    // byte 1: error subtype
+    // byte 2: unused
+    // byte 3: optional code
+    public final int errorCode;
+
+    private DhcpErrorEvent(int errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    private DhcpErrorEvent(Parcel in) {
+        this.errorCode = in.readInt();
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(errorCode);
+    }
+
+    public static final Parcelable.Creator<DhcpErrorEvent> CREATOR
+        = new Parcelable.Creator<DhcpErrorEvent>() {
+        public DhcpErrorEvent createFromParcel(Parcel in) {
+            return new DhcpErrorEvent(in);
+        }
+
+        public DhcpErrorEvent[] newArray(int size) {
+            return new DhcpErrorEvent[size];
+        }
+    };
+
+    public static void logEvent(int errorCode) {
+        IpConnectivityEvent.logEvent(IPCE_DHCP_PARSE_ERROR, new DhcpErrorEvent(errorCode));
+    }
+
+    public static void logEvent(int errorCode, int option) {
+        logEvent((0xFFFF0000 & errorCode) | (0xFF & option));
+    }
+
+    private static int makeErrorCode(int type, int subtype) {
+        return (type << 24) | ((0xFF & subtype) << 16);
+    }
+}
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
index a881408..b184fe5 100644
--- a/services/net/java/android/net/dhcp/DhcpPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -3,6 +3,7 @@
 import android.net.DhcpResults;
 import android.net.LinkAddress;
 import android.net.NetworkUtils;
+import android.net.metrics.DhcpErrorEvent;
 import android.os.Build;
 import android.os.SystemProperties;
 import android.system.OsConstants;
@@ -765,6 +766,7 @@
         // check to see if we need to parse L2, IP, and UDP encaps
         if (pktType == ENCAP_L2) {
             if (packet.remaining() < MIN_PACKET_LENGTH_L2) {
+                DhcpErrorEvent.logEvent(DhcpErrorEvent.L2_TOO_SHORT);
                 throw new ParseException("L2 packet too short, %d < %d",
                         packet.remaining(), MIN_PACKET_LENGTH_L2);
             }
@@ -777,13 +779,16 @@
 
             short l2type = packet.getShort();
 
-            if (l2type != OsConstants.ETH_P_IP)
+            if (l2type != OsConstants.ETH_P_IP) {
+                DhcpErrorEvent.logEvent(DhcpErrorEvent.L2_WRONG_ETH_TYPE);
                 throw new ParseException("Unexpected L2 type 0x%04x, expected 0x%04x",
                         l2type, OsConstants.ETH_P_IP);
+            }
         }
 
         if (pktType <= ENCAP_L3) {
             if (packet.remaining() < MIN_PACKET_LENGTH_L3) {
+                DhcpErrorEvent.logEvent(DhcpErrorEvent.L3_TOO_SHORT);
                 throw new ParseException("L3 packet too short, %d < %d",
                         packet.remaining(), MIN_PACKET_LENGTH_L3);
             }
@@ -791,6 +796,7 @@
             byte ipTypeAndLength = packet.get();
             int ipVersion = (ipTypeAndLength & 0xf0) >> 4;
             if (ipVersion != 4) {
+                DhcpErrorEvent.logEvent(DhcpErrorEvent.L3_NOT_IPV4);
                 throw new ParseException("Invalid IP version %d", ipVersion);
             }
 
@@ -808,6 +814,7 @@
             ipDst = readIpAddress(packet);
 
             if (ipProto != IP_TYPE_UDP) {
+                DhcpErrorEvent.logEvent(DhcpErrorEvent.L4_NOT_UDP);
                 throw new ParseException("Protocol not UDP: %d", ipProto);
             }
 
@@ -834,12 +841,14 @@
                 // socket to drop packets that don't have the right source ports. However, it's
                 // possible that a packet arrives between when the socket is bound and when the
                 // filter is set. http://b/26696823 .
+                DhcpErrorEvent.logEvent(DhcpErrorEvent.L4_WRONG_PORT);
                 throw new ParseException("Unexpected UDP ports %d->%d", udpSrcPort, udpDstPort);
             }
         }
 
         // We need to check the length even for ENCAP_L3 because the IPv4 header is variable-length.
         if (pktType > ENCAP_BOOTP || packet.remaining() < MIN_PACKET_LENGTH_BOOTP) {
+            DhcpErrorEvent.logEvent(DhcpErrorEvent.BOOTP_TOO_SHORT);
             throw new ParseException("Invalid type or BOOTP packet too short, %d < %d",
                         packet.remaining(), MIN_PACKET_LENGTH_BOOTP);
         }
@@ -864,6 +873,7 @@
             packet.get(ipv4addr);
             relayIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr);
         } catch (UnknownHostException ex) {
+            DhcpErrorEvent.logEvent(DhcpErrorEvent.L3_INVALID_IP);
             throw new ParseException("Invalid IPv4 address: %s", Arrays.toString(ipv4addr));
         }
 
@@ -888,6 +898,7 @@
         int dhcpMagicCookie = packet.getInt();
 
         if (dhcpMagicCookie != DHCP_MAGIC_COOKIE) {
+            DhcpErrorEvent.logEvent(DhcpErrorEvent.DHCP_BAD_MAGIC_COOKIE);
             throw new ParseException("Bad magic cookie 0x%08x, should be 0x%08x", dhcpMagicCookie,
                     DHCP_MAGIC_COOKIE);
         }
@@ -896,9 +907,8 @@
         boolean notFinishedOptions = true;
 
         while ((packet.position() < packet.limit()) && notFinishedOptions) {
+            final byte optionType = packet.get(); // cannot underflow because position < limit
             try {
-                byte optionType = packet.get();
-
                 if (optionType == DHCP_OPTION_END) {
                     notFinishedOptions = false;
                 } else if (optionType == DHCP_OPTION_PAD) {
@@ -999,11 +1009,14 @@
                     }
 
                     if (expectedLen != optionLen) {
+                        DhcpErrorEvent.logEvent(
+                                DhcpErrorEvent.DHCP_INVALID_OPTION_LENGTH, optionType);
                         throw new ParseException("Invalid length %d for option %d, expected %d",
                                 optionLen, optionType, expectedLen);
                     }
                 }
             } catch (BufferUnderflowException e) {
+                DhcpErrorEvent.logEvent(DhcpErrorEvent.BUFFER_UNDERFLOW, optionType);
                 throw new ParseException("BufferUnderflowException");
             }
         }
@@ -1012,6 +1025,7 @@
 
         switch(dhcpType) {
             case (byte) 0xFF:
+                DhcpErrorEvent.logEvent(DhcpErrorEvent.DHCP_NO_MSG_TYPE);
                 throw new ParseException("No DHCP message type option");
             case DHCP_MESSAGE_TYPE_DISCOVER:
                 newPacket = new DhcpDiscoverPacket(
@@ -1045,6 +1059,7 @@
                     clientMac);
                 break;
             default:
+                DhcpErrorEvent.logEvent(DhcpErrorEvent.DHCP_UNKNOWN_MSG_TYPE);
                 throw new ParseException("Unimplemented DHCP type %d", dhcpType);
         }