qcacld-3.0: Handle NUD events within driver

Currently NUD events are used by Framework to detect
the Network Reachability. Framework issues disconnection
for NUD_FAILED event.

Now, NUD events are tracked within driver to detect the
reachability and based on TxRx traffic, driver takes decision
to issue disconnection.

Change-Id: I461610c220288ff1fd718bb7bc2dd8375588505c
CRs-Fixed: 2195796
diff --git a/Kbuild b/Kbuild
index 72236e0..d6a9805 100644
--- a/Kbuild
+++ b/Kbuild
@@ -421,6 +421,10 @@
 #Flag to enable p2p debug feature
 CONFIG_WLAN_FEATURE_P2P_DEBUG := 1
 
+#Flag to enable nud tracking feature
+CONFIG_WLAN_NUD_TRACKING := 1
+
+
 ifeq ($(CONFIG_CFG80211),y)
 HAVE_CFG80211 := 1
 else
@@ -558,6 +562,10 @@
 HDD_OBJS += $(HDD_SRC_DIR)/wlan_hdd_rx_monitor.o
 endif
 
+ifeq ($(CONFIG_WLAN_NUD_TRACKING), 1)
+HDD_OBJS += $(HDD_SRC_DIR)/wlan_hdd_nud_tracking.o
+endif
+
 ########### HOST DIAG LOG ###########
 HOST_DIAG_LOG_DIR :=	$(WLAN_COMMON_ROOT)/utils/host_diag_log
 
@@ -1876,6 +1884,7 @@
 endif
 ####################################
 
+
 ifeq ($(CONFIG_FEATURE_HTC_CREDIT_HISTORY), 1)
 CDEFINES += -DFEATURE_HTC_CREDIT_HISTORY
 endif
@@ -2552,6 +2561,11 @@
 CDEFINES += -DENABLE_SMMU_S1_TRANSLATION
 endif
 
+#Flag to enable NUD tracking
+ifeq ($(CONFIG_WLAN_NUD_TRACKING), 1)
+CDEFINES += -DWLAN_NUD_TRACKING
+endif
+
 KBUILD_CPPFLAGS += $(CDEFINES)
 
 # Currently, for versions of gcc which support it, the kernel Makefile
diff --git a/core/hdd/inc/wlan_hdd_cfg.h b/core/hdd/inc/wlan_hdd_cfg.h
index 8526201..982f791 100644
--- a/core/hdd/inc/wlan_hdd_cfg.h
+++ b/core/hdd/inc/wlan_hdd_cfg.h
@@ -96,6 +96,31 @@
 #define CFG_ENABLE_CONNECTED_SCAN_MAX         (1)
 #define CFG_ENABLE_CONNECTED_SCAN_DEFAULT     (1)
 
+#ifdef WLAN_NUD_TRACKING
+/*
+ * <ini>
+ * gEnableNUDTracking - Will enable or disable NUD tracking within driver
+ * @Min: 0
+ * @Max: 1
+ * @Default: 0
+ *
+ * This ini is used to enable or disable NUD tracking within driver
+ *
+ * Related: None
+ *
+ * Supported Feature: STA
+ *
+ * Usage: External
+ *
+ * <ini>
+ */
+
+#define CFG_ENABLE_NUD_TRACKING_NAME            "gEnableNUDTracking"
+#define CFG_ENABLE_NUD_TRACKING_MIN             (0)
+#define CFG_ENABLE_NUD_TRACKING_MAX             (1)
+#define CFG_ENABLE_NUD_TRACKING_DEFAULT         (0)
+#endif
+
 /*
  * <ini>
  * RTSThreshold - Will provide RTSThreshold
@@ -14375,6 +14400,9 @@
 	DECLARE_BITMAP(bExplicitCfg, MAX_CFG_INI_ITEMS);
 
 	/* Config parameters */
+#ifdef WLAN_NUD_TRACKING
+	bool enable_nud_tracking;
+#endif
 	bool enable_connected_scan;
 	uint32_t RTSThreshold;
 	uint32_t FragmentationThreshold;
diff --git a/core/hdd/inc/wlan_hdd_main.h b/core/hdd/inc/wlan_hdd_main.h
index 9c0b725..13cabfb 100644
--- a/core/hdd/inc/wlan_hdd_main.h
+++ b/core/hdd/inc/wlan_hdd_main.h
@@ -98,6 +98,10 @@
 #endif
 #include "wlan_hdd_he.h"
 
+#include <net/neighbour.h>
+#include <net/netevent.h>
+#include "wlan_hdd_nud_tracking.h"
+
 /*
  * Preprocessor definitions and constants
  */
@@ -1157,6 +1161,12 @@
 	/** Current MAC Address for the adapter  */
 	struct qdf_mac_addr mac_addr;
 
+#ifdef WLAN_NUD_TRACKING
+	struct hdd_nud_tracking_info nud_tracking;
+#endif
+	bool disconnection_in_progress;
+	qdf_mutex_t disconnection_status_lock;
+
 	/**Event Flags*/
 	unsigned long event_flags;
 
@@ -3109,4 +3119,13 @@
  */
 bool hdd_is_cli_iface_up(struct hdd_context *hdd_ctx);
 
