shill: Exposing wifi disconnect reason to UMA.

This change provides UMA with a reason for each WiFi deauthentication
that occurs.  These values are extracted from the deauthentication
message that we get from the mac80211 drivers in the kernel (because
we've asked that driver to send us events).

BUG=chromium-os:33637,26854
TEST=Manual (disconnected in various ways and verified with UMA).  Ran
shill unit tests.

Change-Id: I630ffbba02b40484fc3c6b20e444e71093cfce65
Reviewed-on: https://gerrit.chromium.org/gerrit/31475
Commit-Ready: Wade Guthrie <wdg@chromium.org>
Reviewed-by: Wade Guthrie <wdg@chromium.org>
Tested-by: Wade Guthrie <wdg@chromium.org>
diff --git a/callback80211_object.cc b/callback80211_object.cc
index d028558..589125b 100644
--- a/callback80211_object.cc
+++ b/callback80211_object.cc
@@ -9,8 +9,10 @@
 #include <base/memory/weak_ptr.h>
 
 #include "shill/config80211.h"
+#include "shill/ieee80211.h"
 #include "shill/link_monitor.h"
 #include "shill/logging.h"
+#include "shill/metrics.h"
 #include "shill/scope_logger.h"
 #include "shill/user_bound_nlmessage.h"
 
@@ -24,13 +26,8 @@
 LazyInstance<Callback80211Object> g_callback80211 = LAZY_INSTANCE_INITIALIZER;
 }  // namespace
 
