[RESEND] dbus: export roam time, roam complete, and session length to dbus

Message ID 20180504181602.34764-1-matthewmwang@chromium.org
State Changes Requested
Headers show
Series
  • [RESEND] dbus: export roam time, roam complete, and session length to dbus
Related show

Commit Message

Matthew Wang May 4, 2018, 6:16 p.m.
Added new Interface properties "RoamTime", "RoamComplete", and "SessionLength".

"RoamTime" carries the roam time of the most recent roam in milliseconds.
"RoamComplete" carries True or False corresponding to the success status of the
most recent roam.
"SessionLength" carries the number of milliseconds corresponding to how long
the connection to the last AP was before a roam or disconnect happened.

These properties will all be logged in UMA to compare FT vs non-FT network
behaviors.

Signed-off-by: Matthew Wang <matthewmwang@chromium.org>
---
 doc/dbus.doxygen                        | 15 ++++++
 wpa_supplicant/dbus/dbus_new.c          | 27 ++++++++++
 wpa_supplicant/dbus/dbus_new.h          |  3 ++
 wpa_supplicant/dbus/dbus_new_handlers.c | 65 +++++++++++++++++++++++++
 wpa_supplicant/dbus/dbus_new_handlers.h |  3 ++
 wpa_supplicant/notify.c                 | 27 ++++++++++
 wpa_supplicant/notify.h                 |  3 ++
 wpa_supplicant/wpa_supplicant.c         | 34 +++++++++++++
 wpa_supplicant/wpa_supplicant_i.h       |  4 ++
 9 files changed, 181 insertions(+)

Comments

Dan Williams May 18, 2018, 2:14 p.m. | #1
On Fri, 2018-05-04 at 11:16 -0700, Matthew Wang wrote:
> Added new Interface properties "RoamTime", "RoamComplete", and
> "SessionLength".
> 
> "RoamTime" carries the roam time of the most recent roam in
> milliseconds.
> "RoamComplete" carries True or False corresponding to the success
> status of the
> most recent roam.
> "SessionLength" carries the number of milliseconds corresponding to
> how long
> the connection to the last AP was before a roam or disconnect
> happened.
> 
> These properties will all be logged in UMA to compare FT vs non-FT
> network
> behaviors.

Overall seems OK to me, though Jouni should weigh in on the
wpa_supplicant structure changes.  Only thing I would suggest is to
perhaps pull the wpa_supplicant_state_changed() bits out to their own
static function, since they are mostly roaming/session related and
stand on their own?

Dan