+/**
+ * hdd_set_disconnect_status() - set adapter disconnection status
+ * @hdd_adapter: Pointer to hdd adapter
+ * @disconnecting: Disconnect status to set
+ *
+ * Return: None
+ */
+void hdd_set_disconnect_status(struct hdd_adapter *adapter, bool disconnecting);
+
 #endif /* end #if !defined(WLAN_HDD_MAIN_H) */
diff --git a/core/hdd/inc/wlan_hdd_tx_rx.h b/core/hdd/inc/wlan_hdd_tx_rx.h
index c8c4d51..7ffd112 100644
--- a/core/hdd/inc/wlan_hdd_tx_rx.h
+++ b/core/hdd/inc/wlan_hdd_tx_rx.h
@@ -205,4 +205,11 @@
 	}
 }
 
+/**
+ * hdd_txrx_get_tx_ack_count() - get tx acked count
+ * @adapter: Pointer to adapter
+ *
+ * Return: tx acked count
+ */
+uint32_t hdd_txrx_get_tx_ack_count(struct hdd_adapter *adapter);
 #endif /* end #if !defined(WLAN_HDD_TX_RX_H) */
diff --git a/core/hdd/src/wlan_hdd_assoc.c b/core/hdd/src/wlan_hdd_assoc.c
index 459b41e..68c46ac 100644
--- a/core/hdd/src/wlan_hdd_assoc.c
+++ b/core/hdd/src/wlan_hdd_assoc.c
@@ -69,6 +69,7 @@
 #include "wlan_p2p_ucfg_api.h"
 #include "wlan_ipa_ucfg_api.h"
 
+#include "wlan_hdd_nud_tracking.h"
 /* These are needed to recognize WPA and RSN suite types */
 #define HDD_WPA_OUI_SIZE 4
 #define HDD_RSN_OUI_SIZE 4
@@ -1893,6 +1894,10 @@
 	/* Unblock anyone waiting for disconnect to complete */
 	complete(&adapter->disconnect_comp_var);
 
+	hdd_nud_reset_tracking(adapter);
+
+	hdd_set_disconnect_status(adapter, false);
+
 	hdd_reset_limit_off_chan(adapter);
 
 	hdd_print_bss_info(sta_ctx);