-// static
-const char Callback80211Object::kMetricLinkDisconnectCount[] =
-    "Network.Shill.DisconnectCount";
-
-Callback80211Object::Callback80211Object() :
-    config80211_(NULL),
-    weak_ptr_factory_(this) {
+Callback80211Object::Callback80211Object()
+  : config80211_(NULL), metrics_(NULL), weak_ptr_factory_(this) {
 }
 
 Callback80211Object::~Callback80211Object() {
@@ -38,17 +35,38 @@
 }
 
 void Callback80211Object::Config80211MessageCallback(
-    const UserBoundNlMessage &msg) {
-  SLOG(WiFi, 2) << "Received " << msg.GetMessageTypeString()
-                << " (" << + msg.GetMessageType() << ")";
+    const UserBoundNlMessage &message) {
+  if (metrics_ && message.GetMessageType() == DeauthenticateMessage::kCommand) {
+    Metrics::WiFiDisconnectByWhom by_whom =
+        message.AttributeExists(NL80211_ATTR_DISCONNECTED_BY_AP) ?
+                    Metrics::kDisconnectedByAp : Metrics::kDisconnectedNotByAp;
+    uint16_t reason = static_cast<uint16_t>(
+        IEEE_80211::kReasonCodeInvalid);
+    void *rawdata = NULL;
+    int frame_byte_count = 0;
+    if (message.GetRawAttributeData(NL80211_ATTR_FRAME, &rawdata,
+                                    &frame_byte_count)) {
+      const uint8_t *frame_data = reinterpret_cast<const uint8_t *>(rawdata);
+      Nl80211Frame frame(frame_data, frame_byte_count);
+      reason = frame.reason();
+    }
+    IEEE_80211::WiFiReasonCode reason_enum =
+        static_cast<IEEE_80211::WiFiReasonCode>(reason);
+    metrics_->Notify80211Disconnect(by_whom, reason_enum);
+  }
+
+  // Now, print out the message.
+
+  SLOG(WiFi, 2) << "Received " << message.GetMessageTypeString()
+                << " (" << + message.GetMessageType() << ")";
   scoped_ptr<UserBoundNlMessage::AttributeNameIterator> i;
 
-  for (i.reset(msg.GetAttributeNameIterator()); !i->AtEnd(); i->Advance()) {
+  for (i.reset(message.GetAttributeNameIterator()); !i->AtEnd(); i->Advance()) {
     string value = "<unknown>";
-    msg.GetAttributeString(i->GetName(), &value);
-    SLOG(WiFi, 2) << "   Attr:" << msg.StringFromAttributeName(i->GetName())
+    message.GetAttributeString(i->GetName(), &value);
+    SLOG(WiFi, 2) << "   Attr:" << message.StringFromAttributeName(i->GetName())
                   << "=" << value
-                  << " Type:" << msg.GetAttributeTypeString(i->GetName());
+                  << " Type:" << message.GetAttributeTypeString(i->GetName());
   }
 }
 
diff --git a/callback80211_object.h b/callback80211_object.h
index d76ab84..18e3e06 100644
--- a/callback80211_object.h
+++ b/callback80211_object.h
@@ -16,10 +16,10 @@
 #include <base/bind.h>
 #include <base/lazy_instance.h>
 
-#include "shill/config80211.h"
-
 namespace shill {
 
+class Config80211;
+class Metrics;
 class UserBoundNlMessage;
 
 // Example Config80211 callback object; the callback prints a description of
@@ -43,6 +43,9 @@
   // Simple accessor.
   void set_config80211(Config80211 *config80211) { config80211_ = config80211; }
 
+  // Simple accessor.
+  void set_metrics(Metrics *metrics) { metrics_ = metrics; }
+
  protected:
   friend struct base::DefaultLazyInstanceTraits<Callback80211Object>;
 
@@ -55,6 +58,8 @@
 
   Config80211 *config80211_;
 
+  Metrics *metrics_;
+
   // Config80211MessageCallback method bound to this object to install as a
   // callback.
   base::WeakPtrFactory<Callback80211Object> weak_ptr_factory_;
diff --git a/config80211.cc b/config80211.cc
index 64a7b84..252bdca 100644
--- a/config80211.cc
+++ b/config80211.cc
@@ -123,8 +123,6 @@
   if (new_state == kWifiUp) {
     SubscribedEvents::const_iterator i;
     for (i = subscribed_events_.begin(); i != subscribed_events_.end(); ++i) {
-      string event_type_string;
-      GetEventTypeString(*i, &event_type_string);
       ActuallySubscribeToEvents(*i);
     }
   }
@@ -132,8 +130,6 @@
 }
 
 bool Config80211::SubscribeToEvents(EventType type) {
-  string event_type_string;
-  GetEventTypeString(type, &event_type_string);
   bool it_worked = true;
   if (!ContainsKey(subscribed_events_, type)) {
     if (wifi_state_ == kWifiUp) {
@@ -149,8 +145,6 @@
 
 bool Config80211::ActuallySubscribeToEvents(EventType type) {
   string group_name;
-  string event_type_string;
-  GetEventTypeString(type, &event_type_string);
 
   if (!GetEventTypeString(type, &group_name)) {
     return false;
diff --git a/config80211.h b/config80211.h
index cf00efc..2af1bc7 100644
--- a/config80211.h
+++ b/config80211.h
@@ -100,8 +100,8 @@
 
   // This represents whether the cfg80211/mac80211 are installed in the kernel.
   enum WifiState {
-    kWifiUp=10,
-    kWifiDown=20
+    kWifiUp,
+    kWifiDown
   };
 
   virtual ~Config80211();
@@ -149,6 +149,8 @@
 
  private:
   friend class Config80211Test;
+  typedef std::map<EventType, std::string> EventTypeStrings;
+  typedef std::set<EventType> SubscribedEvents;
 
   // Sign-up to receive and log multicast events of a specific type (assumes
   // wifi is up).
@@ -174,12 +176,10 @@
   // TODO(wdg): implement the following.
   // std::map<uint32_t, Callback> message_callback_;
 
-  typedef std::map<EventType, std::string> EventTypeStrings;
   static EventTypeStrings *event_types_;
 
   WifiState wifi_state_;
 
-  typedef std::set<EventType> SubscribedEvents;
   SubscribedEvents subscribed_events_;
 
   // Hooks needed to be called by shill's EventDispatcher.
diff --git a/ieee80211.h b/ieee80211.h
index ee52e02..cbbee26 100644
--- a/ieee80211.h
+++ b/ieee80211.h
@@ -62,60 +62,93 @@
 
 // Status/reason code returned by nl80211 messages: Authenticate,
 // Deauthenticate, Associate, and Reassociate.
-enum ConnectStatus {
-  kConnectStatusSuccessful = 0,
-  kConnectStatusFailure = 1,
-  kConnectStatusNoLongerValid = 2,
-  kConnectStatusSenderHasLeft = 3,
-  kConnectStatusNonAuthenticated = 7,
-  kConnectStatusAllCapabilitiesNotSupported = 10,
-  kConnectStatusCantConfirmAssociation = 11,
-  kConnectStatusAssociationDenied = 12,
-  kConnectStatusAuthenticationUnsupported = 13,
-  kConnectStatusOutOfSequence = 14,
-  kConnectStatusChallengeFailure = 15,
-  kConnectStatusFrameTimeout = 16,
-  kConnectStatusMaxSta = 17,
-  kConnectStatusDataRateUnsupported = 18,
-  kConnectStatusShortPreambleUnsupported = 19,
-  kConnectStatusPbccUnsupported = 20,
-  kConnectStatusChannelAgilityUnsupported = 21,
-  kConnectStatusNeedSpectrumManagement = 22,
-  kConnectStatusUnacceptablePowerCapability = 23,
-  kConnectStatusUnacceptableSupportedChannelInfo = 24,
-  kConnectStatusShortTimeSlotRequired = 25,
-  kConnectStatusErPbccRequired = 26,
-  kConnectStatusHtFeaturesRequired = 27,
-  kConnectStatusR0khUnreachable = 28,
-  kConnectStatusPcoTransitionRequired = 29,
-  kConnectStatusRejectedTemporarily = 30,
-  kConnectStatusRobustPolicyViolated = 31,
-  kConnectStatusQosFailure = 32,
-  kConnectStatusInsufficientBandwithForQsta = 33,
-  kConnectStatusPoorConditions = 34,
-  kConnectStatusQosNotSupported = 35,
-  kConnectStatusDeclined = 37,
-  kConnectStatusInvalidParameterValues = 38,
-  kConnectStatusCannotBeHonored = 39,
-  kConnectStatusInvalidInfoElement = 40,
-  kConnectStatusGroupCipherInvalid = 41,
-  kConnectStatusPairwiseCipherInvalid = 42,
-  kConnectStatusAkmpInvalid = 43,
-  kConnectStatusUnsupportedRsnIeVersion = 44,
-  kConnectStatusInvalidRsnIeCaps = 45,
-  kConnectStatusCipherSuiteRejected = 46,
-  kConnectStatusTsDelayNotMet = 47,
-  kConnectStatusDirectLinkIllegal = 48,
-  kConnectStatusStaNotInQbss = 49,
-  kConnectStatusStaNotInQsta = 50,
-  kConnectStatusExcessiveListenInterval = 51,
-  kConnectStatusInvalidFastBssFrameCount = 52,
-  kConnectStatusInvalidPmkid = 53,
-  kConnectStatusInvalidMdie = 54,
-  kConnectStatusInvalidFtie = 55,
+enum WiFiReasonCode {
+  // 0 is reserved.
+  kReasonCodeUnspecified = 1,
+  kReasonCodePreviousAuthenticationInvalid = 2,
+  kReasonCodeSenderHasLeft = 3,
+  kReasonCodeInactivity = 4,
+  kReasonCodeTooManySTAs = 5,
+  kReasonCodeNonAuthenticated = 6,
+  kReasonCodeNonAssociated = 7,
+  kReasonCodeDisassociatedHasLeft = 8,
+  kReasonCodeReassociationNotAuthenticated = 9,
+  kReasonCodeUnacceptablePowerCapability = 10,
+  kReasonCodeUnacceptableSupportedChannelInfo = 11,
+  // 12 is reserved.
+  kReasonCodeInvalidInfoElement = 13,
+  kReasonCodeMICFailure = 14,
+  kReasonCode4WayTimeout = 15,
+  kReasonCodeGroupKeyHandshakeTimeout = 16,
+  kReasonCodeDifferenIE = 17,
+  kReasonCodeGroupCipherInvalid = 18,
+  kReasonCodePairwiseCipherInvalid = 19,
+  kReasonCodeAkmpInvalid = 20,
+  kReasonCodeUnsupportedRsnIeVersion = 21,
+  kReasonCodeInvalidRsnIeCaps = 22,
+  kReasonCode8021XAuth = 23,
+  kReasonCodeCipherSuiteRejected = 24,
+  // 25-31 are reserved.
+  kReasonCodeUnspecifiedQoS = 32,
+  kReasonCodeQoSBandwidth = 33,
+  kReasonCodeiPoorConditions = 34,
+  kReasonCodeOutsideTxop = 35,
+  kReasonCodeStaLeaving = 36,
+  kReasonCodeUnacceptableMechanism = 37,
+  kReasonCodeSetupRequired = 38,
+  kReasonCodeTimeout = 39,
+  kReasonCodeCipherSuiteNotSupported = 45,
+  kReasonCodeMax,
+  kReasonCodeInvalid = UINT16_MAX
 };
 
-}
+enum WiFiStatusCode {
+  kStatusCodeSuccessful = 0,
+  kStatusCodeFailure = 1,
+  // 2-9 are reserved.
+  kStatusCodeAllCapabilitiesNotSupported = 10,
+  kStatusCodeCantConfirmAssociation = 11,
+  kStatusCodeAssociationDenied = 12,
+  kStatusCodeAuthenticationUnsupported = 13,
+  kStatusCodeOutOfSequence = 14,
+  kStatusCodeChallengeFailure = 15,
+  kStatusCodeFrameTimeout = 16,
+  kStatusCodeMaxSta = 17,
+  kStatusCodeDataRateUnsupported = 18,
+  kStatusCodeShortPreambleUnsupported = 19,
+  kStatusCodePbccUnsupported = 20,
+  kStatusCodeChannelAgilityUnsupported = 21,
+  kStatusCodeNeedSpectrumManagement = 22,
+  kStatusCodeUnacceptablePowerCapability = 23,
+  kStatusCodeUnacceptableSupportedChannelInfo = 24,
+  kStatusCodeShortTimeSlotRequired = 25,
+  kStatusCodeDssOfdmRequired = 26,
+  // 27-31 are reserved.
+  kStatusCodeQosFailure = 32,
+  kStatusCodeInsufficientBandwithForQsta = 33,
+  kStatusCodePoorConditions = 34,
+  kStatusCodeQosNotSupported = 35,
+  // 36 is reserved.
+  kStatusCodeDeclined = 37,
+  kStatusCodeInvalidParameterValues = 38,
+  kStatusCodeCannotBeHonored = 39,
+  kStatusCodeInvalidInfoElement = 40,
+  kStatusCodeGroupCipherInvalid = 41,
+  kStatusCodePairwiseCipherInvalid = 42,
+  kStatusCodeAkmpInvalid = 43,
+  kStatusCodeUnsupportedRsnIeVersion = 44,
+  kStatusCodeInvalidRsnIeCaps = 45,
+  kStatusCodeCipherSuiteRejected = 46,
+  kStatusCodeTsDelayNotMet = 47,
+  kStatusCodeDirectLinkIllegal = 48,
+  kStatusCodeStaNotInBss = 49,
+  kStatusCodeStaNotInQsta = 50,
+  kStatusCodeExcessiveListenInterval = 51,
+  kStatusCodeMax,
+  kStatusCodeInvalid = UINT16_MAX
+};
+
+}  // namespace IEEE_80211
 
 }  // namespace shill
 
diff --git a/metrics.cc b/metrics.cc
index dc83a50..9bc7e9e 100644
--- a/metrics.cc
+++ b/metrics.cc
@@ -9,6 +9,7 @@
 #include <chromeos/dbus/service_constants.h>
 #include <metrics/bootstat.h>
 
+#include "shill/ieee80211.h"
 #include "shill/link_monitor.h"
 #include "shill/logging.h"
 #include "shill/wifi_service.h"
@@ -124,6 +125,17 @@
 const int Metrics::kMetricLinkMonitorErrorCountNumBuckets =
     LinkMonitor::kFailureThreshold + 1;
 
+// static
+const char Metrics::kMetricLinkClientDisconnectReason[] =
+    "Network.Shill.WiFi.ClientDisconnectReason";
+const char Metrics::kMetricLinkApDisconnectReason[] =
+    "Network.Shill.WiFi.ApDisconnectReason";
+const char Metrics::kMetricLinkClientDisconnectType[] =
+    "Network.Shill.WiFi.ClientDisconnectType";
+const char Metrics::kMetricLinkApDisconnectType[] =
+    "Network.Shill.WiFi.ApDisconnectType";
+
+
 Metrics::Metrics()
     : library_(&metrics_library_),
       last_default_technology_(Technology::kUnknown),
@@ -451,6 +463,39 @@
             kMetricLinkMonitorResponseTimeSampleNumBuckets);
 }
 
+void Metrics::Notify80211Disconnect(WiFiDisconnectByWhom by_whom,
+                                    IEEE_80211::WiFiReasonCode reason) {
+  string metric_disconnect_reason;
+  string metric_disconnect_type;
+  WiFiStatusType type;
+
+  if (by_whom == kDisconnectedByAp) {
+    metric_disconnect_reason = kMetricLinkApDisconnectReason;
+    metric_disconnect_type = kMetricLinkApDisconnectType;
+    type = kStatusCodeTypeByAp;
+  } else {
+    metric_disconnect_reason = kMetricLinkClientDisconnectReason;
+    metric_disconnect_type = kMetricLinkClientDisconnectType;
+    switch(reason) {
+      case IEEE_80211::kReasonCodeSenderHasLeft:
+      case IEEE_80211::kReasonCodeDisassociatedHasLeft:
+        type = kStatusCodeTypeByUser;
+        break;
+
+      case IEEE_80211::kReasonCodeInactivity:
+        type = kStatusCodeTypeConsideredDead;
+        break;
+
+      default:
+        type = kStatusCodeTypeByClient;
+        break;
+    }
+  }
+  SendEnumToUMA(metric_disconnect_reason, reason,
+                IEEE_80211::kStatusCodeMax);
+  SendEnumToUMA(metric_disconnect_type, type, kStatusCodeTypeMax);
+}
+
 bool Metrics::SendEnumToUMA(const string &name, int sample, int max) {
   return library_->SendEnumToUMA(name, sample, max);
 }
diff --git a/metrics.h b/metrics.h
index 9e7ea0e..a433dd8 100644
--- a/metrics.h
+++ b/metrics.h
@@ -11,6 +11,7 @@
 #include <metrics/metrics_library.h>
 #include <metrics/timer.h>
 
+#include "shill/ieee80211.h"
 #include "shill/portal_detector.h"
 #include "shill/power_manager.h"
 #include "shill/refptr_types.h"
@@ -125,6 +126,19 @@
     kLinkMonitorFailureMax
   };
 
+  enum WiFiStatusType {
+    kStatusCodeTypeByAp,
+    kStatusCodeTypeByClient,
+    kStatusCodeTypeByUser,
+    kStatusCodeTypeConsideredDead,
+    kStatusCodeTypeMax
+  };
+
+  enum WiFiDisconnectByWhom {
+    kDisconnectedByAp,
+    kDisconnectedNotByAp
+  };
+
   static const char kMetricDisconnect[];
   static const int kMetricDisconnectMax;
   static const int kMetricDisconnectMin;
@@ -190,6 +204,11 @@
   static const unsigned int kMetricLinkMonitorErrorCountMax;
   static const int kMetricLinkMonitorErrorCountNumBuckets;
 
+  static const char kMetricLinkClientDisconnectReason[];
+  static const char kMetricLinkApDisconnectReason[];
+  static const char kMetricLinkClientDisconnectType[];
+  static const char kMetricLinkApDisconnectType[];
+
   Metrics();
   virtual ~Metrics();
 
@@ -250,6 +269,10 @@
       Technology::Identifier technology,
       unsigned int response_time_milliseconds);
 
+  // Notifies this object of WiFi disconnect.
+  void Notify80211Disconnect(WiFiDisconnectByWhom by_whom,
+                             IEEE_80211::WiFiReasonCode reason);
+
   // Sends linear histogram data to UMA.
   virtual bool SendEnumToUMA(const std::string &name, int sample, int max);
 
diff --git a/shill_daemon.cc b/shill_daemon.cc
index a86721b..3b8e6ac 100644
--- a/shill_daemon.cc
+++ b/shill_daemon.cc
@@ -108,6 +108,7 @@
       Config80211::kEventTypeMlme };
 
     // Install |callback80211_| in the Config80211 singleton.