> Signed-off-by: Matthew Wang <matthewmwang@chromium.org>
> ---
>  doc/dbus.doxygen                        | 15 ++++++
>  wpa_supplicant/dbus/dbus_new.c          | 27 ++++++++++
>  wpa_supplicant/dbus/dbus_new.h          |  3 ++
>  wpa_supplicant/dbus/dbus_new_handlers.c | 65
> +++++++++++++++++++++++++
>  wpa_supplicant/dbus/dbus_new_handlers.h |  3 ++
>  wpa_supplicant/notify.c                 | 27 ++++++++++
>  wpa_supplicant/notify.h                 |  3 ++
>  wpa_supplicant/wpa_supplicant.c         | 34 +++++++++++++
>  wpa_supplicant/wpa_supplicant_i.h       |  4 ++
>  9 files changed, 181 insertions(+)
> 
> diff --git a/doc/dbus.doxygen b/doc/dbus.doxygen
> index 2ca40ae9b..64d2d0985 100644
> --- a/doc/dbus.doxygen
> +++ b/doc/dbus.doxygen
> @@ -769,6 +769,21 @@ fi.w1.wpa_supplicant1.CreateInterface.
>  	<p>The most recent IEEE 802.11 status code for association
> rejection.</p>
>        </li>
>  
> +      <li>
> +	<h3>RoamTime - u - (read)</h3>
> +	<p>The most recent roam time in milliseconds.</p>
> +      </li>
> +
> +      <li>
> +	<h3>RoamComplete - b - (read)</h3>
> +	<p>The most recent roam success or failure.</p>
> +      </li>
> +
> +      <li>
> +	<h3>SessionLength - u - (read)</h3>
> +	<p>The most recent BSS session length in milliseconds.</p>
> +      </li>
> +
>        <li>
>  	<h3>EapolVersion - s - (read/write)</h3>
>  	<p>IEEE 802.1X/EAPOL version number</p>
> diff --git a/wpa_supplicant/dbus/dbus_new.c
> b/wpa_supplicant/dbus/dbus_new.c
> index e0f16bbda..a50c7a540 100644
> --- a/wpa_supplicant/dbus/dbus_new.c
> +++ b/wpa_supplicant/dbus/dbus_new.c
> @@ -2162,6 +2162,15 @@ void wpas_dbus_signal_prop_changed(struct
> wpa_supplicant *wpa_s,
>  		prop = "AssocStatusCode";
>  		flush = TRUE;
>  		break;
> +	case WPAS_DBUS_PROP_ROAM_TIME:
> +		prop = "RoamTime";
> +		break;
> +	case WPAS_DBUS_PROP_ROAM_COMPLETE:
> +		prop = "RoamComplete";
> +		break;
> +	case WPAS_DBUS_PROP_SESSION_LENGTH:
> +		prop = "SessionLength";
> +		break;
>  	default:
>  		wpa_printf(MSG_ERROR, "dbus: %s: Unknown Property
> value %d",
>  			   __func__, property);
> @@ -2731,6 +2740,24 @@ static const struct wpa_dbus_property_desc
> wpas_dbus_bss_properties[] = {
>  	  NULL,
>  	  NULL
>  	},
> +	{
> +	  "RoamTime", WPAS_DBUS_NEW_IFACE_INTERFACE, "u",
> +	  wpas_dbus_getter_roam_time,
> +	  NULL,
> +	  NULL
> +	},
> +	{
> +	  "RoamComplete", WPAS_DBUS_NEW_IFACE_INTERFACE, "b",
> +	  wpas_dbus_getter_roam_complete,
> +	  NULL,
> +	  NULL
> +	},
> +	{
> +	  "SessionLength", WPAS_DBUS_NEW_IFACE_INTERFACE, "u",
> +	  wpas_dbus_getter_session_length,
> +	  NULL,
> +	  NULL
> +	},
>  	{ NULL, NULL, NULL, NULL, NULL, NULL }
>  };
>  
> diff --git a/wpa_supplicant/dbus/dbus_new.h
> b/wpa_supplicant/dbus/dbus_new.h
> index e68acb7a1..08f4858b1 100644
> --- a/wpa_supplicant/dbus/dbus_new.h
> +++ b/wpa_supplicant/dbus/dbus_new.h
> @@ -31,6 +31,9 @@ enum wpas_dbus_prop {
>  	WPAS_DBUS_PROP_BSSS,
>  	WPAS_DBUS_PROP_DISCONNECT_REASON,
>  	WPAS_DBUS_PROP_ASSOC_STATUS_CODE,
> +	WPAS_DBUS_PROP_ROAM_TIME,
> +	WPAS_DBUS_PROP_ROAM_COMPLETE,
> +	WPAS_DBUS_PROP_SESSION_LENGTH,
>  };
>  
>  enum wpas_dbus_bss_prop {
> diff --git a/wpa_supplicant/dbus/dbus_new_handlers.c
> b/wpa_supplicant/dbus/dbus_new_handlers.c
> index a3c98fadd..e84acf230 100644
> --- a/wpa_supplicant/dbus/dbus_new_handlers.c
> +++ b/wpa_supplicant/dbus/dbus_new_handlers.c
> @@ -3158,6 +3158,71 @@ dbus_bool_t
> wpas_dbus_getter_assoc_status_code(
>  }
>  
>  
> +/**
> + * wpas_dbus_getter_roam_time - Get most recent roam time
> + * @iter: Pointer to incoming dbus message iter
> + * @error: Location to store error on failure
> + * @user_data: Function specific data
> + * Returns: TRUE on success, FALSE on failure
> + *
> + * Getter for "RoamTime" property.
> + */
> +dbus_bool_t wpas_dbus_getter_roam_time(
> +	const struct wpa_dbus_property_desc *property_desc,
> +	DBusMessageIter *iter, DBusError *error, void *user_data)
> +{
> +	struct wpa_supplicant *wpa_s = user_data;
> +	dbus_uint32_t roam_time = wpa_s->roam_time.sec * 1000 +
> +				  wpa_s->roam_time.usec / 1000;
> +
> +	return wpas_dbus_simple_property_getter(iter,
> DBUS_TYPE_UINT32,
> +						&roam_time, error);
> +}
> +
> +/**
> + * wpas_dbus_getter_roam_complete - Get most recent roam success or
> failure
> + * @iter: Pointer to incoming dbus message iter
> + * @error: Location to store error on failure
> + * @user_data: Function specific data
> + * Returns: TRUE on success, FALSE on failure
> + *
> + * Getter for "RoamComplete" property.
> + */
> +dbus_bool_t wpas_dbus_getter_roam_complete(
> +	const struct wpa_dbus_property_desc *property_desc,
> +	DBusMessageIter *iter, DBusError *error, void *user_data)
> +{
> +	struct wpa_supplicant *wpa_s = user_data;
> +	dbus_bool_t roam_complete = os_reltime_initialized(&wpa_s-
> >roam_time) ?
> +				    TRUE : FALSE;
> +
> +	return wpas_dbus_simple_property_getter(iter,
> DBUS_TYPE_BOOLEAN,
> +						&roam_complete,
> error);
> +}
> +
> +
> +/**
> + * wpas_dbus_getter_session_length - Get most recent BSS session
> length
> + * @iter: Pointer to incoming dbus message iter
> + * @error: Location to store error on failure
> + * @user_data: Function specific data
> + * Returns: TRUE on success, FALSE on failure
> + *
> + * Getter for "SessionLength" property.
> + */
> +dbus_bool_t wpas_dbus_getter_session_length(
> +	const struct wpa_dbus_property_desc *property_desc,
> +	DBusMessageIter *iter, DBusError *error, void *user_data)
> +{
> +	struct wpa_supplicant *wpa_s = user_data;
> +	dbus_uint32_t session_length = wpa_s->session_length.sec *
> 1000 +
> +				       wpa_s->session_length.usec /
> 1000;
> +
> +	return wpas_dbus_simple_property_getter(iter,
> DBUS_TYPE_UINT32,
> +						&session_length,
> error);
> +}
> +
> +
>  /**
>   * wpas_dbus_getter_bss_expire_age - Get BSS entry expiration age
>   * @iter: Pointer to incoming dbus message iter
> diff --git a/wpa_supplicant/dbus/dbus_new_handlers.h
> b/wpa_supplicant/dbus/dbus_new_handlers.h
> index 26652ad3d..7a03b7f9f 100644
> --- a/wpa_supplicant/dbus/dbus_new_handlers.h
> +++ b/wpa_supplicant/dbus/dbus_new_handlers.h
> @@ -148,6 +148,9 @@ DECLARE_ACCESSOR(wpas_dbus_setter_fast_reauth);
>  DECLARE_ACCESSOR(wpas_dbus_getter_disconnect_reason);
>  DECLARE_ACCESSOR(wpas_dbus_getter_disassociate_reason);
>  DECLARE_ACCESSOR(wpas_dbus_getter_assoc_status_code);
> +DECLARE_ACCESSOR(wpas_dbus_getter_roam_time);
> +DECLARE_ACCESSOR(wpas_dbus_getter_roam_complete);
> +DECLARE_ACCESSOR(wpas_dbus_getter_session_length);
>  DECLARE_ACCESSOR(wpas_dbus_getter_bss_expire_age);
>  DECLARE_ACCESSOR(wpas_dbus_setter_bss_expire_age);
>  DECLARE_ACCESSOR(wpas_dbus_getter_bss_expire_count);
> diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c
> index 83df04f39..a2b04716d 100644
> --- a/wpa_supplicant/notify.c
> +++ b/wpa_supplicant/notify.c
> @@ -149,6 +149,33 @@ void wpas_notify_assoc_status_code(struct
> wpa_supplicant *wpa_s)
>  }
>  
>  
> +void wpas_notify_roam_time(struct wpa_supplicant *wpa_s)
> +{
> +	if (wpa_s->p2p_mgmt)
> +		return;
> +
> +	wpas_dbus_signal_prop_changed(wpa_s,
> WPAS_DBUS_PROP_ROAM_TIME);
> +}
> +
> +
> +void wpas_notify_roam_complete(struct wpa_supplicant *wpa_s)
> +{
> +	if (wpa_s->p2p_mgmt)
> +		return;
> +
> +	wpas_dbus_signal_prop_changed(wpa_s,
> WPAS_DBUS_PROP_ROAM_COMPLETE);
> +}
> +
> +
> +void wpas_notify_session_length(struct wpa_supplicant *wpa_s)
> +{
> +	if (wpa_s->p2p_mgmt)
> +		return;
> +
> +	wpas_dbus_signal_prop_changed(wpa_s,
> WPAS_DBUS_PROP_SESSION_LENGTH);
> +}
> +
> +
>  void wpas_notify_network_changed(struct wpa_supplicant *wpa_s)
>  {
>  	if (wpa_s->p2p_mgmt)
> diff --git a/wpa_supplicant/notify.h b/wpa_supplicant/notify.h
> index 3ca933c76..bde20e83d 100644
> --- a/wpa_supplicant/notify.h
> +++ b/wpa_supplicant/notify.h
> @@ -24,6 +24,9 @@ void wpas_notify_state_changed(struct
> wpa_supplicant *wpa_s,
>  			       enum wpa_states old_state);
>  void wpas_notify_disconnect_reason(struct wpa_supplicant *wpa_s);
>  void wpas_notify_assoc_status_code(struct wpa_supplicant *wpa_s);
> +void wpas_notify_roam_time(struct wpa_supplicant *wpa_s);
> +void wpas_notify_roam_complete(struct wpa_supplicant *wpa_s);
> +void wpas_notify_session_length(struct wpa_supplicant *wpa_s);
>  void wpas_notify_network_changed(struct wpa_supplicant *wpa_s);
>  void wpas_notify_ap_scan_changed(struct wpa_supplicant *wpa_s);
>  void wpas_notify_bssid_changed(struct wpa_supplicant *wpa_s);
> diff --git a/wpa_supplicant/wpa_supplicant.c
> b/wpa_supplicant/wpa_supplicant.c
> index dcd787bec..df27c55ad 100644
> --- a/wpa_supplicant/wpa_supplicant.c
> +++ b/wpa_supplicant/wpa_supplicant.c
> @@ -839,6 +839,40 @@ void wpa_supplicant_set_state(struct
> wpa_supplicant *wpa_s,
>  		wpa_supplicant_state_txt(wpa_s->wpa_state),
>  		wpa_supplicant_state_txt(state));
>  
> +	if (state == WPA_AUTHENTICATING && wpa_s->wpa_state ==
> WPA_COMPLETED) {
> +		if (wpa_s->sme.prev_bssid_set &&
> +		    os_memcmp(wpa_s->sme.prev_bssid, wpa_s-
> >pending_bssid, ETH_ALEN) != 0) {
> +			os_get_reltime(&wpa_s->roam_start);
> +		}
> +	} else if (state == WPA_COMPLETED &&
> +		   os_reltime_initialized(&wpa_s->roam_start)) {
> +		os_reltime_age(&wpa_s->roam_start, &wpa_s-
> >roam_time);
> +		wpa_s->roam_start.sec = 0;
> +		wpa_s->roam_start.usec = 0;
> +		wpas_notify_auth_changed(wpa_s);
> +		wpas_notify_roam_time(wpa_s);
> +		wpas_notify_roam_complete(wpa_s);
> +	} else if (state == WPA_DISCONNECTED &&
> +		   os_reltime_initialized(&wpa_s->roam_start)) {
> +		wpa_s->roam_start.sec = 0;
> +		wpa_s->roam_start.usec = 0;
> +		wpa_s->roam_time.sec = 0;
> +		wpa_s->roam_time.usec = 0;
> +		wpas_notify_roam_complete(wpa_s);
> +	}
> +
> +	if (state == WPA_COMPLETED && wpa_s->wpa_state !=
> WPA_COMPLETED) {
> +		wpas_notify_auth_changed(wpa_s);
> +		os_get_reltime(&wpa_s->session_start);
> +	} else if (state != WPA_COMPLETED &&
> +		   wpa_s->wpa_state == WPA_COMPLETED &&
> +		   os_reltime_initialized(&wpa_s->session_start)) {
> +		os_reltime_age(&wpa_s->session_start, &wpa_s-
> >session_length);
> +		wpa_s->session_start.sec = 0;
> +		wpa_s->session_start.usec = 0;
> +		wpas_notify_session_length(wpa_s);
> +	}
> +
>  	if (state == WPA_INTERFACE_DISABLED) {
>  		/* Assure normal scan when interface is restored */
>  		wpa_s->normal_scans = 0;
> diff --git a/wpa_supplicant/wpa_supplicant_i.h
> b/wpa_supplicant/wpa_supplicant_i.h
> index 4d18177fb..a4d56b810 100644
> --- a/wpa_supplicant/wpa_supplicant_i.h
> +++ b/wpa_supplicant/wpa_supplicant_i.h
> @@ -492,6 +492,10 @@ struct wpa_supplicant {
>  	struct wpa_supplicant *next;
>  	struct l2_packet_data *l2;
>  	struct l2_packet_data *l2_br;
> +	struct os_reltime roam_start;
> +	struct os_reltime roam_time;
> +	struct os_reltime session_start;
> +	struct os_reltime session_length;
>  	unsigned char own_addr[ETH_ALEN];
>  	unsigned char perm_addr[ETH_ALEN];
>  	char ifname[100];
Jouni Malinen June 11, 2018, 7:17 p.m. | #2
On Fri, May 04, 2018 at 11:16:02AM -0700, Matthew Wang wrote:
> Added new Interface properties "RoamTime", "RoamComplete", and "SessionLength".
> 
> "RoamTime" carries the roam time of the most recent roam in milliseconds.
> "RoamComplete" carries True or False corresponding to the success status of the
> most recent roam.
> "SessionLength" carries the number of milliseconds corresponding to how long
> the connection to the last AP was before a roam or disconnect happened.
> 
> These properties will all be logged in UMA to compare FT vs non-FT network
> behaviors.

> diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
> @@ -839,6 +839,40 @@ void wpa_supplicant_set_state(struct wpa_supplicant *wpa_s,
>  		wpa_supplicant_state_txt(wpa_s->wpa_state),
>  		wpa_supplicant_state_txt(state));
>  
> +	if (state == WPA_AUTHENTICATING && wpa_s->wpa_state == WPA_COMPLETED) {
> +		if (wpa_s->sme.prev_bssid_set &&

This would break the build without CONFIG_SME=y. 

> +		    os_memcmp(wpa_s->sme.prev_bssid, wpa_s->pending_bssid, ETH_ALEN) != 0) {
> +			os_get_reltime(&wpa_s->roam_start);
> +		}

I guess it might be acceptable to make this work only with
wpa_supplicant-SME case if this were the only issue (i.e., just add
#ifdef CONFIG_SME/#endif around these four lines), but there may be need
to use something completely different to determine the start of session
duration and if so, this part might be addressed as well in some more
generic manner.

> +	if (state == WPA_COMPLETED && wpa_s->wpa_state != WPA_COMPLETED) {
> +		wpas_notify_auth_changed(wpa_s);
> +		os_get_reltime(&wpa_s->session_start);

Please note that COMPLETED state can be entered multiple time during an
association. E.g., it is re-entered whenever EAP reauthentication, PTK
rekeying, or GTK rekeying is completed. I'd assume these cases should
not really reset session_start.

> +	} else if (state != WPA_COMPLETED &&
> +		   wpa_s->wpa_state == WPA_COMPLETED &&
> +		   os_reltime_initialized(&wpa_s->session_start)) {
> +		os_reltime_age(&wpa_s->session_start, &wpa_s->session_length);
> +		wpa_s->session_start.sec = 0;
> +		wpa_s->session_start.usec = 0;
> +		wpas_notify_session_length(wpa_s);

And this part would end up currently indicating a new session start for
all those reauthentication/rekeying cases even if there is no new
association.

How do you define session length here? The commit message seemed to
imply that this would be the full time of an association with the
previous AP. wpa_s->wpa_state changes from/to WPA_COMPLETED cannot be
used to determine that. Time from wpa_supplicant_event_assoc() to
wpa_supplicant_mark_disassoc() might be closer. Though, for the roaming
case, session termination would need to be taken care of in
wpa_supplicant_event_assoc() as well, if a session was already open.

I'd also consider some other trigger for roam start to at least address
the no CONFIG_SME=y case. Maybe wpa_supplicant_associate() would be the
appropriate place to indicate start for the roam. The commit message was
not very clear on which step you would consider to be included in
RoamTime.


FYI - this is a bit cleaned up version of the patch that I was
considering when reviewing the changes:

From: Matthew Wang <matthewmwang@chromium.org>
Date: Fri, 4 May 2018 11:16:02 -0700
Subject: [PATCH] dbus: Export roam time, roam complete, and session length to
 dbus

Add new Interface properties "RoamTime", "RoamComplete", and
"SessionLength".

"RoamTime" carries the roam time of the most recent roam in milliseconds.

"RoamComplete" carries True or False corresponding to the success status
of the most recent roam.

"SessionLength" carries the number of milliseconds corresponding to how
long the connection to the last AP was before a roam or disconnect
happened.

These properties will all be logged in UMA to compare FT vs. non-FT
network behaviors.

Signed-off-by: Matthew Wang <matthewmwang@chromium.org>
---
 doc/dbus.doxygen                        | 15 ++++++++
 wpa_supplicant/dbus/dbus_new.c          | 27 +++++++++++++
 wpa_supplicant/dbus/dbus_new.h          |  3 ++
 wpa_supplicant/dbus/dbus_new_handlers.c | 67 +++++++++++++++++++++++++++++++++
 wpa_supplicant/dbus/dbus_new_handlers.h |  3 ++
 wpa_supplicant/notify.c                 | 27 +++++++++++++
 wpa_supplicant/notify.h                 |  3 ++
 wpa_supplicant/wpa_supplicant.c         | 36 ++++++++++++++++++
 wpa_supplicant/wpa_supplicant_i.h       |  4 ++
 9 files changed, 185 insertions(+)

diff --git a/doc/dbus.doxygen b/doc/dbus.doxygen
index 2ca40ae..64d2d09 100644
--- a/doc/dbus.doxygen
+++ b/doc/dbus.doxygen
@@ -770,6 +770,21 @@ fi.w1.wpa_supplicant1.CreateInterface.
       </li>
 
       <li>
+	<h3>RoamTime - u - (read)</h3>
+	<p>The most recent roam time in milliseconds.</p>
+      </li>
+
+      <li>
+	<h3>RoamComplete - b - (read)</h3>
+	<p>The most recent roam success or failure.</p>
+      </li>
+
+      <li>
+	<h3>SessionLength - u - (read)</h3>
+	<p>The most recent BSS session length in milliseconds.</p>
+      </li>
+
+      <li>
 	<h3>EapolVersion - s - (read/write)</h3>
 	<p>IEEE 802.1X/EAPOL version number</p>
       </li>
diff --git a/wpa_supplicant/dbus/dbus_new.c b/wpa_supplicant/dbus/dbus_new.c
index e0f16bb..a50c7a5 100644
--- a/wpa_supplicant/dbus/dbus_new.c
+++ b/wpa_supplicant/dbus/dbus_new.c
@@ -2162,6 +2162,15 @@ void wpas_dbus_signal_prop_changed(struct wpa_supplicant *wpa_s,
 		prop = "AssocStatusCode";
 		flush = TRUE;
 		break;
+	case WPAS_DBUS_PROP_ROAM_TIME:
+		prop = "RoamTime";
+		break;
+	case WPAS_DBUS_PROP_ROAM_COMPLETE:
+		prop = "RoamComplete";
+		break;
+	case WPAS_DBUS_PROP_SESSION_LENGTH:
+		prop = "SessionLength";
+		break;
 	default:
 		wpa_printf(MSG_ERROR, "dbus: %s: Unknown Property value %d",
 			   __func__, property);
@@ -2731,6 +2740,24 @@ static const struct wpa_dbus_property_desc wpas_dbus_bss_properties[] = {
 	  NULL,
 	  NULL
 	},
+	{
+	  "RoamTime", WPAS_DBUS_NEW_IFACE_INTERFACE, "u",
+	  wpas_dbus_getter_roam_time,
+	  NULL,
+	  NULL
+	},
+	{
+	  "RoamComplete", WPAS_DBUS_NEW_IFACE_INTERFACE, "b",
+	  wpas_dbus_getter_roam_complete,
+	  NULL,
+	  NULL
+	},
+	{
+	  "SessionLength", WPAS_DBUS_NEW_IFACE_INTERFACE, "u",
+	  wpas_dbus_getter_session_length,
+	  NULL,
+	  NULL
+	},
 	{ NULL, NULL, NULL, NULL, NULL, NULL }
 };
 
diff --git a/wpa_supplicant/dbus/dbus_new.h b/wpa_supplicant/dbus/dbus_new.h
index e68acb7..08f4858 100644
--- a/wpa_supplicant/dbus/dbus_new.h
+++ b/wpa_supplicant/dbus/dbus_new.h
@@ -31,6 +31,9 @@ enum wpas_dbus_prop {
 	WPAS_DBUS_PROP_BSSS,
 	WPAS_DBUS_PROP_DISCONNECT_REASON,
 	WPAS_DBUS_PROP_ASSOC_STATUS_CODE,
+	WPAS_DBUS_PROP_ROAM_TIME,
+	WPAS_DBUS_PROP_ROAM_COMPLETE,
+	WPAS_DBUS_PROP_SESSION_LENGTH,
 };
 
 enum wpas_dbus_bss_prop {
diff --git a/wpa_supplicant/dbus/dbus_new_handlers.c b/wpa_supplicant/dbus/dbus_new_handlers.c
index a3c98fa..e9bcbca 100644
--- a/wpa_supplicant/dbus/dbus_new_handlers.c
+++ b/wpa_supplicant/dbus/dbus_new_handlers.c
@@ -3159,6 +3159,73 @@ dbus_bool_t wpas_dbus_getter_assoc_status_code(
 
 
 /**
+ * wpas_dbus_getter_roam_time - Get most recent roam time in milliseconds
+ * @iter: Pointer to incoming dbus message iter
+ * @error: Location to store error on failure
+ * @user_data: Function specific data
+ * Returns: TRUE on success, FALSE on failure
+ *
+ * Getter for "RoamTime" property.
+ */
+dbus_bool_t wpas_dbus_getter_roam_time(
+	const struct wpa_dbus_property_desc *property_desc,
+	DBusMessageIter *iter, DBusError *error, void *user_data)
+{
+	struct wpa_supplicant *wpa_s = user_data;
+	dbus_uint32_t roam_time;
+
+	roam_time = wpa_s->roam_time.sec * 1000 + wpa_s->roam_time.usec / 1000;
+	return wpas_dbus_simple_property_getter(iter, DBUS_TYPE_UINT32,
+						&roam_time, error);
+}
+
+
+/**
+ * wpas_dbus_getter_roam_complete - Get most recent roam success or failure
+ * @iter: Pointer to incoming dbus message iter
+ * @error: Location to store error on failure
+ * @user_data: Function specific data
+ * Returns: TRUE on success, FALSE on failure
+ *
+ * Getter for "RoamComplete" property.
+ */
+dbus_bool_t wpas_dbus_getter_roam_complete(
+	const struct wpa_dbus_property_desc *property_desc,
+	DBusMessageIter *iter, DBusError *error, void *user_data)
+{
+	struct wpa_supplicant *wpa_s = user_data;
+	dbus_bool_t complete;
+
+	complete = os_reltime_initialized(&wpa_s->roam_time) ? TRUE : FALSE;
+	return wpas_dbus_simple_property_getter(iter, DBUS_TYPE_BOOLEAN,
+						&complete, error);
+}
+
+
+/**
+ * wpas_dbus_getter_session_length - Get most recent BSS session length in ms
+ * @iter: Pointer to incoming dbus message iter
+ * @error: Location to store error on failure
+ * @user_data: Function specific data
+ * Returns: TRUE on success, FALSE on failure
+ *
+ * Getter for "SessionLength" property.
+ */
+dbus_bool_t wpas_dbus_getter_session_length(
+	const struct wpa_dbus_property_desc *property_desc,
+	DBusMessageIter *iter, DBusError *error, void *user_data)
+{
+	struct wpa_supplicant *wpa_s = user_data;
+	dbus_uint32_t session_length;
+
+	session_length = wpa_s->session_length.sec * 1000 +
+		wpa_s->session_length.usec / 1000;
+	return wpas_dbus_simple_property_getter(iter, DBUS_TYPE_UINT32,
+						&session_length, error);
+}
+
+
+/**
  * wpas_dbus_getter_bss_expire_age - Get BSS entry expiration age
  * @iter: Pointer to incoming dbus message iter
  * @error: Location to store error on failure
diff --git a/wpa_supplicant/dbus/dbus_new_handlers.h b/wpa_supplicant/dbus/dbus_new_handlers.h
index 26652ad..7a03b7f 100644
--- a/wpa_supplicant/dbus/dbus_new_handlers.h
+++ b/wpa_supplicant/dbus/dbus_new_handlers.h
@@ -148,6 +148,9 @@ DECLARE_ACCESSOR(wpas_dbus_setter_fast_reauth);
 DECLARE_ACCESSOR(wpas_dbus_getter_disconnect_reason);
 DECLARE_ACCESSOR(wpas_dbus_getter_disassociate_reason);
 DECLARE_ACCESSOR(wpas_dbus_getter_assoc_status_code);
+DECLARE_ACCESSOR(wpas_dbus_getter_roam_time);
+DECLARE_ACCESSOR(wpas_dbus_getter_roam_complete);
+DECLARE_ACCESSOR(wpas_dbus_getter_session_length);
 DECLARE_ACCESSOR(wpas_dbus_getter_bss_expire_age);
 DECLARE_ACCESSOR(wpas_dbus_setter_bss_expire_age);
 DECLARE_ACCESSOR(wpas_dbus_getter_bss_expire_count);
diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c
index 83df04f..a2b0471 100644
--- a/wpa_supplicant/notify.c
+++ b/wpa_supplicant/notify.c
@@ -149,6 +149,33 @@ void wpas_notify_assoc_status_code(struct wpa_supplicant *wpa_s)
 }
 
 
+void wpas_notify_roam_time(struct wpa_supplicant *wpa_s)
+{
+	if (wpa_s->p2p_mgmt)
+		return;
+
+	wpas_dbus_signal_prop_changed(wpa_s, WPAS_DBUS_PROP_ROAM_TIME);
+}
+
+
+void wpas_notify_roam_complete(struct wpa_supplicant *wpa_s)
+{
+	if (wpa_s->p2p_mgmt)
+		return;
+
+	wpas_dbus_signal_prop_changed(wpa_s, WPAS_DBUS_PROP_ROAM_COMPLETE);
+}
+
+
+void wpas_notify_session_length(struct wpa_supplicant *wpa_s)
+{
+	if (wpa_s->p2p_mgmt)
+		return;
+
+	wpas_dbus_signal_prop_changed(wpa_s, WPAS_DBUS_PROP_SESSION_LENGTH);
+}
+
+
 void wpas_notify_network_changed(struct wpa_supplicant *wpa_s)
 {
 	if (wpa_s->p2p_mgmt)
diff --git a/wpa_supplicant/notify.h b/wpa_supplicant/notify.h
index 3ca933c..bde20e8 100644
--- a/wpa_supplicant/notify.h
+++ b/wpa_supplicant/notify.h
@@ -24,6 +24,9 @@ void wpas_notify_state_changed(struct wpa_supplicant *wpa_s,
 			       enum wpa_states old_state);
 void wpas_notify_disconnect_reason(struct wpa_supplicant *wpa_s);
 void wpas_notify_assoc_status_code(struct wpa_supplicant *wpa_s);
+void wpas_notify_roam_time(struct wpa_supplicant *wpa_s);
+void wpas_notify_roam_complete(struct wpa_supplicant *wpa_s);
+void wpas_notify_session_length(struct wpa_supplicant *wpa_s);
 void wpas_notify_network_changed(struct wpa_supplicant *wpa_s);
 void wpas_notify_ap_scan_changed(struct wpa_supplicant *wpa_s);
 void wpas_notify_bssid_changed(struct wpa_supplicant *wpa_s);
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index 185a8d5..012eda4 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -839,6 +839,42 @@ void wpa_supplicant_set_state(struct wpa_supplicant *wpa_s,
 		wpa_supplicant_state_txt(wpa_s->wpa_state),
 		wpa_supplicant_state_txt(state));
 
+	if (state == WPA_AUTHENTICATING && wpa_s->wpa_state == WPA_COMPLETED) {
+#ifdef CONFIG_SME
+		if (wpa_s->sme.prev_bssid_set &&
+		    os_memcmp(wpa_s->sme.prev_bssid, wpa_s->pending_bssid,
+			      ETH_ALEN) != 0)
+			os_get_reltime(&wpa_s->roam_start);
+#endif /* CONFIG_SME */
+	} else if (state == WPA_COMPLETED &&
+		   os_reltime_initialized(&wpa_s->roam_start)) {
+		os_reltime_age(&wpa_s->roam_start, &wpa_s->roam_time);
+		wpa_s->roam_start.sec = 0;
+		wpa_s->roam_start.usec = 0;
+		wpas_notify_auth_changed(wpa_s);
+		wpas_notify_roam_time(wpa_s);
+		wpas_notify_roam_complete(wpa_s);
+	} else if (state == WPA_DISCONNECTED &&
+		   os_reltime_initialized(&wpa_s->roam_start)) {
+		wpa_s->roam_start.sec = 0;
+		wpa_s->roam_start.usec = 0;
+		wpa_s->roam_time.sec = 0;
+		wpa_s->roam_time.usec = 0;
+		wpas_notify_roam_complete(wpa_s);
+	}
+
+	if (state == WPA_COMPLETED && wpa_s->wpa_state != WPA_COMPLETED) {
+		wpas_notify_auth_changed(wpa_s);
+		os_get_reltime(&wpa_s->session_start);
+	} else if (state != WPA_COMPLETED &&
+		   wpa_s->wpa_state == WPA_COMPLETED &&
+		   os_reltime_initialized(&wpa_s->session_start)) {
+		os_reltime_age(&wpa_s->session_start, &wpa_s->session_length);
+		wpa_s->session_start.sec = 0;
+		wpa_s->session_start.usec = 0;
+		wpas_notify_session_length(wpa_s);
+	}
+
 	if (state == WPA_INTERFACE_DISABLED) {
 		/* Assure normal scan when interface is restored */
 		wpa_s->normal_scans = 0;
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 2b0dca0..4a1ca8a 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -492,6 +492,10 @@ struct wpa_supplicant {
 	struct wpa_supplicant *next;
 	struct l2_packet_data *l2;
 	struct l2_packet_data *l2_br;
+	struct os_reltime roam_start;
+	struct os_reltime roam_time;
+	struct os_reltime session_start;
+	struct os_reltime session_length;
 	unsigned char own_addr[ETH_ALEN];
 	unsigned char perm_addr[ETH_ALEN];
 	char ifname[100];

Patch

diff --git a/doc/dbus.doxygen b/doc/dbus.doxygen
index 2ca40ae9b..64d2d0985 100644
--- a/doc/dbus.doxygen
+++ b/doc/dbus.doxygen
@@ -769,6 +769,21 @@  fi.w1.wpa_supplicant1.CreateInterface.
 	<p>The most recent IEEE 802.11 status code for association rejection.</p>
       </li>
 
+      <li>
+	<h3>RoamTime - u - (read)</h3>
+	<p>The most recent roam time in milliseconds.</p>
+      </li>
+
+      <li>
+	<h3>RoamComplete - b - (read)</h3>
+	<p>The most recent roam success or failure.</p>
+      </li>
+
+      <li>
+	<h3>SessionLength - u - (read)</h3>
+	<p>The most recent BSS session length in milliseconds.</p>
+      </li>
+
       <li>
 	<h3>EapolVersion - s - (read/write)</h3>
 	<p>IEEE 802.1X/EAPOL version number</p>
diff --git a/wpa_supplicant/dbus/dbus_new.c b/wpa_supplicant/dbus/dbus_new.c
index e0f16bbda..a50c7a540 100644
--- a/wpa_supplicant/dbus/dbus_new.c
+++ b/wpa_supplicant/dbus/dbus_new.c
@@ -2162,6 +2162,15 @@  void wpas_dbus_signal_prop_changed(struct wpa_supplicant *wpa_s,
 		prop = "AssocStatusCode";
 		flush = TRUE;
 		break;
+	case WPAS_DBUS_PROP_ROAM_TIME:
+		prop = "RoamTime";
+		break;
+	case WPAS_DBUS_PROP_ROAM_COMPLETE:
+		prop = "RoamComplete";
+		break;
+	case WPAS_DBUS_PROP_SESSION_LENGTH:
+		prop = "SessionLength";
+		break;
 	default:
 		wpa_printf(MSG_ERROR, "dbus: %s: Unknown Property value %d",
 			   __func__, property);
@@ -2731,6 +2740,24 @@  static const struct wpa_dbus_property_desc wpas_dbus_bss_properties[] = {
 	  NULL,
 	  NULL
 	},
+	{
+	  "RoamTime", WPAS_DBUS_NEW_IFACE_INTERFACE, "u",
+	  wpas_dbus_getter_roam_time,
+	  NULL,
+	  NULL
+	},
+	{
+	  "RoamComplete", WPAS_DBUS_NEW_IFACE_INTERFACE, "b",
+	  wpas_dbus_getter_roam_complete,
+	  NULL,
+	  NULL
+	},
+	{
+	  "SessionLength", WPAS_DBUS_NEW_IFACE_INTERFACE, "u",
+	  wpas_dbus_getter_session_length,
+	  NULL,
+	  NULL
+	},
 	{ NULL, NULL, NULL, NULL, NULL, NULL }
 };
 
diff --git a/wpa_supplicant/dbus/dbus_new.h b/wpa_supplicant/dbus/dbus_new.h
index e68acb7a1..08f4858b1 100644
--- a/wpa_supplicant/dbus/dbus_new.h
+++ b/wpa_supplicant/dbus/dbus_new.h
@@ -31,6 +31,9 @@  enum wpas_dbus_prop {
 	WPAS_DBUS_PROP_BSSS,
 	WPAS_DBUS_PROP_DISCONNECT_REASON,
 	WPAS_DBUS_PROP_ASSOC_STATUS_CODE,
+	WPAS_DBUS_PROP_ROAM_TIME,
+	WPAS_DBUS_PROP_ROAM_COMPLETE,
+	WPAS_DBUS_PROP_SESSION_LENGTH,
 };
 
 enum wpas_dbus_bss_prop {
diff --git a/wpa_supplicant/dbus/dbus_new_handlers.c b/wpa_supplicant/dbus/dbus_new_handlers.c
index a3c98fadd..e84acf230 100644
--- a/wpa_supplicant/dbus/dbus_new_handlers.c
+++ b/wpa_supplicant/dbus/dbus_new_handlers.c
@@ -3158,6 +3158,71 @@  dbus_bool_t wpas_dbus_getter_assoc_status_code(
 }
 
 
+/**
+ * wpas_dbus_getter_roam_time - Get most recent roam time
+ * @iter: Pointer to incoming dbus message iter
+ * @error: Location to store error on failure
+ * @user_data: Function specific data
+ * Returns: TRUE on success, FALSE on failure
+ *
+ * Getter for "RoamTime" property.
+ */
+dbus_bool_t wpas_dbus_getter_roam_time(
+	const struct wpa_dbus_property_desc *property_desc,
+	DBusMessageIter *iter, DBusError *error, void *user_data)
+{
+	struct wpa_supplicant *wpa_s = user_data;
+	dbus_uint32_t roam_time = wpa_s->roam_time.sec * 1000 +
+				  wpa_s->roam_time.usec / 1000;
+
+	return wpas_dbus_simple_property_getter(iter, DBUS_TYPE_UINT32,
+						&roam_time, error);
+}
+
+/**
+ * wpas_dbus_getter_roam_complete - Get most recent roam success or failure
+ * @iter: Pointer to incoming dbus message iter
+ * @error: Location to store error on failure
+ * @user_data: Function specific data
+ * Returns: TRUE on success, FALSE on failure
+ *
+ * Getter for "RoamComplete" property.
+ */
+dbus_bool_t wpas_dbus_getter_roam_complete(
+	const struct wpa_dbus_property_desc *property_desc,
+	DBusMessageIter *iter, DBusError *error, void *user_data)
+{
+	struct wpa_supplicant *wpa_s = user_data;
+	dbus_bool_t roam_complete = os_reltime_initialized(&wpa_s->roam_time) ?
+				    TRUE : FALSE;
+
+	return wpas_dbus_simple_property_getter(iter, DBUS_TYPE_BOOLEAN,
+						&roam_complete, error);
+}
+
+
+/**
+ * wpas_dbus_getter_session_length - Get most recent BSS session length
+ * @iter: Pointer to incoming dbus message iter
+ * @error: Location to store error on failure
+ * @user_data: Function specific data
+ * Returns: TRUE on success, FALSE on failure
+ *
+ * Getter for "SessionLength" property.
+ */
+dbus_bool_t wpas_dbus_getter_session_length(
+	const struct wpa_dbus_property_desc *property_desc,
+	DBusMessageIter *iter, DBusError *error, void *user_data)
+{
+	struct wpa_supplicant *wpa_s = user_data;
+	dbus_uint32_t session_length = wpa_s->session_length.sec * 1000 +
+				       wpa_s->session_length.usec / 1000;
+
+	return wpas_dbus_simple_property_getter(iter, DBUS_TYPE_UINT32,
+						&session_length, error);
+}
+
+
 /**
  * wpas_dbus_getter_bss_expire_age - Get BSS entry expiration age
  * @iter: Pointer to incoming dbus message iter
diff --git a/wpa_supplicant/dbus/dbus_new_handlers.h b/wpa_supplicant/dbus/dbus_new_handlers.h
index 26652ad3d..7a03b7f9f 100644
--- a/wpa_supplicant/dbus/dbus_new_handlers.h
+++ b/wpa_supplicant/dbus/dbus_new_handlers.h
@@ -148,6 +148,9 @@  DECLARE_ACCESSOR(wpas_dbus_setter_fast_reauth);
 DECLARE_ACCESSOR(wpas_dbus_getter_disconnect_reason);
 DECLARE_ACCESSOR(wpas_dbus_getter_disassociate_reason);
 DECLARE_ACCESSOR(wpas_dbus_getter_assoc_status_code);
+DECLARE_ACCESSOR(wpas_dbus_getter_roam_time);
+DECLARE_ACCESSOR(wpas_dbus_getter_roam_complete);
+DECLARE_ACCESSOR(wpas_dbus_getter_session_length);
 DECLARE_ACCESSOR(wpas_dbus_getter_bss_expire_age);
 DECLARE_ACCESSOR(wpas_dbus_setter_bss_expire_age);
 DECLARE_ACCESSOR(wpas_dbus_getter_bss_expire_count);
diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c
index 83df04f39..a2b04716d 100644
--- a/wpa_supplicant/notify.c
+++ b/wpa_supplicant/notify.c
@@ -149,6 +149,33 @@  void wpas_notify_assoc_status_code(struct wpa_supplicant *wpa_s)
 }
 
 
+void wpas_notify_roam_time(struct wpa_supplicant *wpa_s)
+{
+	if (wpa_s->p2p_mgmt)
+		return;
+
+	wpas_dbus_signal_prop_changed(wpa_s, WPAS_DBUS_PROP_ROAM_TIME);
+}
+
+
+void wpas_notify_roam_complete(struct wpa_supplicant *wpa_s)
+{
+	if (wpa_s->p2p_mgmt)
+		return;
+
+	wpas_dbus_signal_prop_changed(wpa_s, WPAS_DBUS_PROP_ROAM_COMPLETE);
+}
+
+
+void wpas_notify_session_length(struct wpa_supplicant *wpa_s)
+{
+	if (wpa_s->p2p_mgmt)
+		return;
+
+	wpas_dbus_signal_prop_changed(wpa_s, WPAS_DBUS_PROP_SESSION_LENGTH);
+}
+
+
 void wpas_notify_network_changed(struct wpa_supplicant *wpa_s)
 {
 	if (wpa_s->p2p_mgmt)
diff --git a/wpa_supplicant/notify.h b/wpa_supplicant/notify.h
index 3ca933c76..bde20e83d 100644
--- a/wpa_supplicant/notify.h
+++ b/wpa_supplicant/notify.h
@@ -24,6 +24,9 @@  void wpas_notify_state_changed(struct wpa_supplicant *wpa_s,
 			       enum wpa_states old_state);
 void wpas_notify_disconnect_reason(struct wpa_supplicant *wpa_s);
 void wpas_notify_assoc_status_code(struct wpa_supplicant *wpa_s);
+void wpas_notify_roam_time(struct wpa_supplicant *wpa_s);
+void wpas_notify_roam_complete(struct wpa_supplicant *wpa_s);
+void wpas_notify_session_length(struct wpa_supplicant *wpa_s);
 void wpas_notify_network_changed(struct wpa_supplicant *wpa_s);
 void wpas_notify_ap_scan_changed(struct wpa_supplicant *wpa_s);
 void wpas_notify_bssid_changed(struct wpa_supplicant *wpa_s);
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index dcd787bec..df27c55ad 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -839,6 +839,40 @@  void wpa_supplicant_set_state(struct wpa_supplicant *wpa_s,
 		wpa_supplicant_state_txt(wpa_s->wpa_state),
 		wpa_supplicant_state_txt(state));
 
+	if (state == WPA_AUTHENTICATING && wpa_s->wpa_state == WPA_COMPLETED) {
+		if (wpa_s->sme.prev_bssid_set &&
+		    os_memcmp(wpa_s->sme.prev_bssid, wpa_s->pending_bssid, ETH_ALEN) != 0) {
+			os_get_reltime(&wpa_s->roam_start);
+		}
+	} else if (state == WPA_COMPLETED &&
+		   os_reltime_initialized(&wpa_s->roam_start)) {
+		os_reltime_age(&wpa_s->roam_start, &wpa_s->roam_time);
+		wpa_s->roam_start.sec = 0;
+		wpa_s->roam_start.usec = 0;
+		wpas_notify_auth_changed(wpa_s);
+		wpas_notify_roam_time(wpa_s);
+		wpas_notify_roam_complete(wpa_s);
+	} else if (state == WPA_DISCONNECTED &&
+		   os_reltime_initialized(&wpa_s->roam_start)) {
+		wpa_s->roam_start.sec = 0;
+		wpa_s->roam_start.usec = 0;
+		wpa_s->roam_time.sec = 0;
+		wpa_s->roam_time.usec = 0;
+		wpas_notify_roam_complete(wpa_s);
+	}
+
+	if (state == WPA_COMPLETED && wpa_s->wpa_state != WPA_COMPLETED) {
+		wpas_notify_auth_changed(wpa_s);
+		os_get_reltime(&wpa_s->session_start);
+	} else if (state != WPA_COMPLETED &&
+		   wpa_s->wpa_state == WPA_COMPLETED &&
+		   os_reltime_initialized(&wpa_s->session_start)) {
+		os_reltime_age(&wpa_s->session_start, &wpa_s->session_length);
+		wpa_s->session_start.sec = 0;
+		wpa_s->session_start.usec = 0;
+		wpas_notify_session_length(wpa_s);
+	}
+
 	if (state == WPA_INTERFACE_DISABLED) {
 		/* Assure normal scan when interface is restored */
 		wpa_s->normal_scans = 0;
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 4d18177fb..a4d56b810 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -492,6 +492,10 @@  struct wpa_supplicant {
 	struct wpa_supplicant *next;
 	struct l2_packet_data *l2;
 	struct l2_packet_data *l2_br;
+	struct os_reltime roam_start;
+	struct os_reltime roam_time;
+	struct os_reltime session_start;
+	struct os_reltime session_length;
 	unsigned char own_addr[ETH_ALEN];
 	unsigned char perm_addr[ETH_ALEN];
 	char ifname[100];