diff --git a/core/hdd/src/wlan_hdd_cfg.c b/core/hdd/src/wlan_hdd_cfg.c
index f50a4e4..cad8e15 100644
--- a/core/hdd/src/wlan_hdd_cfg.c
+++ b/core/hdd/src/wlan_hdd_cfg.c
@@ -349,6 +349,15 @@
 
 
 struct reg_table_entry g_registry_table[] = {
+#ifdef WLAN_NUD_TRACKING
+	REG_VARIABLE(CFG_ENABLE_NUD_TRACKING_NAME, WLAN_PARAM_Integer,
+		     struct hdd_config, enable_nud_tracking,
+		     VAR_FLAGS_OPTIONAL | VAR_FLAGS_RANGE_CHECK_ASSUME_DEFAULT,
+		     CFG_ENABLE_NUD_TRACKING_DEFAULT,
+		     CFG_ENABLE_NUD_TRACKING_MIN,
+		     CFG_ENABLE_NUD_TRACKING_MAX),
+#endif
+
 	REG_VARIABLE(CFG_ENABLE_CONNECTED_SCAN_NAME, WLAN_PARAM_Integer,
 		     struct hdd_config, enable_connected_scan,
 		     VAR_FLAGS_OPTIONAL | VAR_FLAGS_RANGE_CHECK_ASSUME_DEFAULT,
@@ -7446,6 +7455,8 @@
 	hdd_debug("Name = [%s] value = [0x%x]",
 		  CFG_CHANNEL_SELECT_LOGIC_CONC_NAME,
 		  hdd_ctx->config->channel_select_logic_conc);
+
+	hdd_nud_cfg_print(hdd_ctx);
 }
 
 
diff --git a/core/hdd/src/wlan_hdd_cfg80211.c b/core/hdd/src/wlan_hdd_cfg80211.c
index 3787522..dcf3e0e 100644
--- a/core/hdd/src/wlan_hdd_cfg80211.c
+++ b/core/hdd/src/wlan_hdd_cfg80211.c
@@ -18002,6 +18002,8 @@
 			hdd_conn_set_connection_state(adapter,
 			eConnectionState_Connecting);
 
+		hdd_set_disconnect_status(adapter, false);
+
 		qdf_runtime_pm_prevent_suspend(
 				&hdd_ctx->runtime_context.connect);
 		hdd_prevent_suspend_timeout(HDD_WAKELOCK_TIMEOUT_CONNECT,
@@ -19739,6 +19741,15 @@
 	if (0 != status)
 		return status;
 
+	qdf_mutex_acquire(&adapter->disconnection_status_lock);
+	if (adapter->disconnection_in_progress) {
+		qdf_mutex_release(&adapter->disconnection_status_lock);
+		hdd_debug("Disconnect is already in progress");
+		return 0;
+	}
+	adapter->disconnection_in_progress = true;
+	qdf_mutex_release(&adapter->disconnection_status_lock);
+
 	/* Issue disconnect request to SME, if station is in connected state */
 	if ((sta_ctx->conn_info.connState == eConnectionState_Associated) ||
 	    (sta_ctx->conn_info.connState == eConnectionState_Connecting)) {
@@ -19794,11 +19805,13 @@
 		status = wlan_hdd_disconnect(adapter, reasonCode);
 		if (0 != status) {
 			hdd_err("wlan_hdd_disconnect failed, status: %d", status);
+			hdd_set_disconnect_status(adapter, false);
 			return -EINVAL;
 		}
 	} else {
 		hdd_err("Unexpected cfg disconnect called while in state: %d",
 		       sta_ctx->conn_info.connState);
+		hdd_set_disconnect_status(adapter, false);
 	}
 
 	return status;
diff --git a/core/hdd/src/wlan_hdd_main.c b/core/hdd/src/wlan_hdd_main.c
index 510e1b8..a23a78b 100644
--- a/core/hdd/src/wlan_hdd_main.c
+++ b/core/hdd/src/wlan_hdd_main.c
@@ -133,6 +133,7 @@
 #include "wlan_disa_ucfg_api.h"
 #include "wlan_ipa_ucfg_api.h"
 #include <target_if.h>
+#include "wlan_hdd_nud_tracking.h"
 
 #ifdef CNSS_GENL
 #include <net/cnss_nl.h>
@@ -3672,6 +3673,7 @@
 
 		adapter->offloads_configured = false;
 		adapter->is_link_up_service_needed = false;
+		adapter->disconnection_in_progress = false;
 		/* Init the net_device structure */
 		strlcpy(dev->name, name, IFNAMSIZ);
 
@@ -4181,6 +4183,9 @@
 		return;
 	}
 
+	hdd_nud_deinit_tracking(adapter);
+	qdf_mutex_destroy(&adapter->disconnection_status_lock);
+
 	hdd_debugfs_exit(adapter);
 
 	/*
@@ -4677,6 +4682,10 @@
 				goto err_free_netdev;
 		}
 
+		hdd_nud_init_tracking(adapter);
+
+		qdf_mutex_create(&adapter->disconnection_status_lock);
+
 		break;
 
 	case QDF_P2P_GO_MODE:
@@ -4930,6 +4939,9 @@
 
 	hdd_enter();
 
+	hdd_nud_ignore_tracking(adapter, true);
+	hdd_nud_reset_tracking(adapter);
+
 	hdd_debug("Disabling queues");
 	wlan_hdd_netif_queue_control(adapter,
 				     WLAN_STOP_ALL_NETIF_QUEUE_N_CARRIER,
@@ -5311,6 +5323,10 @@
 			}
 		}
 
+		hdd_nud_reset_tracking(adapter);
+		hdd_nud_ignore_tracking(adapter, true);
+		hdd_set_disconnect_status(adapter, false);
+
 		hdd_softap_deinit_tx_rx(adapter);
 
 		/* Destroy vdev which will be recreated during reinit. */
@@ -5916,6 +5932,7 @@
 					hdd_tx_resume_cb,
 					hdd_tx_flow_control_is_pause);
 
+			hdd_nud_ignore_tracking(adapter, false);
 			break;
 
 		case QDF_SAP_MODE:
@@ -6365,6 +6382,14 @@
 }
 #endif
 