+    callback80211_->set_metrics(&metrics_);
     callback80211_->set_config80211(config80211_);
     callback80211_->InstallAsCallback();
 
diff --git a/user_bound_nlmessage.cc b/user_bound_nlmessage.cc
index ab4d406..7121512 100644
--- a/user_bound_nlmessage.cc
+++ b/user_bound_nlmessage.cc
@@ -72,7 +72,8 @@
 
 const uint32_t UserBoundNlMessage::kIllegalMessage = 0xFFFFFFFF;
 const int UserBoundNlMessage::kEthernetAddressBytes = 6;
-map<uint16_t, string> *UserBoundNlMessage::connect_status_map_ = NULL;
+map<uint16_t, string> *UserBoundNlMessage::reason_code_string_ = NULL;
+map<uint16_t, string> *UserBoundNlMessage::status_code_string_ = NULL;
 
 // The nl messages look like this:
 //
@@ -129,137 +130,196 @@
   // Convert integer values provided by libnl (for example, from the
   // NL80211_ATTR_STATUS_CODE or NL80211_ATTR_REASON_CODE attribute) into
   // strings describing the status.
-  if (!connect_status_map_) {
-    connect_status_map_ = new map<uint16_t, string>;
-    (*connect_status_map_)[IEEE_80211::kConnectStatusSuccessful] = "Successful";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusFailure] =
-        "Unspecified failure";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusNoLongerValid] =
+  if (!reason_code_string_) {
+    reason_code_string_ = new map<uint16_t, string>;
+    (*reason_code_string_)[IEEE_80211::kReasonCodeUnspecified] =
+        "Unspecified reason";
+    (*reason_code_string_)[
+        IEEE_80211::kReasonCodePreviousAuthenticationInvalid] =
         "Previous authentication no longer valid";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusSenderHasLeft] =
