diff --git a/doc/dbus.doxygen b/doc/dbus.doxygen
index 2ca40ae9b..d3003b31d 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 - i - (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 - i - (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..168e9baf6 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, "i",
+	  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, "i",
+	  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..4e3412cdc 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_int32_t roam_time = wpa_s->roam_time.sec * 1000 +
+				 wpa_s->roam_time.usec / 1000;
+
+	return wpas_dbus_simple_property_getter(iter, DBUS_TYPE_INT32,
+						&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_int32_t session_length = wpa_s->session_length.sec * 1000 +
+				      wpa_s->session_length.usec / 1000;
+
+	return wpas_dbus_simple_property_getter(iter, DBUS_TYPE_INT32,
+						&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];