+void hdd_set_disconnect_status(struct hdd_adapter *adapter, bool status)
+{
+	qdf_mutex_acquire(&adapter->disconnection_status_lock);
+	adapter->disconnection_in_progress = status;
+	qdf_mutex_release(&adapter->disconnection_status_lock);
+	hdd_debug("setting disconnection status: %d", status);
+}
+
 /**
  * hdd_register_notifiers - Register netdev notifiers.
  * @hdd_ctx: HDD context
@@ -6388,8 +6413,16 @@
 		goto unregister_ip6_notifier;
 	}
 
+	ret = hdd_nud_register_netevent_notifier(hdd_ctx);
+	if (ret) {
+		hdd_err("Failed to register netevent notifier: %d",
+			ret);
+		goto unregister_inetaddr_notifier;
+	}
 	return 0;
 
+unregister_inetaddr_notifier:
+	unregister_inetaddr_notifier(&hdd_ctx->ipv4_notifier);
 unregister_ip6_notifier:
 	hdd_wlan_unregister_ip6_notifier(hdd_ctx);
 out:
@@ -6407,6 +6440,7 @@
  */
 void hdd_unregister_notifiers(struct hdd_context *hdd_ctx)
 {
+	hdd_nud_unregister_netevent_notifier(hdd_ctx);
 	hdd_wlan_unregister_ip6_notifier(hdd_ctx);
 
 	unregister_inetaddr_notifier(&hdd_ctx->ipv4_notifier);
diff --git a/core/hdd/src/wlan_hdd_nud_tracking.c b/core/hdd/src/wlan_hdd_nud_tracking.c
new file mode 100644
index 0000000..bceff98
--- /dev/null
+++ b/core/hdd/src/wlan_hdd_nud_tracking.c
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) 2018 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * DOC: contains nud event tracking main function definitions
+ */
+
+#include "wlan_hdd_main.h"
+
+void hdd_nud_set_gateway_addr(struct hdd_adapter *adapter,
+			      struct qdf_mac_addr gw_mac_addr)
+{
+	qdf_mem_copy(adapter->nud_tracking.gw_mac_addr.bytes,
+		     gw_mac_addr.bytes,
+		     sizeof(struct qdf_mac_addr));
+}
+
+void hdd_nud_cfg_print(struct hdd_context *hdd_ctx)
+{
+	hdd_debug("Name = [%s] value = [0x%x]",
+		  CFG_ENABLE_NUD_TRACKING_NAME,
+		  hdd_ctx->config->enable_nud_tracking);
+}
+
+void hdd_nud_incr_gw_rx_pkt_cnt(struct hdd_adapter *adapter,
+				struct qdf_mac_addr *mac_addr)
+{
+	if (!adapter->nud_tracking.is_gw_rx_pkt_track_enabled)
+		return;
+
+	if (qdf_is_macaddr_equal(&adapter->nud_tracking.gw_mac_addr,
+				 mac_addr))
+		qdf_atomic_inc(&adapter
+			       ->nud_tracking.tx_rx_stats.gw_rx_packets);
+}
+
+/**
+ * hdd_nud_flush_work() - flush pending nud work
+ * @adapter: Pointer to hdd adapter
+ *
+ * Return: None
+ */
+static inline void
+hdd_nud_flush_work(struct hdd_adapter *adapter)
+{
+	qdf_disable_work(&adapter->nud_tracking.nud_event_work);
+}
+
+void hdd_nud_deinit_tracking(struct hdd_adapter *adapter)
+{
+	qdf_destroy_work(NULL, &adapter->nud_tracking.nud_event_work);
+}
+
+void hdd_nud_ignore_tracking(struct hdd_adapter *adapter, bool ignoring)
+{
+	struct hdd_context *hdd_ctx;
+
+	hdd_ctx = WLAN_HDD_GET_CTX(adapter);
+
+	if (adapter->device_mode == QDF_STA_MODE &&
+	    hdd_ctx->config->enable_nud_tracking)
+		adapter->nud_tracking.ignore_nud_tracking = ignoring;
+}
+
+void hdd_nud_reset_tracking(struct hdd_adapter *adapter)
+{
+	struct hdd_context *hdd_ctx;
+
+	hdd_ctx = WLAN_HDD_GET_CTX(adapter);
+
+	if (adapter->device_mode == QDF_STA_MODE &&
+	    hdd_ctx->config->enable_nud_tracking) {
+		hdd_debug("De Initialize the NUD tracking");
+
+		qdf_zero_macaddr(&adapter->nud_tracking.gw_mac_addr);
+		qdf_mem_zero(&adapter->nud_tracking.tx_rx_stats,
+			     sizeof(struct hdd_nud_tx_rx_stats));
+
+		adapter->nud_tracking.curr_state = NUD_NONE;
+		qdf_atomic_set(&adapter
+			       ->nud_tracking.tx_rx_stats.gw_rx_packets, 0);
+
+		hdd_nud_flush_work(adapter);
+	}
+}
+
+/**
+ * hdd_nud_stats_info() - display wlan NUD stats info
+ * @hdd_adapter: Pointer to hdd adapter
+ *
+ * Return: None
+ */
+static void hdd_nud_stats_info(struct hdd_adapter *adapter)
+{
+	hdd_debug("**** NUD STATS: ****");
+	hdd_debug("NUD Probe Tx  : %d",
+		  adapter->nud_tracking.tx_rx_stats.pre_tx_packets);
+	hdd_debug("NUD Probe Ack : %d",
+		  adapter->nud_tracking.tx_rx_stats.pre_tx_acked);
+	hdd_debug("NUD Probe Rx  : %d",
+		  adapter->nud_tracking.tx_rx_stats.pre_rx_packets);
+	hdd_debug("NUD Failure Tx  : %d",
+		  adapter->nud_tracking.tx_rx_stats.post_tx_packets);
+	hdd_debug("NUD Failure Ack : %d",
+		  adapter->nud_tracking.tx_rx_stats.post_tx_acked);
+	hdd_debug("NUD Failure Rx  : %d",
+		  adapter->nud_tracking.tx_rx_stats.post_rx_packets);
+	hdd_debug("NUD Gateway Rx  : %d",
+		  qdf_atomic_read(&adapter
+				  ->nud_tracking.tx_rx_stats.gw_rx_packets));
+}
+
+/**
+ * hdd_nud_capture_stats() - capture wlan NUD stats
+ * @hdd_adapter: Pointer to hdd adapter
+ * @nud_state: NUD state for which stats to capture
+ *
+ * Return: None
+ */
+static void hdd_nud_capture_stats(struct hdd_adapter *adapter,
+				  uint8_t nud_state)
+{
+	switch (nud_state) {
+	case NUD_INCOMPLETE:
+	case NUD_PROBE:
+		adapter->nud_tracking.tx_rx_stats.pre_tx_packets =
+				adapter->stats.tx_packets;
+		adapter->nud_tracking.tx_rx_stats.pre_rx_packets =
+				adapter->stats.rx_packets;
+		adapter->nud_tracking.tx_rx_stats.pre_tx_acked =
+				hdd_txrx_get_tx_ack_count(adapter);
+		break;
+	case NUD_FAILED:
+		adapter->nud_tracking.tx_rx_stats.post_tx_packets =
+				adapter->stats.tx_packets;
+		adapter->nud_tracking.tx_rx_stats.post_rx_packets =
+				adapter->stats.rx_packets;
+		adapter->nud_tracking.tx_rx_stats.post_tx_acked =
+				hdd_txrx_get_tx_ack_count(adapter);
+		break;
+	default:
+		break;
+	}
+}
+
+/**
+ * hdd_nud_honour_failure() - check if nud failure to be honored
+ * @hdd_adapter: Pointer to hdd_adapter
+ *
+ * Return: true if nud failure to be honored, else false.
+ */
+static bool hdd_nud_honour_failure(struct hdd_adapter *adapter)
+{
+	uint32_t tx_transmitted;
+	uint32_t tx_acked;
+	uint32_t gw_rx_pkt;
+
+	tx_transmitted = adapter->nud_tracking.tx_rx_stats.post_tx_packets -
+		adapter->nud_tracking.tx_rx_stats.pre_tx_packets;
+	tx_acked = adapter->nud_tracking.tx_rx_stats.post_tx_acked -
+		adapter->nud_tracking.tx_rx_stats.pre_tx_acked;
+	gw_rx_pkt = qdf_atomic_read(&adapter
+			->nud_tracking.tx_rx_stats.gw_rx_packets);
+
+	if (!tx_transmitted || !tx_acked || !gw_rx_pkt) {
+		hdd_debug("NUD_FAILURE_HONORED");
+		hdd_nud_stats_info(adapter);
+		return true;
+	}
+	hdd_debug("NUD_FAILURE_NOT_HONORED");
+	hdd_nud_stats_info(adapter);
+	return false;
+}
+
+/**
+ * hdd_nud_set_tracking() - set the NUD tracking info
+ * @hdd_adapter: Pointer to hdd_adapter
+ * @nud_state: Current NUD state to set
+ * @capture_enabled: GW Rx packet to be capture or not
+ *
+ * Return: None
+ */
+static void hdd_nud_set_tracking(struct hdd_adapter *adapter,
+				 uint8_t nud_state,
+				 bool capture_enabled)
+{
+	hdd_debug("set the NUD tracking");
+
+	adapter->nud_tracking.curr_state = nud_state;
+	qdf_atomic_set(&adapter->nud_tracking.tx_rx_stats.gw_rx_packets, 0);
+	adapter->nud_tracking.is_gw_rx_pkt_track_enabled = capture_enabled;
+}
+
+/**
+ * __hdd_nud_failure_work() - work for nud event
+ * @data: Pointer to hdd_adapter
+ *
+ * Return: None
+ */
+static void __hdd_nud_failure_work(void *data)
+{
+	struct hdd_adapter *adapter;
+	struct hdd_context *hdd_ctx;
+	eConnectionState conn_state;
+	int status;
+
+	hdd_enter();
+
+	if (!data)
+		return;
+
+	adapter = (struct hdd_adapter *)data;
+
+	status = hdd_validate_adapter(adapter);
+	if (0 != status)
+		return;
+
+	hdd_ctx = WLAN_HDD_GET_CTX(adapter);
+	status = wlan_hdd_validate_context(hdd_ctx);
+	if (0 != status)
+		return;
+
+	conn_state = (WLAN_HDD_GET_STATION_CTX_PTR(adapter))
+		      ->conn_info.connState;
+
+	if (eConnectionState_Associated != conn_state) {
+		hdd_debug("Not in Connected State");
+		return;
+	}
+
+	qdf_mutex_acquire(&adapter->disconnection_status_lock);
+	if (adapter->disconnection_in_progress) {
+		qdf_mutex_release(&adapter->disconnection_status_lock);
+		hdd_debug("Disconnect is already in progress");
+		return;
+	}
+	adapter->disconnection_in_progress = true;
+	qdf_mutex_release(&adapter->disconnection_status_lock);
+
+	hdd_debug("Disconnecting STA with session id: %d",
+		  adapter->session_id);
+	/* Issue Disconnect */
+	status = wlan_hdd_disconnect(adapter, eCSR_DISCONNECT_REASON_DEAUTH);
+	if (0 != status) {
+		hdd_err("wlan_hdd_disconnect failed, status: %d", status);
+		hdd_set_disconnect_status(adapter, false);
+	}
+
+	hdd_exit();
+}
+
+/**
+ * hdd_nud_failure_work() - work for nud event
+ * @data: Pointer to hdd_adapter
+ *
+ * Return: None
+ */
+static void hdd_nud_failure_work(void *data)
+{
+	cds_ssr_protect(__func__);
+	__hdd_nud_failure_work(data);
+	cds_ssr_unprotect(__func__);
+}
+
+void hdd_nud_init_tracking(struct hdd_adapter *adapter)
+{
+	struct hdd_context *hdd_ctx;
+
+	hdd_ctx = WLAN_HDD_GET_CTX(adapter);
+
+	if (adapter->device_mode == QDF_STA_MODE &&
+	    hdd_ctx->config->enable_nud_tracking) {
+		hdd_debug("Initialize the NUD tracking");
+
+		qdf_zero_macaddr(&adapter->nud_tracking.gw_mac_addr);
+		qdf_mem_zero(&adapter->nud_tracking.tx_rx_stats,
+			     sizeof(struct hdd_nud_tx_rx_stats));
+
+		adapter->nud_tracking.curr_state = NUD_NONE;
+		adapter->nud_tracking.ignore_nud_tracking = false;
+
+		qdf_atomic_init(&adapter
+				->nud_tracking.tx_rx_stats.gw_rx_packets);
+		qdf_create_work(0, &adapter->nud_tracking.nud_event_work,
+				hdd_nud_failure_work,
+				(void *)adapter);
+	}
+}
+
+/**
+ * hdd_nud_process_failure_event() - processing NUD_FAILED event
+ * @hdd_adapter: Pointer to hdd_adapter
+ *
+ * Return: None
+ */
+static void hdd_nud_process_failure_event(struct hdd_adapter *adapter)
+{
+	uint8_t curr_state;
+
+	curr_state = adapter->nud_tracking.curr_state;
+	if (curr_state == NUD_PROBE || curr_state == NUD_INCOMPLETE) {
+		hdd_nud_capture_stats(adapter, NUD_FAILED);
+		if (hdd_nud_honour_failure(adapter))
+			qdf_sched_work(0, &adapter
+					->nud_tracking.nud_event_work);
+		else
+			hdd_nud_set_tracking(adapter, NUD_NONE, false);
+	} else {
+		hdd_debug("NUD FAILED -> Current State [0x%x]", curr_state);
+	}
+}
+
+/**
+ * hdd_nud_filter_netevent() - filter netevents for STA interface
+ * @neighbour: Pointer to neighbour
+ *
+ * Return: None
+ */
+static void hdd_nud_filter_netevent(struct neighbour *neigh)
+{
+	int status;
+	struct hdd_adapter *adapter;
+	struct hdd_context *hdd_ctx;
+	eConnectionState conn_state;
+	const struct net_device *netdev = neigh->dev;
+
+	hdd_enter();
+
+	hdd_ctx = cds_get_context(QDF_MODULE_ID_HDD);
+	status = wlan_hdd_validate_context(hdd_ctx);
+	if (0 != status)
+		return;
+
+	adapter = hdd_get_adapter_by_macaddr(hdd_ctx, netdev->dev_addr);
+	status = hdd_validate_adapter(adapter);
+	if (0 != status)
+		return;
+
+	if (adapter->device_mode != QDF_STA_MODE) {
+		hdd_err("Device_mode %s(%d) is not supported for NUD handling",
+			hdd_device_mode_to_string(adapter->device_mode),
+			adapter->device_mode);
+		return;
+	}
+	conn_state = (WLAN_HDD_GET_STATION_CTX_PTR(adapter))
+		->conn_info.connState;
+
+	if (eConnectionState_Associated != conn_state) {
+		hdd_debug("Not in Connected State");
+		return;
+	}
+
+	if (adapter->nud_tracking.ignore_nud_tracking) {
+		hdd_debug("NUD Tracking is Disabled");
+		return;
+	}
+
+	if (!qdf_is_macaddr_equal(&adapter->nud_tracking.gw_mac_addr,
+				  (struct qdf_mac_addr *)&neigh->ha[0])) {
+		hdd_debug("NUD event not for registered GW");
+		return;
+	}
+	switch (neigh->nud_state) {
+	case NUD_PROBE:
+	case NUD_INCOMPLETE:
+		hdd_debug("NUD_START [0x%x]", neigh->nud_state);
+		hdd_nud_capture_stats(adapter, neigh->nud_state);
+		hdd_nud_set_tracking(adapter,
+				     neigh->nud_state,
+				     true);
+		break;
+
+	case NUD_REACHABLE:
+		hdd_debug("NUD_REACHABLE [0x%x]", neigh->nud_state);
+		hdd_nud_set_tracking(adapter, NUD_NONE, false);
+		break;
+
+	case NUD_FAILED:
+		hdd_debug("NUD_FAILED [0x%x]", neigh->nud_state);
+		hdd_nud_process_failure_event(adapter);
+		break;
+	default:
+		hdd_debug("NUD Event For Other State [0x%x]",
+			  neigh->nud_state);
+		break;
+	}
+	hdd_exit();
+}
+
+/**
+ * __hdd_nud_netevent_cb() - netevent callback
+ * @nb: Pointer to notifier block
+ * @event: Net Event triggered
+ * @data: Pointer to neighbour struct
+ *
+ * Callback for netevent
+ *
+ * Return: None
+ */
+static void __hdd_nud_netevent_cb(struct notifier_block *nb,
+				  unsigned long event,
+				  void *data)
+{
+	hdd_enter();
+	hdd_nud_filter_netevent(data);
+	hdd_exit();
+}
+
+/**
+ * hdd_nud_netevent_cb() - netevent callback
+ * @nb: Pointer to notifier block
+ * @event: Net Event triggered
+ * @data: Pointer to neighbour struct
+ *
+ * Callback for netevent
+ *
+ * Return: 0 on success
+ */
+static int hdd_nud_netevent_cb(struct notifier_block *nb, unsigned long event,
+			       void *data)
+{
+	switch (event) {
+	case NETEVENT_NEIGH_UPDATE:
+		cds_ssr_protect(__func__);
+		__hdd_nud_netevent_cb(nb, event, data);
+		cds_ssr_unprotect(__func__);
+		break;
+	case NETEVENT_REDIRECT:
+	default:
+		break;
+	}
+	return 0;
+}
+
+static struct notifier_block wlan_netevent_nb = {
+	.notifier_call = hdd_nud_netevent_cb
+};
+
+int hdd_nud_register_netevent_notifier(struct hdd_context *hdd_ctx)
+{
+	int ret = 0;
+
+	if (hdd_ctx->config->enable_nud_tracking) {
+		ret = register_netevent_notifier(&wlan_netevent_nb);
+		if (!ret)
+			hdd_debug("Registered netevent notifier");
+	}
+	return ret;
+}
+
+void hdd_nud_unregister_netevent_notifier(struct hdd_context *hdd_ctx)
+{
+	int ret;
+
+	if (hdd_ctx->config->enable_nud_tracking) {
+		ret = unregister_netevent_notifier(&wlan_netevent_nb);
+		if (!ret)
+			hdd_debug("Unregistered netevent notifier");
+	}
+}
diff --git a/core/hdd/src/wlan_hdd_nud_tracking.h b/core/hdd/src/wlan_hdd_nud_tracking.h
new file mode 100644
index 0000000..4ba8d0c
--- /dev/null
+++ b/core/hdd/src/wlan_hdd_nud_tracking.h
@@ -0,0 +1,193 @@
+ /*
+ * Copyright (c) 2018 The Linux Foundation. All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * DOC: contains nud event tracking function declarations
+ */
+
+#ifndef _WLAN_NUD_TRACKING_H_
+#define _WLAN_NUD_TRACKING_H_
+
+#ifdef WLAN_NUD_TRACKING
+
+/**
+ * struct hdd_nud_tx_rx_stats - Capture tx and rx count during NUD tracking
+ * @pre_tx_packets: Number of tx packets at NUD_PROBE event
+ * @pre_tx_acked: Number of tx acked at NUD_PROBE event
+ * @pre_rx_packets: Number of rx packets at NUD_PROBE event
+ * @post_tx_packets: Number of tx packets at NUD_FAILED event
+ * @post_tx_acked: Number of tx acked at NUD_FAILED event
+ * @post_rx_packets: Number of rx packets at NUD_FAILED event
+ * @gw_rx_packets: Number of rx packets from the registered gateway
+ *                 during the period from NUD_PROBE to NUD_FAILED
+ */
+struct hdd_nud_tx_rx_stats {
+	uint32_t pre_tx_packets;
+	uint32_t pre_tx_acked;
+	uint32_t pre_rx_packets;
+	uint32_t post_tx_packets;
+	uint32_t post_tx_acked;
+	uint32_t post_rx_packets;
+	qdf_atomic_t gw_rx_packets;
+};
+
+ /**
+  * struct hdd_nud_tracking_info - structure to keep track for NUD information
+  * @curr_state: current state of NUD machine
+  * @ignore_nud_tracking: true if nud tracking is not required else false
+  * @tx_rx_stats: Number of packets during NUD tracking
+  * @gw_mac_addr: gateway mac address for which NUD events are tracked
+  * @nud_event_work: work to be scheduled during NUD_FAILED
+  * @is_gw_rx_pkt_track_enabled: true if rx pkt capturing is enabled for GW,
+  *                              else false
+  */
+struct hdd_nud_tracking_info {
+	uint8_t curr_state;
+	bool ignore_nud_tracking;
+	struct hdd_nud_tx_rx_stats tx_rx_stats;
+	struct qdf_mac_addr gw_mac_addr;
+	qdf_work_t nud_event_work;
+	bool is_gw_rx_pkt_track_enabled;
+};
+
+/**
+ * hdd_nud_set_gateway_addr() - set gateway mac address
+ * @adapter: Pointer to adapter
+ * @gw_mac_addr: mac address to be set
+ *
+ * Return: none
+ */
+void hdd_nud_set_gateway_addr(struct hdd_adapter *adapter,
+			      struct qdf_mac_addr gw_mac_addr);
+
+/**
+* hdd_nud_cfg_print() - Print nud tracking related parameters
+* @hdd_ctx: Pointer to HDD context
+*
+* Return: None
+*/
+void hdd_nud_cfg_print(struct hdd_context *hdd_ctx);
+
+/**
+ * hdd_nud_incr_gw_rx_pkt_cnt() - Increment rx count for gateway
+ * @adapter: Pointer to adapter
+ * @mac_addr: Gateway mac address
+ *
+ * Return: None
+ */
+void hdd_nud_incr_gw_rx_pkt_cnt(struct hdd_adapter *adapter,
+				struct qdf_mac_addr *mac_addr);
+
+/**
+ * hdd_nud_init_tracking() - initialize NUD tracking
+ * @hdd_adapter: Pointer to hdd adapter
+ *
+ * Return: None
+ */
+void hdd_nud_init_tracking(struct hdd_adapter *adapter);
+
+/**
+ * hdd_nud_reset_tracking() - reset NUD tracking
+ * @hdd_adapter: Pointer to hdd adapter
+ *
+ * Return: None
+ */
+void hdd_nud_reset_tracking(struct hdd_adapter *adapter);
+
+/**
+ * hdd_nud_deinit_tracking() - deinitialize NUD tracking
+ * @hdd_adapter: Pointer to hdd adapter
+ *
+ * Return: None
+ */
+void hdd_nud_deinit_tracking(struct hdd_adapter *adapter);
+
+/**
+ * hdd_nud_ignore_tracking() - set/reset nud trackig status
+ * @data: Pointer to hdd_adapter
+ * @ignoring: Ignore status to set
+ *
+ * Return: None
+ */
+void hdd_nud_ignore_tracking(struct hdd_adapter *adapter,
+			     bool ignoring);
+
+/**
+ * hdd_nud_register_netevent_notifier - Register netevent notifiers.
+ * @hdd_ctx: HDD context
+ *
+ * Register netevent notifiers.
+ *
+ * Return: 0 on success and errno on failure
+ */
+int hdd_nud_register_netevent_notifier(struct hdd_context *hdd_ctx);
+
+/**
+ * hdd_nud_unregister_netevent_notifier - Unregister netevent notifiers.
+ * @hdd_ctx: HDD context
+ *
+ * Unregister netevent notifiers.
+ *
+ * Return: None
+ */
+void hdd_nud_unregister_netevent_notifier(struct hdd_context *hdd_ctx);
+
+#else
+static inline void hdd_nud_set_gateway_addr(struct hdd_adapter *adapter,
+					    struct qdf_mac_addr gw_mac_addr)
+{
+}
+
+static inline void hdd_nud_cfg_print(struct hdd_context *hdd_ctx)
+{
+}
+
+static inline void hdd_nud_incr_gw_rx_pkt_cnt(struct hdd_adapter *adapter,
+					      struct qdf_mac_addr *mac_addr)
+{
+}
+
+static inline void hdd_nud_init_tracking(struct hdd_adapter *adapter)
+{
+}
+
+static inline void hdd_nud_reset_tracking(struct hdd_adapter *adapter)
+{
+}
+
+static inline void hdd_nud_deinit_tracking(struct hdd_adapter *adapter)
+{
+}
+
+static inline void hdd_nud_ignore_tracking(struct hdd_adapter *adapter,
+					   bool status)
+{
+}
+
+static inline int
+hdd_nud_register_netevent_notifier(struct hdd_context *hdd_ctx)
+{
+	return 0;
+}
+
+static inline void
+hdd_nud_unregister_netevent_notifier(struct hdd_context *hdd_ctx)
+{
+}
+#endif /* WLAN_NUD_TRACKING */
+#endif /* end  of _WLAN_NUD_TRACKING_H_ */
diff --git a/core/hdd/src/wlan_hdd_subnet_detect.c b/core/hdd/src/wlan_hdd_subnet_detect.c
index 29f149b..21fac9b 100644
--- a/core/hdd/src/wlan_hdd_subnet_detect.c
+++ b/core/hdd/src/wlan_hdd_subnet_detect.c
@@ -157,6 +157,8 @@
 	hdd_info("ipv4 addr: %pI4", req.ipv4_addr);
 	hdd_info("ipv6 addr: %pI6c", req.ipv6_addr);
 