-        "Deauthenticated because sending station is leaving (or has left) the "
-        "IBSS or ESS";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusNonAuthenticated] =
-        "Class 3 frame received from non-authenticated station";
-    (*connect_status_map_)[
-        IEEE_80211::kConnectStatusAllCapabilitiesNotSupported] =
+    (*reason_code_string_)[IEEE_80211::kReasonCodeSenderHasLeft] =
+        "Deauthentcated because sending STA is leaving (or has left) IBSS or "
+        "ESS";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeInactivity] =
+        "Disassociated due to inactivity";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeTooManySTAs] =
+        "Disassociated because AP is unable to handle all currently associated "
+        "STAs";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeNonAuthenticated] =
+        "Class 2 frame received from nonauthenticated STA";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeNonAssociated] =
+        "Class 3 frame received from nonassociated STA";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeDisassociatedHasLeft] =
+        "Disassociated because sending STA is leaving (or has left) BSS";
+    (*reason_code_string_)[
+        IEEE_80211::kReasonCodeReassociationNotAuthenticated] =
+        "STA requesting (re)association is not authenticated with responding "
+        "STA";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeUnacceptablePowerCapability] =
+        "Disassociated because the information in the Power Capability "
+        "element is unacceptable";
+    (*reason_code_string_)[
+        IEEE_80211::kReasonCodeUnacceptableSupportedChannelInfo] =
+        "Disassociated because the information in the Supported Channels "
+        "element is unacceptable";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeInvalidInfoElement] =
+        "Invalid information element, i.e., an information element defined in "
+        "this standard for which the content does not meet the specifications "
+        "in Clause 7";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeMICFailure] =
+        "Message integrity code (MIC) failure";
+    (*reason_code_string_)[IEEE_80211::kReasonCode4WayTimeout] =
+        "4-Way Handshake timeout";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeGroupKeyHandshakeTimeout] =
+        "Group Key Handshake timeout";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeDifferenIE] =
+        "Information element in 4-Way Handshake different from "
+        "(Re)Association Request/Probe Response/Beacon frame";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeGroupCipherInvalid] =
+        "Invalid group cipher";
+    (*reason_code_string_)[IEEE_80211::kReasonCodePairwiseCipherInvalid] =
+        "Invalid pairwise cipher";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeAkmpInvalid] =
+        "Invalid AKMP";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeUnsupportedRsnIeVersion] =
+        "Unsupported RSN information element version";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeInvalidRsnIeCaps] =
+        "Invalid RSN information element capabilities";
+    (*reason_code_string_)[IEEE_80211::kReasonCode8021XAuth] =
+        "IEEE 802.1X authentication failed";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeCipherSuiteRejected] =
+        "Cipher suite rejected because of the security policy";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeUnspecifiedQoS] =
+        "Disassociated for unspecified, QoS-related reason";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeQoSBandwidth] =
+        "Disassociated because QoS AP lacks sufficient bandwidth for this "
+        "QoS STA";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeiPoorConditions] =
+        "Disassociated because excessive number of frames need to be "
+        "acknowledged, but are not acknowledged due to AP transmissions "
+        "and/or poor channel conditions";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeOutsideTxop] =
+        "Disassociated because STA is transmitting outside the limits of its "
+        "TXOPs";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeStaLeaving] =
+        "Requested from peer STA as the STA is leaving the BSS (or resetting)";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeUnacceptableMechanism] =
+        "Requested from peer STA as it does not want to use the mechanism";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeSetupRequired] =
+        "Requested from peer STA as the STA received frames using the "
+        "mechanism for which a setup is required";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeTimeout] =
+        "Requested from peer STA due to timeout";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeCipherSuiteNotSupported] =
+        "Peer STA does not support the requested cipher suite";
+    (*reason_code_string_)[IEEE_80211::kReasonCodeInvalid] = "<INVALID REASON>";
+  }
+
+  if (!status_code_string_) {
+    status_code_string_ = new map<uint16_t, string>;
+    (*status_code_string_)[IEEE_80211::kStatusCodeSuccessful] = "Successful";
+    (*status_code_string_)[IEEE_80211::kStatusCodeFailure] =
+        "Unspecified failure";
+    (*status_code_string_)[IEEE_80211::kStatusCodeAllCapabilitiesNotSupported] =
         "Cannot support all requested capabilities in the capability "
         "information field";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusCantConfirmAssociation] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeCantConfirmAssociation] =
         "Reassociation denied due to inability to confirm that association "
         "exists";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusAssociationDenied] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeAssociationDenied] =
         "Association denied due to reason outside the scope of this standard";
-    (*connect_status_map_)[
-        IEEE_80211::kConnectStatusAuthenticationUnsupported] =
+    (*status_code_string_)[
+        IEEE_80211::kStatusCodeAuthenticationUnsupported] =
         "Responding station does not support the specified authentication "
         "algorithm";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusOutOfSequence] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeOutOfSequence] =
         "Received an authentication frame with authentication transaction "
         "sequence number out of expected sequence";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusChallengeFailure] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeChallengeFailure] =
         "Authentication rejected because of challenge failure";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusFrameTimeout] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeFrameTimeout] =
         "Authentication rejected due to timeout waiting for next frame in "
         "sequence";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusMaxSta] = "Association "
-        "denied because AP is unable to handle additional associated STA";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusDataRateUnsupported] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeMaxSta] =
+        "Association denied because AP is unable to handle additional "
+        "associated STA";
+    (*status_code_string_)[IEEE_80211::kStatusCodeDataRateUnsupported] =
         "Association denied due to requesting station not supporting all of "
         "the data rates in the BSSBasicRateSet parameter";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusShortPreambleUnsupported] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeShortPreambleUnsupported] =
         "Association denied due to requesting station not supporting the "
         "short preamble option";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusPbccUnsupported] =
+    (*status_code_string_)[IEEE_80211::kStatusCodePbccUnsupported] =
         "Association denied due to requesting station not supporting the PBCC "
         "modulation option";