+	hdd_nud_set_gateway_addr(adapter, req.gw_mac_addr);
+
 	status = sme_gateway_param_update(hdd_ctx->hHal, &req);
 	if (!QDF_IS_STATUS_SUCCESS(status)) {
 		hdd_err("sme_gateway_param_update failed(err=%d)", status);
diff --git a/core/hdd/src/wlan_hdd_tx_rx.c b/core/hdd/src/wlan_hdd_tx_rx.c
index af7829a..e11ad6f 100644
--- a/core/hdd/src/wlan_hdd_tx_rx.c
+++ b/core/hdd/src/wlan_hdd_tx_rx.c
@@ -65,6 +65,8 @@
 #include "wlan_hdd_cfg80211.h"
 #include <wlan_hdd_tsf.h>
 
+#include "wlan_hdd_nud_tracking.h"
+
 #ifdef QCA_LL_TX_FLOW_CONTROL_V2
 /*
  * Mapping Linux AC interpretation to SME AC.
@@ -350,6 +352,12 @@
 }
 #endif /* QCA_LL_LEGACY_TX_FLOW_CONTROL */
 
+uint32_t hdd_txrx_get_tx_ack_count(struct hdd_adapter *adapter)
+{
+	return cdp_get_tx_ack_stats(cds_get_context(QDF_MODULE_ID_SOC),
+				    adapter->session_id);
+}
+
 /**
  * qdf_event_eapol_log() - send event to wlan diag
  * @skb: skb ptr
@@ -1622,6 +1630,9 @@
 		++adapter->stats.rx_packets;
 		adapter->stats.rx_bytes += skb->len;
 
+		/* Incr GW Rx count for NUD tracking based on GW mac addr */
+		hdd_nud_incr_gw_rx_pkt_cnt(adapter, mac_addr);
+
 		/* Check & drop replayed mcast packets (for IPV6) */
 		if (hdd_ctx->config->multicast_replay_filter &&
 				hdd_is_mcast_replay(skb)) {