-    (*connect_status_map_)[
-        IEEE_80211::kConnectStatusChannelAgilityUnsupported] =
+    (*status_code_string_)[
+        IEEE_80211::kStatusCodeChannelAgilityUnsupported] =
         "Association denied due to requesting station not supporting the "
         "channel agility option";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusNeedSpectrumManagement] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeNeedSpectrumManagement] =
         "Association request rejected because Spectrum Management capability "
         "is required";
-    (*connect_status_map_)[
-        IEEE_80211::kConnectStatusUnacceptablePowerCapability] =
+    (*status_code_string_)[
+        IEEE_80211::kStatusCodeUnacceptablePowerCapability] =
         "Association request rejected because the information in the Power "
         "Capability element is unacceptable";
-    (*connect_status_map_)[
-        IEEE_80211::kConnectStatusUnacceptableSupportedChannelInfo] =
+    (*status_code_string_)[
+        IEEE_80211::kStatusCodeUnacceptableSupportedChannelInfo] =
         "Association request rejected because the information in the "
         "Supported Channels element is unacceptable";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusShortTimeSlotRequired] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeShortTimeSlotRequired] =
         "Association request rejected due to requesting station not "
-        "supporting the short slot time option";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusErPbccRequired] =
-        "Association request rejected due to requesting station not supporting "
-        "the ER-PBCC modulation option";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusHtFeaturesRequired] =
-        "Association denied due to requesting STA not supporting HT features";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusR0khUnreachable] = "R0KH "
-        "Unreachable";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusPcoTransitionRequired] =
-        "Association denied because the requesting STA does not support the "
-        "PCO transition required by the AP";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusRejectedTemporarily] =
-        "Association request rejected temporarily; try again later";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusRobustPolicyViolated] =
-        "Robust Management frame policy violation";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusQosFailure] =
+        "supporting the Short Slot Time option";
+    (*status_code_string_)[IEEE_80211::kStatusCodeDssOfdmRequired] =
+        "Association request rejected due to requesting station not "
+        "supporting the DSSS-OFDM option";
+    (*status_code_string_)[IEEE_80211::kStatusCodeQosFailure] =
         "Unspecified, QoS related failure";
-    (*connect_status_map_)[
-        IEEE_80211::kConnectStatusInsufficientBandwithForQsta] =
+    (*status_code_string_)[
+        IEEE_80211::kStatusCodeInsufficientBandwithForQsta] =
         "Association denied due to QAP having insufficient bandwidth to handle "
         "another QSTA";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusPoorConditions] =
+    (*status_code_string_)[IEEE_80211::kStatusCodePoorConditions] =
         "Association denied due to poor channel conditions";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusQosNotSupported] =
-        "Association (with QBSS) denied due to requesting station not "
+    (*status_code_string_)[IEEE_80211::kStatusCodeQosNotSupported] =
+        "Association (with QoS BSS) denied due to requesting station not "
         "supporting the QoS facility";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusDeclined] = "The request "
-        "has been declined";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusInvalidParameterValues] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeDeclined] =
+        "The request has been declined";
+    (*status_code_string_)[IEEE_80211::kStatusCodeInvalidParameterValues] =
         "The request has not been successful as one or more parameters have "
         "invalid values";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusCannotBeHonored] = "The "
-        "TS has not been created because the request cannot be honored. "
+    (*status_code_string_)[IEEE_80211::kStatusCodeCannotBeHonored] =
+        "The TS has not been created because the request cannot be honored. "
         "However, a suggested Tspec is provided so that the initiating QSTA "
         "may attempt to send another TS with the suggested changes to the "
         "TSpec";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusInvalidInfoElement] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeInvalidInfoElement] =
         "Invalid Information Element";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusGroupCipherInvalid] =
-        "Group Cipher is not valid";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusPairwiseCipherInvalid] =
-        "Pairwise Cipher is not valid";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusAkmpInvalid] = "AKMP is "
-        "not valid";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusUnsupportedRsnIeVersion] =
-      "Unsupported RSN IE version";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusInvalidRsnIeCaps] =
-        "Invalid RSN IE Capabilities";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusCipherSuiteRejected] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeGroupCipherInvalid] =
+        "Invalid Group Cipher";
+    (*status_code_string_)[IEEE_80211::kStatusCodePairwiseCipherInvalid] =
+        "Invalid Pairwise Cipher";
+    (*status_code_string_)[IEEE_80211::kStatusCodeAkmpInvalid] = "Invalid AKMP";
+    (*status_code_string_)[IEEE_80211::kStatusCodeUnsupportedRsnIeVersion] =
+        "Unsupported RSN Information Element version";
+    (*status_code_string_)[IEEE_80211::kStatusCodeInvalidRsnIeCaps] =
+        "Invalid RSN Information Element Capabilities";
+    (*status_code_string_)[IEEE_80211::kStatusCodeCipherSuiteRejected] =
         "Cipher suite is rejected per security policy";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusTsDelayNotMet] = "The TS "
-        "has not been created. However, the HC may be capable of creating a "
-        "TS, in response to a request, after the time indicated in the TS "
-        "Delay element";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusDirectLinkIllegal] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeTsDelayNotMet] =
+        "The TS has not been created. However, the HC may be capable of "
+        "creating a TS, in response to a request, after the time indicated in "
+        "the TS Delay element";
+    (*status_code_string_)[IEEE_80211::kStatusCodeDirectLinkIllegal] =
         "Direct link is not allowed in the BSS by policy";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusStaNotInQbss] =
-        "Destination STA is not present within this QBSS";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusStaNotInQsta] = "The "
-        "destination STA is not a QSTA";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusExcessiveListenInterval] =
+    (*status_code_string_)[IEEE_80211::kStatusCodeStaNotInBss] =
+        "Destination STA is not present within this BSS";
+    (*status_code_string_)[IEEE_80211::kStatusCodeStaNotInQsta] =
+        "The destination STA is not a QoS STA";
+    (*status_code_string_)[IEEE_80211::kStatusCodeExcessiveListenInterval] =
         "Association denied because Listen Interval is too large";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusInvalidFastBssFrameCount] =
-        "Invalid Fast BSS Transition Action Frame Count";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusInvalidPmkid] =
-        "Invalid PMKID";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusInvalidMdie] =
-        "Invalid MDIE";
-    (*connect_status_map_)[IEEE_80211::kConnectStatusInvalidFtie] =
-        "Invalid FTIE";
+    (*status_code_string_)[IEEE_80211::kStatusCodeInvalid] = "<INVALID STATUS>";
   }
 
   return true;
@@ -1044,19 +1104,40 @@
 }
 
 // static
-string UserBoundNlMessage::StringFromStatus(uint16_t status) {
+string UserBoundNlMessage::StringFromReason(uint16_t status) {
   map<uint16_t, string>::const_iterator match;
-  match = connect_status_map_->find(status);
-  if (match == connect_status_map_->end()) {
+  match = reason_code_string_->find(status);
+  if (match == reason_code_string_->end()) {
     string output;
-    StringAppendF(&output, "<Unknown Status:%u>", status);
+    if (status < IEEE_80211::kReasonCodeMax) {
+      StringAppendF(&output, "<Reserved Reason:%u>", status);
+    } else {
+      StringAppendF(&output, "<Unknown Reason:%u>", status);
+    }
     return output;
   }
   return match->second;
 }
 
-Nl80211Frame::Nl80211Frame(const uint8_t *raw_frame, int frame_byte_count) :
-    frame_type_(kIllegalFrameType), status_(0), frame_(0), byte_count_(0) {
+// static
+string UserBoundNlMessage::StringFromStatus(uint16_t status) {
+  map<uint16_t, string>::const_iterator match;
+  match = status_code_string_->find(status);
+  if (match == status_code_string_->end()) {
+    string output;
+    if (status < IEEE_80211::kStatusCodeMax) {
+      StringAppendF(&output, "<Reserved Status:%u>", status);
+    } else {
+      StringAppendF(&output, "<Unknown Status:%u>", status);
+    }
+    return output;
+  }
+  return match->second;
+}
+
+Nl80211Frame::Nl80211Frame(const uint8_t *raw_frame, int frame_byte_count)
+  : frame_type_(kIllegalFrameType), reason_(UINT16_MAX), status_(UINT16_MAX),
+    frame_(0), byte_count_(0) {
   if (raw_frame == NULL)
     return;
 
@@ -1085,7 +1166,7 @@
 
     case kDisassocFrameType:
     case kDeauthFrameType:
-      status_ = le16toh(frame->u.deauthentiate_message.reason_code);
+      reason_ = le16toh(frame->u.deauthentiate_message.reason_code);
       break;
 
     default:
@@ -1099,7 +1180,7 @@
   frame_ = NULL;
 }
 
-bool Nl80211Frame::ToString(string *output) {
+bool Nl80211Frame::ToString(string *output) const {
   if (!output) {
     LOG(ERROR) << "NULL |output|";
     return false;
@@ -1134,13 +1215,13 @@
 
     case kDisassocFrameType:
       StringAppendF(output, "; Disassoc reason %u: %s",
-                    status_,
-                    UserBoundNlMessage::StringFromStatus(status_).c_str());
+                    reason_,
+                    UserBoundNlMessage::StringFromReason(reason_).c_str());
       break;
     case kDeauthFrameType:
       StringAppendF(output, "; Deauth reason %u: %s",
-                    status_,
-                    UserBoundNlMessage::StringFromStatus(status_).c_str());
+                    reason_,
+                    UserBoundNlMessage::StringFromReason(reason_).c_str());
       break;
 
     default:
@@ -1157,7 +1238,7 @@
   return true;
 }
 
-bool Nl80211Frame::IsEqual(const Nl80211Frame &other) {
+bool Nl80211Frame::IsEqual(const Nl80211Frame &other) const {
   if (byte_count_ != other.byte_count_) {
     return false;
   }
@@ -1171,6 +1252,7 @@
   return true;
 }
 
+
 //
 // Specific UserBoundNlMessage types.
 //
@@ -1292,7 +1374,7 @@
   uint16_t reason = UINT16_MAX;
   if (GetU16Attribute(NL80211_ATTR_REASON_CODE, &reason)) {
     StringAppendF(&output, " reason: %u: %s",
-                  reason, StringFromStatus(reason).c_str());
+                  reason, StringFromReason(reason).c_str());
   }
   return output;
 }
@@ -1877,8 +1959,7 @@
 }
 
 void UserBoundNlMessageDataCollector::CollectDebugData(
-    const UserBoundNlMessage &message, nlmsghdr *msg)
-{
+    const UserBoundNlMessage &message, nlmsghdr *msg) {
   if (!msg) {
     LOG(ERROR) << "NULL |msg| parameter";
     return;
@@ -1900,7 +1981,7 @@
 
     size_t bytes = nlmsg_total_size(payload_bytes);
     unsigned char *rawdata = reinterpret_cast<unsigned char *>(msg);
-    for (size_t i=0; i<bytes; ++i) {
+    for (size_t i = 0; i < bytes; ++i) {
       LOG(INFO) << "  0x"
                  << std::hex << std::setfill('0') << std::setw(2)
                  << + rawdata[i] << ",";
diff --git a/user_bound_nlmessage.h b/user_bound_nlmessage.h
index c727660..aa9881c 100644
--- a/user_bound_nlmessage.h
+++ b/user_bound_nlmessage.h
@@ -156,9 +156,10 @@
   // as a NULL |arg|), |value| is set to a bogus MAC address.
   static std::string StringFromMacAddress(const uint8_t *arg);
 
-  // Returns a string representing the passed-in |status|, the value of which
-  // has been acquired from libnl (for example, from the
+  // Returns a string representing the passed-in |status| or |reason|, the
+  // value of which has been acquired from libnl (for example, from the
   // NL80211_ATTR_STATUS_CODE or NL80211_ATTR_REASON_CODE attribute).
+  static std::string StringFromReason(uint16_t reason);
   static std::string StringFromStatus(uint16_t status);
 
   // Returns a string that describes this message.
@@ -199,7 +200,8 @@
   static const int kEthernetAddressBytes;
 
   nlmsghdr *message_;
-  static std::map<uint16_t, std::string> *connect_status_map_;
+  static std::map<uint16_t, std::string> *reason_code_string_;
+  static std::map<uint16_t, std::string> *status_code_string_;
   std::map<nl80211_attrs, nlattr *> attributes_;
 
   DISALLOW_COPY_AND_ASSIGN(UserBoundNlMessage);
@@ -220,8 +222,10 @@
 
   Nl80211Frame(const uint8_t *frame, int frame_byte_count);
   ~Nl80211Frame();
-  bool ToString(std::string *output);
-  bool IsEqual(const Nl80211Frame &other);
+  bool ToString(std::string *output) const;
+  bool IsEqual(const Nl80211Frame &other) const;
+  uint16_t reason() const { return reason_; }
+  uint16_t status() const { return status_; }
 
  private:
   static const uint8_t kMinimumFrameByteCount;
@@ -230,6 +234,7 @@
   std::string mac_from_;
   std::string mac_to_;
   uint8_t frame_type_;
+  uint16_t reason_;
   uint16_t status_;
   uint8_t *frame_;
   int byte_count_;