Browse Source

Add support for the wifi monitor mode in the SDK (#2204)

Philip Gladstone 6 years ago
parent
commit
ceadd30bca

+ 98 - 0
app/include/sys/network_80211.h

@@ -0,0 +1,98 @@
+#define FRAME_TYPE_MANAGEMENT 0
+#define FRAME_TYPE_CONTROL 1
+#define FRAME_TYPE_DATA 2
+#define FRAME_SUBTYPE_ASSOC_REQUEST 0x00
+#define FRAME_SUBTYPE_ASSOC_RESPONSE 0x01
+#define FRAME_SUBTYPE_REASSOC_REQUEST 0x02
+#define FRAME_SUBTYPE_REASSOC_RESPONSE 0x03
+#define FRAME_SUBTYPE_PROBE_REQUEST 0x04
+#define FRAME_SUBTYPE_PROBE_RESPONSE 0x05
+#define FRAME_SUBTYPE_BEACON 0x08
+#define FRAME_SUBTYPE_ATIM 0x09
+#define FRAME_SUBTYPE_DISASSOCIATION 0x0a
+#define FRAME_SUBTYPE_AUTHENTICATION 0x0b
+#define FRAME_SUBTYPE_DEAUTHENTICATION 0x0c
+#define FRAME_SUBTYPE_DATA 0x14
+typedef struct framectrl_80211
+{
+    //buf[0]
+    u8 Protocol:2;
+    u8 Type:2;
+    u8 Subtype:4;
+    //buf[1]
+    u8 ToDS:1;
+    u8 FromDS:1;
+    u8 MoreFlag:1;
+    u8 Retry:1;
+    u8 PwrMgmt:1;
+    u8 MoreData:1;
+    u8 Protectedframe:1;
+    u8 Order:1;
+} framectrl_80211,*lpframectrl_80211;
+
+typedef struct management_80211
+{
+	struct framectrl_80211 framectrl;
+	uint16 duration;
+	uint8 rdaddr[6];
+	uint8 tsaddr[6];
+	uint8 bssid[6];
+	uint16 number;
+} management_request_t;
+
+typedef struct
+{
+	management_request_t hdr;
+        uint8 timestamp[8];
+	uint16 beacon_interval;
+	uint16 capability_info;
+} wifi_beacon_t;
+
+typedef struct tagged_parameter
+{
+	/* SSID parameter */
+	uint8 tag_number;
+	uint8 tag_length;
+} tagged_parameter, *ptagged_parameter;
+
+struct RxControl {
+    signed rssi:8;//表示该包的信号强度
+    unsigned rate:4;
+    unsigned is_group:1;
+    unsigned:1;
+    unsigned sig_mode:2;//表示该包是否是11n的包,0表示非11n,非0表示11n
+    unsigned legacy_length:12;//如果不是11n的包,它表示包的长度
+    unsigned damatch0:1;
+    unsigned damatch1:1;
+    unsigned bssidmatch0:1;
+    unsigned bssidmatch1:1;
+    unsigned MCS:7;//如果是11n的包,它表示包的调制编码序列,有效值:0-76
+    unsigned CWB:1;//如果是11n的包,它表示是否为HT40的包
+    unsigned HT_length:16;//如果是11n的包,它表示包的长度
+    unsigned Smoothing:1;
+    unsigned Not_Sounding:1;
+    unsigned:1;
+    unsigned Aggregation:1;
+    unsigned STBC:2;
+    unsigned FEC_CODING:1;//如果是11n的包,它表示是否为LDPC的包
+    unsigned SGI:1;
+    unsigned rxend_state:8;
+    unsigned ampdu_cnt:8;
+    unsigned channel:4;//表示该包所在的信道
+    unsigned:12;
+};
+
+struct sniffer_buf2{
+	struct RxControl rx_ctrl;
+	u8 buf[112];//包含ieee80211包头
+	u16 cnt;//包的个数
+	u16 len[1];//包的长度
+};
+
+struct sniffer_buf{
+	struct RxControl rx_ctrl;
+	u8 buf[48];//包含ieee80211包头
+	u16 cnt;//包的个数
+	u16 len[1];//包的长度
+};
+

+ 1 - 0
app/include/user_modules.h

@@ -77,6 +77,7 @@
 //#define LUA_USE_MODULES_UCG
 //#define LUA_USE_MODULES_WEBSOCKET
 #define LUA_USE_MODULES_WIFI
+//#define LUA_USE_MODULES_WIFI_MONITOR
 //#define LUA_USE_MODULES_WPS
 //#define LUA_USE_MODULES_WS2801
 //#define LUA_USE_MODULES_WS2812

+ 6 - 0
app/modules/wifi.c

@@ -1823,6 +1823,9 @@ static const LUA_REG_TYPE wifi_map[] =  {
   { LSTRKEY( "ap" ),             LROVAL( wifi_ap_map ) },
 #if defined(WIFI_SDK_EVENT_MONITOR_ENABLE)
   { LSTRKEY( "eventmon" ),       LROVAL( wifi_event_monitor_map ) }, //declared in wifi_eventmon.c
+#endif
+#if defined(LUA_USE_MODULES_WIFI_MONITOR)
+  { LSTRKEY( "monitor" ),        LROVAL( wifi_monitor_map ) }, //declared in wifi_monitor.c
 #endif
   { LSTRKEY( "NULLMODE" ),       LNUMVAL( NULL_MODE ) },
   { LSTRKEY( "STATION" ),        LNUMVAL( STATION_MODE ) },
@@ -1898,6 +1901,9 @@ int luaopen_wifi( lua_State *L )
   }
 #if defined(WIFI_SDK_EVENT_MONITOR_ENABLE)
   wifi_eventmon_init();
+#endif
+#if defined(LUA_USE_MODULES_WIFI_MONITOR)
+  wifi_monitor_init(L);
 #endif
  return 0;
 }

+ 5 - 0
app/modules/wifi_common.h

@@ -67,4 +67,9 @@ enum wifi_suspension_state
   int wifi_event_monitor_register(lua_State* L);
 #endif
 
+#ifdef LUA_USE_MODULES_WIFI_MONITOR
+  extern const LUA_REG_TYPE wifi_monitor_map[];
+  int wifi_monitor_init(lua_State *L);
+#endif
+
 #endif /* APP_MODULES_WIFI_COMMON_H_ */

+ 14 - 0
app/modules/wifi_eventmon.c

@@ -31,6 +31,14 @@ static evt_queue_t *wifi_event_queue_head; //pointer to beginning of queue
 static evt_queue_t *wifi_event_queue_tail; //pointer to end of queue
 static int wifi_event_cb_ref[EVENT_MAX+1] = { [0 ... EVENT_MAX] = LUA_NOREF}; //holds references to registered Lua callbacks
 
+#ifdef LUA_USE_MODULES_WIFI_MONITOR
+static int (*hook_fn)(System_Event_t *);
+
+void wifi_event_monitor_register_hook(int (*fn)(System_Event_t*)) {
+  hook_fn = fn;
+}
+#endif
+
 // wifi.eventmon.register()
 int wifi_event_monitor_register(lua_State* L)
 {
@@ -58,6 +66,12 @@ static void wifi_event_monitor_handle_event_cb(System_Event_t *evt)
 {
   EVENT_DBG("\n\twifi_event_monitor_handle_event_cb is called\n");
 
+#ifdef LUA_USE_MODULES_WIFI_MONITOR
+  if (hook_fn && hook_fn(evt)) {
+    return;
+  }
+#endif
+
   if((wifi_event_cb_ref[evt->event] != LUA_NOREF) || ((wifi_event_cb_ref[EVENT_MAX] != LUA_NOREF) &&
       !(evt->event == EVENT_STAMODE_CONNECTED || evt->event == EVENT_STAMODE_DISCONNECTED ||
           evt->event == EVENT_STAMODE_AUTHMODE_CHANGE || evt->event == EVENT_STAMODE_GOT_IP ||

+ 806 - 0
app/modules/wifi_monitor.c

@@ -0,0 +1,806 @@
+// Module for interfacing with WIFI
+
+#include "module.h"
+#include "lauxlib.h"
+#include "lapi.h"
+#include "platform.h"
+
+#include "c_string.h"
+#include "c_stdlib.h"
+#include "ctype.h"
+
+#include "c_types.h"
+#include "user_interface.h"
+#include "wifi_common.h"
+#include "sys/network_80211.h"
+
+static int recv_cb;
+static uint8 mon_offset;
+static uint8 mon_value;
+static uint8 mon_mask;
+static task_handle_t tasknumber;
+
+#define SNIFFER_BUF2_BUF_SIZE       112
+
+#define BITFIELD(byte, start, len)   8 * (byte) + (start), (len)
+#define BYTEFIELD(byte, len)   8 * (byte), 8 * (len)
+
+#define IS_SIGNED 1
+#define IS_STRING 2
+#define IS_HEXSTRING 3
+
+#define IE_TABLE  4
+#define SINGLE_IE  5
+#define IS_HEADER  6
+
+#define ANY_FRAME 16
+
+#define IE(id)  0, (id), SINGLE_IE
+
+static void (*on_disconnected)(void);
+
+typedef struct {
+  const char *key;
+  uint8 frametype;
+} typekey_t;
+
+typedef struct {
+  const char *name;
+  unsigned int start : 12;
+  unsigned int length : 12;
+  unsigned int opts : 3;
+  unsigned int frametype : 5;  // 16 is *any*
+} field_t;
+
+// must be sorted alphabetically
+static const field_t fields[] = {
+  { "aggregation", BITFIELD(7, 3, 1), 0, ANY_FRAME},
+  { "ampdu_cnt", BITFIELD(9, 0, 8), 0, ANY_FRAME},
+  { "association_id", BYTEFIELD(40, 2), 0, FRAME_SUBTYPE_ASSOC_RESPONSE},
+  { "association_id", BYTEFIELD(40, 2), 0, FRAME_SUBTYPE_REASSOC_RESPONSE},
+  { "authentication_algorithm", BYTEFIELD(36, 2), 0, FRAME_SUBTYPE_AUTHENTICATION},
+  { "authentication_transaction", BYTEFIELD(38, 2), 0, FRAME_SUBTYPE_AUTHENTICATION},
+  { "beacon_interval", BYTEFIELD(44, 2), 0, FRAME_SUBTYPE_PROBE_RESPONSE},
+  { "beacon_interval", BYTEFIELD(44, 2), 0, FRAME_SUBTYPE_BEACON},
+  { "bssid", BYTEFIELD(28, 6), IS_STRING, ANY_FRAME},
+  { "bssid_hex", BYTEFIELD(28, 6), IS_HEXSTRING, ANY_FRAME},
+  { "bssidmatch0", BITFIELD(3, 6, 1), 0, ANY_FRAME},
+  { "bssidmatch1", BITFIELD(3, 7, 1), 0, ANY_FRAME},
+  { "capability", BYTEFIELD(36, 2), 0, FRAME_SUBTYPE_ASSOC_REQUEST},
+  { "capability", BYTEFIELD(36, 2), 0, FRAME_SUBTYPE_ASSOC_RESPONSE},
+  { "capability", BYTEFIELD(36, 2), 0, FRAME_SUBTYPE_REASSOC_REQUEST},
+  { "capability", BYTEFIELD(36, 2), 0, FRAME_SUBTYPE_REASSOC_RESPONSE},
+  { "capability", BYTEFIELD(46, 2), 0, FRAME_SUBTYPE_PROBE_RESPONSE},
+  { "capability", BYTEFIELD(46, 2), 0, FRAME_SUBTYPE_BEACON},
+  { "channel", BITFIELD(10, 0, 4), 0, ANY_FRAME},
+  { "current_ap", BYTEFIELD(40, 6), IS_STRING, FRAME_SUBTYPE_REASSOC_REQUEST},
+  { "cwb", BITFIELD(4, 7, 1), 0, ANY_FRAME},
+  { "dmatch0", BITFIELD(3, 4, 1), 0, ANY_FRAME},
+  { "dmatch1", BITFIELD(3, 5, 1), 0, ANY_FRAME},
+  { "dstmac", BYTEFIELD(22, 6), IS_STRING, ANY_FRAME},
+  { "dstmac_hex", BYTEFIELD(22, 6), IS_HEXSTRING, ANY_FRAME},
+  { "duration", BYTEFIELD(14, 2), 0, ANY_FRAME},
+  { "fec_coding", BITFIELD(7, 6, 1), 0, ANY_FRAME},
+  { "frame", BYTEFIELD(12, 112), IS_STRING, ANY_FRAME},
+  { "frame_hex", BYTEFIELD(12, 112), IS_HEXSTRING, ANY_FRAME},
+  { "fromds", BITFIELD(13, 1, 1), 0, ANY_FRAME},
+  { "header", BYTEFIELD(12, 0), IS_HEADER, ANY_FRAME},
+  { "ht_length", BITFIELD(5, 0, 16), 0, ANY_FRAME},
+  { "ie_20_40_bss_coexistence", IE(72), ANY_FRAME},
+  { "ie_20_40_bss_intolerant_channel_report", IE(73), ANY_FRAME},
+  { "ie_advertisement_protocol", IE(108), ANY_FRAME},
+  { "ie_aid", IE(197), ANY_FRAME},
+  { "ie_antenna", IE(64), ANY_FRAME},
+  { "ie_ap_channel_report", IE(51), ANY_FRAME},
+  { "ie_authenticated_mesh_peering_exchange", IE(139), ANY_FRAME},
+  { "ie_beacon_timing", IE(120), ANY_FRAME},
+  { "ie_bss_ac_access_delay", IE(68), ANY_FRAME},
+  { "ie_bss_available_admission_capacity", IE(67), ANY_FRAME},
+  { "ie_bss_average_access_delay", IE(63), ANY_FRAME},
+  { "ie_bss_load", IE(11), ANY_FRAME},
+  { "ie_bss_max_idle_period", IE(90), ANY_FRAME},
+  { "ie_cf_parameter_set", IE(4), ANY_FRAME},
+  { "ie_challenge_text", IE(16), ANY_FRAME},
+  { "ie_channel_switch_announcement", IE(37), ANY_FRAME},
+  { "ie_channel_switch_timing", IE(104), ANY_FRAME},
+  { "ie_channel_switch_wrapper", IE(196), ANY_FRAME},
+  { "ie_channel_usage", IE(97), ANY_FRAME},
+  { "ie_collocated_interference_report", IE(96), ANY_FRAME},
+  { "ie_congestion_notification", IE(116), ANY_FRAME},
+  { "ie_country", IE(7), ANY_FRAME},
+  { "ie_destination_uri", IE(141), ANY_FRAME},
+  { "ie_diagnostic_report", IE(81), ANY_FRAME},
+  { "ie_diagnostic_request", IE(80), ANY_FRAME},
+  { "ie_dms_request", IE(99), ANY_FRAME},
+  { "ie_dms_response", IE(100), ANY_FRAME},
+  { "ie_dse_registered_location", IE(58), ANY_FRAME},
+  { "ie_dsss_parameter_set", IE(3), ANY_FRAME},
+  { "ie_edca_parameter_set", IE(12), ANY_FRAME},
+  { "ie_emergency_alart_identifier", IE(112), ANY_FRAME},
+  { "ie_erp_information", IE(42), ANY_FRAME},
+  { "ie_event_report", IE(79), ANY_FRAME},
+  { "ie_event_request", IE(78), ANY_FRAME},
+  { "ie_expedited_bandwidth_request", IE(109), ANY_FRAME},
+  { "ie_extended_bss_load", IE(193), ANY_FRAME},
+  { "ie_extended_capabilities", IE(127), ANY_FRAME},
+  { "ie_extended_channel_switch_announcement", IE(60), ANY_FRAME},
+  { "ie_extended_supported_rates", IE(50), ANY_FRAME},
+  { "ie_fast_bss_transition", IE(55), ANY_FRAME},
+  { "ie_fh_parameter_set", IE(2), ANY_FRAME},
+  { "ie_fms_descriptor", IE(86), ANY_FRAME},
+  { "ie_fms_request", IE(87), ANY_FRAME},
+  { "ie_fms_response", IE(88), ANY_FRAME},
+  { "ie_gann", IE(125), ANY_FRAME},
+  { "ie_he_capabilities", IE(255), ANY_FRAME},
+  { "ie_hopping_pattern_parameters", IE(8), ANY_FRAME},
+  { "ie_hopping_pattern_table", IE(9), ANY_FRAME},
+  { "ie_ht_capabilities", IE(45), ANY_FRAME},
+  { "ie_ht_operation", IE(61), ANY_FRAME},
+  { "ie_ibss_dfs", IE(41), ANY_FRAME},
+  { "ie_ibss_parameter_set", IE(6), ANY_FRAME},
+  { "ie_interworking", IE(107), ANY_FRAME},
+  { "ie_link_identifier", IE(101), ANY_FRAME},
+  { "ie_location_parameters", IE(82), ANY_FRAME},
+  { "ie_management_mic", IE(76), ANY_FRAME},
+  { "ie_mccaop", IE(124), ANY_FRAME},
+  { "ie_mccaop_advertisement", IE(123), ANY_FRAME},
+  { "ie_mccaop_advertisement_overview", IE(174), ANY_FRAME},
+  { "ie_mccaop_setup_reply", IE(122), ANY_FRAME},
+  { "ie_mccaop_setup_request", IE(121), ANY_FRAME},
+  { "ie_measurement_pilot_transmission", IE(66), ANY_FRAME},
+  { "ie_measurement_report", IE(39), ANY_FRAME},
+  { "ie_measurement_request", IE(38), ANY_FRAME},
+  { "ie_mesh_awake_window", IE(119), ANY_FRAME},
+  { "ie_mesh_channel_switch_parameters", IE(118), ANY_FRAME},
+  { "ie_mesh_configuration", IE(113), ANY_FRAME},
+  { "ie_mesh_id", IE(114), ANY_FRAME},
+  { "ie_mesh_link_metric_report", IE(115), ANY_FRAME},
+  { "ie_mesh_peering_management", IE(117), ANY_FRAME},
+  { "ie_mic", IE(140), ANY_FRAME},
+  { "ie_mobility_domain", IE(54), ANY_FRAME},
+  { "ie_multiple_bssid", IE(71), ANY_FRAME},
+  { "ie_multiple_bssid_index", IE(85), ANY_FRAME},
+  { "ie_neighbor_report", IE(52), ANY_FRAME},
+  { "ie_nontransmitted_bssid_capability", IE(83), ANY_FRAME},
+  { "ie_operating_mode_notification", IE(199), ANY_FRAME},
+  { "ie_overlapping_bss_scan_parameters", IE(74), ANY_FRAME},
+  { "ie_perr", IE(132), ANY_FRAME},
+  { "ie_power_capability", IE(33), ANY_FRAME},
+  { "ie_power_constraint", IE(32), ANY_FRAME},
+  { "ie_prep", IE(131), ANY_FRAME},
+  { "ie_preq", IE(130), ANY_FRAME},
+  { "ie_proxy_update", IE(137), ANY_FRAME},
+  { "ie_proxy_update_confirmation", IE(138), ANY_FRAME},
+  { "ie_pti_control", IE(105), ANY_FRAME},
+  { "ie_qos_capability", IE(46), ANY_FRAME},
+  { "ie_qos_map_set", IE(110), ANY_FRAME},
+  { "ie_qos_traffic_capability", IE(89), ANY_FRAME},
+  { "ie_quiet", IE(40), ANY_FRAME},
+  { "ie_quiet_channel", IE(198), ANY_FRAME},
+  { "ie_rann", IE(126), ANY_FRAME},
+  { "ie_rcpi", IE(53), ANY_FRAME},
+  { "ie_request", IE(10), ANY_FRAME},
+  { "ie_ric_data", IE(57), ANY_FRAME},
+  { "ie_ric_descriptor", IE(75), ANY_FRAME},
+  { "ie_rm_enabled_capacities", IE(70), ANY_FRAME},
+  { "ie_roaming_consortium", IE(111), ANY_FRAME},
+  { "ie_rsn", IE(48), ANY_FRAME},
+  { "ie_rsni", IE(65), ANY_FRAME},
+  { "ie_schedule", IE(15), ANY_FRAME},
+  { "ie_secondary_channel_offset", IE(62), ANY_FRAME},
+  { "ie_ssid", IE(0), ANY_FRAME},
+  { "ie_ssid_list", IE(84), ANY_FRAME},
+  { "ie_supported_channels", IE(36), ANY_FRAME},
+  { "ie_supported_operating_classes", IE(59), ANY_FRAME},
+  { "ie_supported_rates", IE(1), ANY_FRAME},
+  { "ie_table", BITFIELD(0, 0, 0), IE_TABLE, ANY_FRAME},
+  { "ie_tclas", IE(14), ANY_FRAME},
+  { "ie_tclas_processing", IE(44), ANY_FRAME},
+  { "ie_tfs_request", IE(91), ANY_FRAME},
+  { "ie_tfs_response", IE(92), ANY_FRAME},
+  { "ie_tim", IE(5), ANY_FRAME},
+  { "ie_tim_broadcast_request", IE(94), ANY_FRAME},
+  { "ie_tim_broadcast_response", IE(95), ANY_FRAME},
+  { "ie_time_advertisement", IE(69), ANY_FRAME},
+  { "ie_time_zone", IE(98), ANY_FRAME},
+  { "ie_timeout_interval", IE(56), ANY_FRAME},
+  { "ie_tpc_report", IE(35), ANY_FRAME},
+  { "ie_tpc_request", IE(34), ANY_FRAME},
+  { "ie_tpu_buffer_status", IE(106), ANY_FRAME},
+  { "ie_ts_delay", IE(43), ANY_FRAME},
+  { "ie_tspec", IE(13), ANY_FRAME},
+  { "ie_uapsd_coexistence", IE(142), ANY_FRAME},
+  { "ie_vendor_specific", IE(221), ANY_FRAME},
+  { "ie_vht_capabilities", IE(191), ANY_FRAME},
+  { "ie_vht_operation", IE(192), ANY_FRAME},
+  { "ie_vht_transmit_power_envelope", IE(195), ANY_FRAME},
+  { "ie_wakeup_schedule", IE(102), ANY_FRAME},
+  { "ie_wide_bandwidth_channel_switch", IE(194), ANY_FRAME},
+  { "ie_wnm_sleep_mode", IE(93), ANY_FRAME},
+  { "is_group", BITFIELD(1, 4, 1), 0, ANY_FRAME},
+  { "legacy_length", BITFIELD(2, 0, 12), 0, ANY_FRAME},
+  { "listen_interval", BYTEFIELD(38, 2), 0, FRAME_SUBTYPE_ASSOC_REQUEST},
+  { "listen_interval", BYTEFIELD(38, 2), 0, FRAME_SUBTYPE_REASSOC_REQUEST},
+  { "mcs", BITFIELD(4, 0, 7), 0, ANY_FRAME},
+  { "moredata", BITFIELD(13, 5, 1), 0, ANY_FRAME},
+  { "moreflag", BITFIELD(13, 2, 1), 0, ANY_FRAME},
+  { "not_counding", BITFIELD(7, 1, 1), 0, ANY_FRAME},
+  { "number", BYTEFIELD(34, 2), 0, ANY_FRAME},
+  { "order", BITFIELD(13, 7, 1), 0, ANY_FRAME},
+  { "protectedframe", BITFIELD(13, 6, 1), 0, ANY_FRAME},
+  { "protocol", BITFIELD(12, 0, 2), 0, ANY_FRAME},
+  { "pwrmgmt", BITFIELD(13, 4, 1), 0, ANY_FRAME},
+  { "radio", BYTEFIELD(0, 12), IS_STRING, ANY_FRAME},
+  { "rate", BITFIELD(1, 0, 4), 0, ANY_FRAME},
+  { "reason", BYTEFIELD(36, 2), 0, FRAME_SUBTYPE_DEAUTHENTICATION},
+  { "retry", BITFIELD(13, 3, 1), 0, ANY_FRAME},
+  { "rssi", BYTEFIELD(0, 1), IS_SIGNED, ANY_FRAME},
+  { "rxend_state", BITFIELD(8, 0, 8), 0, ANY_FRAME},
+  { "sgi", BITFIELD(7, 7, 1), 0, ANY_FRAME},
+  { "sig_mode", BITFIELD(1, 6, 2), 0, ANY_FRAME},
+  { "smoothing", BITFIELD(7, 0, 1), 0, ANY_FRAME},
+  { "srcmac", BYTEFIELD(16, 6), IS_STRING, ANY_FRAME},
+  { "srcmac_hex", BYTEFIELD(16, 6), IS_HEXSTRING, ANY_FRAME},
+  { "status", BYTEFIELD(38, 2), 0, FRAME_SUBTYPE_ASSOC_RESPONSE},
+  { "status", BYTEFIELD(38, 2), 0, FRAME_SUBTYPE_REASSOC_RESPONSE},
+  { "status", BYTEFIELD(40, 2), 0, FRAME_SUBTYPE_AUTHENTICATION},
+  { "stbc", BITFIELD(7, 4, 2), 0, ANY_FRAME},
+  { "subtype", BITFIELD(12, 4, 4), 0, ANY_FRAME},
+  { "timestamp", BYTEFIELD(36, 8), IS_STRING, FRAME_SUBTYPE_PROBE_RESPONSE},
+  { "timestamp", BYTEFIELD(36, 8), IS_STRING, FRAME_SUBTYPE_BEACON},
+  { "tods", BITFIELD(13, 0, 1), 0, ANY_FRAME},
+  { "type", BITFIELD(12, 2, 2), 0, ANY_FRAME}
+};
+
+static int8 variable_start[16] = {
+   4,   // assoc req
+   6,   // assoc response
+  10,   // reassoc req
+   6,   // reassoc resp
+   0,   // probe req
+  12,   // probe resp
+  -1,
+  -1,
+  12,   // beacon
+  -1,   // ATIM
+   2,   // Disassociation
+   6,   // authentication
+   2,   // Deauthentication
+   2,   // action
+  -1,
+  -1
+};
+
+typedef struct {
+  uint16 len;
+  uint8 buf[];
+} packet_t;
+
+static const LUA_REG_TYPE packet_function_map[];
+
+static void wifi_rx_cb(uint8 *buf, uint16 len) {
+  if (len != sizeof(struct sniffer_buf2)) {
+    return;
+  }
+
+  struct sniffer_buf2 *snb = (struct sniffer_buf2 *) buf;
+  management_request_t *mgt = (management_request_t *) snb->buf;
+
+  if (mon_offset > len) {
+    return;
+  }
+
+  if ((buf[mon_offset] & mon_mask) != mon_value) {
+    return;
+  }
+
+  packet_t *packet = (packet_t *) c_malloc(len + sizeof(packet_t));
+  if (packet) {
+    packet->len = len;
+    memcpy(packet->buf, buf, len);
+    if (!task_post_medium(tasknumber, (ETSParam) packet)) {
+      c_free(packet);
+    }
+  }
+}
+
+static void monitor_task(os_param_t param, uint8_t prio) 
+{
+  packet_t *input = (packet_t *) param;
+  (void) prio;
+
+  lua_State *L = lua_getstate();
+
+  if (recv_cb != LUA_NOREF) {
+    lua_rawgeti(L, LUA_REGISTRYINDEX, recv_cb);
+
+    packet_t *packet = (packet_t *) lua_newuserdata(L, input->len + sizeof(packet_t));
+    packet->len = input->len;
+    memcpy(packet->buf, input->buf, input->len);
+    luaL_getmetatable(L, "wifi.packet");
+    lua_setmetatable(L, -2);
+
+    c_free(input);
+
+    lua_call(L, 1, 0);
+  } else {
+    c_free(input);
+  }
+}
+
+static ptrdiff_t posrelat (ptrdiff_t pos, size_t len) {
+  /* relative string position: negative means back from end */
+  if (pos < 0) pos += (ptrdiff_t)len + 1;
+  return (pos >= 0) ? pos : 0;
+}
+
+static int packet_sub(lua_State *L, int buf_offset, int buf_length) {
+  packet_t *packet = luaL_checkudata(L, 1, "wifi.packet");
+
+  ptrdiff_t start = posrelat(luaL_checkinteger(L, 2), buf_length);
+  ptrdiff_t end = posrelat(luaL_optinteger(L, 3, -1), buf_length);
+
+  if (start < 1) start = 1;
+  if (end > buf_length) end = buf_length;
+  if (start <= end) {
+    lua_pushlstring(L, packet->buf+start-1 + buf_offset, end-start+1);
+  } else {
+    lua_pushliteral(L, "");
+  }
+
+  return 1;
+}
+
+static int packet_frame_sub(lua_State *L) {
+  return packet_sub(L, sizeof(struct RxControl), SNIFFER_BUF2_BUF_SIZE);
+}
+
+static int packet_radio_sub(lua_State *L) {
+  return packet_sub(L, 0, sizeof(struct RxControl));
+}
+
+static void push_hex_string(lua_State *L, const uint8 *buf, int len, char *sep) {
+  luaL_Buffer b;
+  luaL_buffinit(L, &b);
+
+  int i;
+  for (i = 0; i < len; i++) {
+    if (i && sep && *sep) {
+      luaL_addstring(&b, sep);
+    }
+    char hex[3];
+
+    uint8 c = buf[i];
+
+    hex[0] = "0123456789abcdef"[c >> 4];
+    hex[1] = "0123456789abcdef"[c & 0xf];
+    hex[2] = 0;
+    luaL_addstring(&b, hex);
+  }
+  
+  luaL_pushresult(&b);
+}
+
+static void push_hex_string_colon(lua_State *L, const uint8 *buf, int len) {
+  push_hex_string(L, buf, len, ":");
+}
+
+static int comparator(const void *typekey, const void *obj) {
+  field_t *f = (field_t *) obj;
+  const char *name = f->name;
+  const typekey_t *tk = (const typekey_t *) typekey;
+  const char *key = tk->key;
+
+  if (!((uint32)key & 3) && !((uint32)name & 3)) {
+    // Since all strings are 3 characters or more, can do accelerated first comparison
+    uint32 key32 = htonl(*(uint32 *) key);
+    uint32 name32 = htonl(*(uint32 *) name);
+
+    if (key32 < name32) {
+      return -1;
+    }
+    if (key32 > name32) {
+      return 1;
+    }
+  }
+
+  int rc = strcmp((const char *) key, name);
+  if (rc) {
+    return rc;
+  }
+
+  if (f->frametype == ANY_FRAME) {
+    return 0;
+  }
+
+  return tk->frametype - f->frametype;
+}
+
+static bool push_field_value_string(lua_State *L, const uint8 *pkt,
+    const uint8 *packet_end, const char *field) {
+  const struct RxControl *rxc = (struct RxControl *) pkt; 
+  const management_request_t *mgt = (management_request_t *) (rxc + 1);
+
+  typekey_t tk;
+  tk.key = field;
+  tk.frametype = mgt->framectrl.Subtype;
+
+  field_t *f = bsearch(&tk, fields, sizeof(fields) / sizeof(fields[0]), sizeof(fields[0]), comparator);
+
+  if (f) {
+
+    if (f->opts == SINGLE_IE) {
+      int varstart = variable_start[mgt->framectrl.Subtype];
+      if (varstart >= 0) {
+        const uint8 *var = (uint8 *) (mgt + 1) + varstart;
+
+        while (var + 2 <= packet_end && var + 2 + var[1] <= packet_end) {
+          if (*var == f->length) {
+            lua_pushlstring(L, var + 2, var[1]);
+            return true;
+          }
+          var += 2 + var[1];
+        }
+      }
+
+      lua_pushnil(L);
+
+      return true;
+    }
+
+    if (f->opts == IE_TABLE) {
+      lua_newtable(L);
+      int varstart = variable_start[mgt->framectrl.Subtype];
+      if (varstart >= 0) {
+        const uint8 *var = (uint8 *) (mgt + 1) + varstart;
+
+        while (var + 2 <= packet_end && var + 2 + var[1] <= packet_end) {
+          lua_pushlstring(L, var + 2, var[1]);
+          lua_rawseti(L, -2, *var);
+          var += 2 + var[1];
+        }
+      }
+
+      return true;
+    }
+
+    if (f->opts == IS_STRING) {
+      int use = f->length >> 3;
+      const uint8 *start = pkt + (f->start >> 3);
+      if (start + use > packet_end) {
+        use = packet_end - start;
+      }
+      lua_pushlstring(L, start, use);
+      return true;
+    }
+
+    if (f->opts == IS_HEXSTRING) {
+      int use = f->length >> 3;
+      const uint8 *start = pkt + (f->start >> 3);
+      if (start + use > packet_end) {
+        use = packet_end - start;
+      }
+      push_hex_string_colon(L, start, use);
+      return true;
+    }
+
+    if (f->opts == IS_HEADER) {
+      int varstart = variable_start[mgt->framectrl.Subtype];
+      if (varstart >= 0) {
+        lua_pushlstring(L, (const uint8 *) (mgt + 1), varstart);
+      } else {
+        lua_pushnil(L);
+      }
+
+      return true;
+    }
+
+    if (f->opts == 0 || f->opts == IS_SIGNED) {
+      // bits start from the bottom of the byte.
+      int value = 0;
+      int bits = 0;
+      int bitoff = f->start & 7;
+      int byteoff = f->start >> 3;
+
+      while (bits < f->length) {
+        uint8 b = pkt[byteoff];
+
+        value |= (b >> bitoff) << bits;
+        bits += (8 - bitoff);
+        bitoff = 0;
+        byteoff++;
+      }
+      // get rid of excess bits
+      value &= (1 << f->length) - 1;
+      if (f->opts & IS_SIGNED) {
+        if (value & (1 << (f->length - 1))) {
+          value |= - (1 << f->length);
+        }
+      }
+
+      lua_pushinteger(L, value);
+      return true;
+    }
+  }
+
+  return false;
+}
+
+static bool push_field_value_int(lua_State *L, management_request_t *mgt, 
+    const uint8 *packet_end, int field) {
+
+  int varstart = variable_start[mgt->framectrl.Subtype];
+  if (varstart >= 0) {
+    uint8 *var = (uint8 *) (mgt + 1) + varstart;
+
+    while (var + 2 <= packet_end && var + 2 + var[1] <= packet_end) {
+      if (*var == field) {
+        lua_pushlstring(L, var + 2, var[1]);
+        return true;
+      }
+      var += var[1] + 2;
+    }
+  }
+
+  return false;
+}
+
+static int packet_map_lookup(lua_State *L) {
+  packet_t *packet = luaL_checkudata(L, 1, "wifi.packet");
+  struct RxControl *rxc = (struct RxControl *) packet->buf; 
+  management_request_t *mgt = (management_request_t *) (rxc + 1);
+  const uint8 *packet_end = packet->buf + packet->len;
+
+  if ((void *) (mgt + 1) > (void *) packet_end) {
+    return 0;
+  }
+  if (mgt->framectrl.Type != FRAME_TYPE_MANAGEMENT) {
+    return 0;
+  }
+
+  if (lua_type(L, 2) == LUA_TNUMBER) {
+    int field = luaL_checkinteger(L, 2);
+    if (push_field_value_int(L, mgt, packet_end, field)) {
+      return 1;
+    }
+  } else {
+    const char *field = luaL_checkstring(L, 2);
+    if (push_field_value_string(L, packet->buf, packet_end, field)) {
+      return 1;
+    }
+
+    // Now search the packet function map
+    const TValue *res = luaR_findentry((void *) packet_function_map, field, 0, NULL);
+    if (res) {
+      luaA_pushobject(L, res);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int packet_byte(lua_State *L, int buf_offset, int buf_length) {
+  packet_t *packet = luaL_checkudata(L, 1, "wifi.packet");
+
+  int offset = luaL_checkinteger(L, 2);
+
+  if (offset < 1 || offset > buf_length) {
+    return 0;
+  }
+
+  lua_pushinteger(L, packet->buf[offset - 1 + buf_offset]);
+
+  return 1;
+}
+
+static int packet_frame_byte(lua_State *L) {
+  return packet_byte(L, sizeof(struct RxControl), SNIFFER_BUF2_BUF_SIZE);
+}
+
+static int packet_radio_byte(lua_State *L) {
+  return packet_byte(L, 0, sizeof(struct RxControl));
+}
+
+static int packet_subhex(lua_State *L, int buf_offset, int buf_length) {
+  packet_t *packet = luaL_checkudata(L, 1, "wifi.packet");
+
+  ptrdiff_t start = posrelat(luaL_checkinteger(L, 2), buf_length);
+  ptrdiff_t end = posrelat(luaL_optinteger(L, 3, -1), buf_length);
+  const char *sep = luaL_optstring(L, 4, "");
+
+  if (start < 1) start = 1;
+  if (end > buf_length) end = buf_length;
+  if (start <= end) {
+    luaL_Buffer b;
+    luaL_buffinit(L, &b);
+
+    int i;
+    for (i = start - 1; i < end; i++) {
+      char hex[3];
+
+      if (i >= start) {
+        luaL_addstring(&b, sep);
+      }
+
+      uint8 c = packet->buf[i + buf_offset];
+
+      hex[0] = "0123456789abcdef"[c >> 4];
+      hex[1] = "0123456789abcdef"[c & 0xf];
+      hex[2] = 0;
+      luaL_addstring(&b, hex);
+    }
+    
+    luaL_pushresult(&b);
+  } else {
+    lua_pushliteral(L, "");
+  }
+
+  return 1;
+}
+
+static int packet_frame_subhex(lua_State *L) {
+  return packet_subhex(L, sizeof(struct RxControl), SNIFFER_BUF2_BUF_SIZE);
+}
+
+static int packet_radio_subhex(lua_State *L) {
+  return packet_subhex(L, 0, sizeof(struct RxControl));
+}
+
+static void start_actually_monitoring() {
+  wifi_set_channel(1);
+  wifi_promiscuous_enable(1);
+}
+
+static int wifi_event_monitor_handle_event_cb_hook(System_Event_t *evt)
+{
+  if (evt->event == EVENT_STAMODE_DISCONNECTED) {
+    if (on_disconnected) {
+      on_disconnected();
+      on_disconnected = NULL;
+      return 1;    // We did handle the event
+    }
+  }
+  return 0;     // We did not handle the event
+}
+
+// This is a bit ugly as we have to use a bit of the event monitor infrastructure
+#ifdef WIFI_SDK_EVENT_MONITOR_ENABLE
+extern void wifi_event_monitor_register_hook(int (*fn)(System_Event_t*));
+
+static void eventmon_setup() {
+  wifi_event_monitor_register_hook(wifi_event_monitor_handle_event_cb_hook);
+}
+#else
+static void wifi_event_monitor_handle_event_cb(System_Event_t *evt)
+{
+  wifi_event_monitor_handle_event_cb_hook(evt);
+}
+
+static void eventmon_setup() {
+  wifi_set_event_handler_cb(wifi_event_monitor_handle_event_cb);
+}
+#endif
+
+static void eventmon_call_on_disconnected(void (*fn)(void)) {
+  on_disconnected = fn;
+}
+
+static int wifi_monitor_start(lua_State *L) {
+  int argno = 1;
+  if (lua_type(L, argno) == LUA_TNUMBER) {
+    int offset = luaL_checkinteger(L, argno);
+    argno++;
+    if (lua_type(L, argno) == LUA_TNUMBER) {
+      int value = luaL_checkinteger(L, argno);
+
+      int mask = 0xff;
+      argno++;
+
+      if (lua_type(L, argno) == LUA_TNUMBER) {
+        mask = luaL_checkinteger(L, argno);
+        argno++;
+      }
+      mon_offset = offset - 1;
+      mon_value = value;
+      mon_mask = mask;
+    } else {
+      return luaL_error(L, "Must supply offset and value");
+    }
+  } else {
+    // Management frames by default
+    mon_offset = 12;
+    mon_value = 0x00;
+    mon_mask = 0x0C;
+  }
+  if (lua_type(L, argno) == LUA_TFUNCTION || lua_type(L, argno) == LUA_TLIGHTFUNCTION)
+  {
+    lua_pushvalue(L, argno);  // copy argument (func) to the top of stack
+    recv_cb = luaL_ref(L, LUA_REGISTRYINDEX);
+    uint8 connect_status = wifi_station_get_connect_status();
+    wifi_station_set_auto_connect(0);
+    wifi_set_opmode_current(1);
+    wifi_promiscuous_enable(0);
+    wifi_station_disconnect();
+    wifi_set_promiscuous_rx_cb(wifi_rx_cb);
+    // Now we have to wait until we get the EVENT_STAMODE_DISCONNECTED event
+    // before we can go further.
+    if (connect_status == STATION_IDLE) {
+      start_actually_monitoring();
+    } else {
+      eventmon_call_on_disconnected(start_actually_monitoring);
+    }
+    return 0;
+  }
+  return luaL_error(L, "Missing callback");
+}
+
+static int wifi_monitor_channel(lua_State *L) {
+  lua_pushinteger(L, wifi_get_channel());
+  if (lua_type(L, 1) == LUA_TNUMBER) {
+    int channel = luaL_checkinteger(L, 1);
+
+    if (channel < 1 || channel > 15) {
+      return luaL_error(L, "Channel number (%d) is out of range", channel);
+    }
+    wifi_set_channel(channel);
+  }
+
+  return 1;
+}
+
+static int wifi_monitor_stop(lua_State *L) {
+  wifi_promiscuous_enable(0);
+  wifi_set_opmode_current(1);
+  luaL_unref(L, LUA_REGISTRYINDEX, recv_cb);
+  recv_cb = LUA_NOREF;
+  return 0;
+}
+
+static const LUA_REG_TYPE packet_function_map[] = {
+  { LSTRKEY( "radio_byte" ),        LFUNCVAL( packet_radio_byte ) },
+  { LSTRKEY( "frame_byte" ),        LFUNCVAL( packet_frame_byte ) },
+  { LSTRKEY( "radio_sub" ),         LFUNCVAL( packet_radio_sub ) },
+  { LSTRKEY( "frame_sub" ),         LFUNCVAL( packet_frame_sub ) },
+  { LSTRKEY( "radio_subhex" ),      LFUNCVAL( packet_radio_subhex ) },
+  { LSTRKEY( "frame_subhex" ),      LFUNCVAL( packet_frame_subhex ) },
+  { LNILKEY, LNILVAL }
+};
+
+static const LUA_REG_TYPE packet_map[] = {
+  { LSTRKEY( "__index" ),     LFUNCVAL( packet_map_lookup ) },
+  { LNILKEY, LNILVAL }
+};
+
+// Module function map
+const LUA_REG_TYPE wifi_monitor_map[] = {
+  { LSTRKEY( "start" ),      LFUNCVAL( wifi_monitor_start ) },
+  { LSTRKEY( "stop" ),       LFUNCVAL( wifi_monitor_stop ) },
+  { LSTRKEY( "channel" ),    LFUNCVAL( wifi_monitor_channel ) },
+  { LNILKEY, LNILVAL }
+};
+
+int wifi_monitor_init(lua_State *L)
+{
+  luaL_rometatable(L, "wifi.packet", (void *)packet_map);
+  tasknumber = task_get_id(monitor_task);
+  eventmon_setup();
+
+#ifdef CHECK_TABLE_IN_ORDER
+    // verify that the table is in order
+    typekey_t tk;
+    tk.key = "";
+    tk.frametype = 0;
+
+    int i;
+    for (i = 0; i < sizeof(fields) / sizeof(fields[0]); i++) {
+      if (comparator(&tk, &fields[i]) >= 0) {
+        dbg_printf("Wrong order: %s,%d should be after %s,%d\n", tk.key, tk.frametype, fields[i].name, fields[i].frametype);
+      }
+      tk.key = fields[i].name;
+      tk.frametype = fields[i].frametype;
+    }
+#endif
+  return 0;
+}
+

+ 2 - 0
docs/en/modules/wifi.md

@@ -42,6 +42,7 @@ The NodeMCU WiFi control is spread across several tables:
 - [`wifi.ap`](#wifiap-module) for wireless access point (WAP or simply AP) functions
 - [`wifi.ap.dhcp`](#wifiapdhcp-module) for DHCP server control
 - [`wifi.eventmon`](#wifieventmon-module) for wifi event monitor
+- [`wifi.monitor`](wifi_monitor.md#wifimonitor-module) for wifi monitor mode
 
 
 ## wifi.getchannel()
@@ -1640,3 +1641,4 @@ Table containing disconnect reasons.
 |wifi.eventmon.reason.AUTH_FAIL         |  202  |
 |wifi.eventmon.reason.ASSOC_FAIL        |  203  |
 |wifi.eventmon.reason.HANDSHAKE_TIMEOUT |  204  |
+

+ 419 - 0
docs/en/modules/wifi_monitor.md

@@ -0,0 +1,419 @@
+# WiFi.monitor Module
+| Since  | Origin / Contributor  | Maintainer  | Source  |
+| :----- | :-------------------- | :---------- | :------ |
+| 2017-12-20 | [Philip Gladstone](https://github.com/pjsg) | [Philip Gladstone](https://github.com/pjsg) | [wifi_monitor.c](../../../app/modules/wifi_monitor.c)|
+
+This is an optional module that is only included if `LUA_USE_MODULES_WIFI_MONITOR` is defined in the `user_modules.h` file. This module
+provides access to the monitor mode features of the ESP8266 chipset. In particular, it provides access to received WiFi management frames.
+
+This module is not for casual use -- it requires an understanding of IEEE802.11 management protocols.
+
+## wifi.monitor.start()
+
+This registers a callback function to be called whenever a management frame is received. Note that this can be at quite a high rate, so some limited
+filtering is provided before the callback is invoked. Only the first 110 bytes or so of the frame are returned -- this is an SDK restriction.
+Any connected ap/station will be disconnected.
+
+#### Syntax
+`wifi.monitor.start([filter parameters,] mgmt_frame_callback)`
+
+#### Parameters
+- filter parameters. This is a byte offset (1 based) into the underlying data structure, a value to match against, and an optional mask to use for matching.
+  The data structure used for filtering is 12 bytes of [radio header](#the-radio-header), and then the actual frame. The first byte of the frame is therefore numbered 13. The filter
+  values of 13, 0x80 will just extract beacon frames.
+- `mgmt_frame_callback` is a function which is invoked with a single argument which is a `wifi.packet` object which has many methods and attributes.
+
+
+#### Returns
+nothing.
+
+#### Example
+```
+wifi.monitor.channel(6)
+wifi.monitor.start(13, 0x80, function(pkt)
+    print ('Beacon: ' .. pkt.bssid_hex .. " '" .. pkt[0] .. "' ch " .. pkt[3]:byte(1))
+end)
+```
+
+## wifi.monitor.stop()
+
+This disables the monitor mode and returns to normal operation. There are no parameters and no return value.
+
+#### Syntax
+`wifi.monitor.stop()`
+
+## wifi.monitor.channel()
+
+This sets the channel number to monitor. Note that in many applications you will want to step through the channel numbers at regular intervals. Beacon
+frames (in particular) are typically sent every 102 milliseconds, so a switch time of (say) 150 milliseconds seems to work well.
+
+#### Syntax
+`wifi.monitor.channel(channel)`
+
+#### Parameters
+- `channel` sets the channel number in the range 1 to 15.
+
+#### Returns
+nothing.
+
+# wifi.packet object
+
+This object provides access to the raw packet data and also many methods to extract data from the packet in a simple way.
+
+## packet:radio_byte()
+
+This is like the `string.byte` method, except that it gives access to the bytes of the [radio header](#the-radio-header).
+
+#### Syntax
+`packet:radio_byte(n)`
+
+#### Parameters
+- `n` the byte number (1 based) to get from the [radio header](#the-radio-header) portion of the packet
+
+#### Returns
+0-255 as the value of the byte
+nothing if the offset is not within the [radio header](#the-radio-header).
+
+## packet:frame_byte()
+
+This is like the `string.byte` method, except that it gives access to the bytes of the received frame.
+
+#### Syntax
+`packet:frame_byte(n)`
+
+#### Parameters
+- `n` the byte number (1 based) to get from the received frame.
+
+#### Returns
+0-255 as the value of the byte
+nothing if the offset is not within the received frame.
+
+
+## packet:radio_sub()
+
+This is like the `string.sub` method, except that it gives access to the bytes of the [radio header](#the-radio-header).
+
+#### Syntax
+`packet:radio_sub(start, end)`
+
+#### Parameters
+Same rules as for `string.sub` except that it operates on the [radio header](#the-radio-header).
+
+#### Returns
+A string according to the `string.sub` rules.
+
+## packet:frame_sub()
+
+This is like the `string.sub` method, except that it gives access to the bytes of the received frame.
+
+#### Syntax
+`packet:frame_sub(start, end)`
+
+#### Parameters
+Same rules as for `string.sub` except that it operates on the received frame.
+
+#### Returns
+A string according to the `string.sub` rules.
+
+
+## packet:radio_subhex()
+
+This is like the `string.sub` method, except that it gives access to the bytes of the [radio header](#the-radio-header). It also
+converts them into hex efficiently.
+
+#### Syntax
+`packet:radio_subhex(start, end [, seperator])`
+
+#### Parameters
+Same rules as for `string.sub` except that it operates on the [radio header](#the-radio-header).
+- `seperator` is an optional sting which is placed between the individual hex pairs returned.
+
+#### Returns
+A string according to the `string.sub` rules, converted into hex with possible inserted spacers.
+
+## packet:frame_sub()
+
+This is like the `string.sub` method, except that it gives access to the bytes of the received frame.
+
+#### Syntax
+`packet:frame_subhex(start, end [, seperator])`
+
+#### Parameters
+Same rules as for `string.sub` except that it operates on the received frame.
+- `seperator` is an optional sting which is placed between the individual hex pairs returned.
+
+#### Returns
+A string according to the `string.sub` rules, converted into hex with possible inserted spacers.
+
+
+## packet:ie_table()
+
+This returns a table of the information elements from the management frame. The table keys values are the
+information element numbers (0 - 255). Note that IE0 is the SSID. This method is mostly only useful if
+you need to determine which information elements were in the management frame.
+
+#### Syntax
+`packet:ie_table()`
+
+#### Parameters
+None.
+
+#### Returns
+A table with all the information elements in it.
+
+#### Example
+```
+print ("SSID", packet:ie_table()[0])
+```
+
+Note that this is possibly the worst way of getting the SSID. 
+
+#### Alternative
+
+The `packet` object itself can be indexed to extract the information elements.
+
+#### Example
+```
+print ("SSID", packet[0])
+```
+
+This is more efficient than the above approach, but requires you to remember that IE0 is the SSID.
+
+## packet.&lt;attribute&gt;
+
+The packet object has many attributes on it. These allow easy access to all the fields, though not an easy way to enumerate them. All integers are unsigned
+except where noted. Information Elements are only returned if they are completely within the captured frame. This can mean that for some frames, some of the
+information elements can be missing.
+
+When a string is returned as the value of a field, it can (and often is) be a binary string with embedded nulls. All information elements are returned as strings
+even if they are only one byte long and look like a number in the specification. This is purely to make the interface consistent. Note that even SSIDs can contain
+embedded nulls. 
+
+|  Attribute name  |  Type  |
+|:--------------------|:-------:|
+| aggregation | Integer |
+| ampdu_cnt | Integer |
+| association_id | Integer |
+| authentication_algorithm | Integer |
+| authentication_transaction | Integer |
+| beacon_interval | Integer |
+| beacon_interval | Integer |
+| bssid | String |
+| bssid_hex | String |
+| bssidmatch0 | Integer |
+| bssidmatch1 | Integer |
+| capability | Integer |
+| channel | Integer |
+| current_ap | String |
+| cwb | Integer |
+| dmatch0 | Integer |
+| dmatch1 | Integer |
+| dstmac | String |
+| dstmac_hex | String |
+| duration | Integer |
+| fec_coding | Integer |
+| frame | String (the entire received frame) |
+| frame_hex | String |
+| fromds | Integer |
+| header | String (the fixed part of the management frame) |
+| ht_length | Integer |
+| ie_20_40_bss_coexistence | String |
+| ie_20_40_bss_intolerant_channel_report | String |
+| ie_advertisement_protocol | String |
+| ie_aid | String |
+| ie_antenna | String |
+| ie_ap_channel_report | String |
+| ie_authenticated_mesh_peering_exchange | String |
+| ie_beacon_timing | String |
+| ie_bss_ac_access_delay | String |
+| ie_bss_available_admission_capacity | String |
+| ie_bss_average_access_delay | String |
+| ie_bss_load | String |
+| ie_bss_max_idle_period | String |
+| ie_cf_parameter_set | String |
+| ie_challenge_text | String |
+| ie_channel_switch_announcement | String |
+| ie_channel_switch_timing | String |
+| ie_channel_switch_wrapper | String |
+| ie_channel_usage | String |
+| ie_collocated_interference_report | String |
+| ie_congestion_notification | String |
+| ie_country | String |
+| ie_destination_uri | String |
+| ie_diagnostic_report | String |
+| ie_diagnostic_request | String |
+| ie_dms_request | String |
+| ie_dms_response | String |
+| ie_dse_registered_location | String |
+| ie_dsss_parameter_set | String |
+| ie_edca_parameter_set | String |
+| ie_emergency_alart_identifier | String |
+| ie_erp_information | String |
+| ie_event_report | String |
+| ie_event_request | String |
+| ie_expedited_bandwidth_request | String |
+| ie_extended_bss_load | String |
+| ie_extended_capabilities | String |
+| ie_extended_channel_switch_announcement | String |
+| ie_extended_supported_rates | String |
+| ie_fast_bss_transition | String |
+| ie_fh_parameter_set | String |
+| ie_fms_descriptor | String |
+| ie_fms_request | String |
+| ie_fms_response | String |
+| ie_gann | String |
+| ie_he_capabilities | String |
+| ie_hopping_pattern_parameters | String |
+| ie_hopping_pattern_table | String |
+| ie_ht_capabilities | String |
+| ie_ht_operation | String |
+| ie_ibss_dfs | String |
+| ie_ibss_parameter_set | String |
+| ie_interworking | String |
+| ie_link_identifier | String |
+| ie_location_parameters | String |
+| ie_management_mic | String |
+| ie_mccaop | String |
+| ie_mccaop_advertisement | String |
+| ie_mccaop_advertisement_overview | String |
+| ie_mccaop_setup_reply | String |
+| ie_mccaop_setup_request | String |
+| ie_measurement_pilot_transmission | String |
+| ie_measurement_report | String |
+| ie_measurement_request | String |
+| ie_mesh_awake_window | String |
+| ie_mesh_channel_switch_parameters | String |
+| ie_mesh_configuration | String |
+| ie_mesh_id | String |
+| ie_mesh_link_metric_report | String |
+| ie_mesh_peering_management | String |
+| ie_mic | String |
+| ie_mobility_domain | String |
+| ie_multiple_bssid | String |
+| ie_multiple_bssid_index | String |
+| ie_neighbor_report | String |
+| ie_nontransmitted_bssid_capability | String |
+| ie_operating_mode_notification | String |
+| ie_overlapping_bss_scan_parameters | String |
+| ie_perr | String |
+| ie_power_capability | String |
+| ie_power_constraint | String |
+| ie_prep | String |
+| ie_preq | String |
+| ie_proxy_update | String |
+| ie_proxy_update_confirmation | String |
+| ie_pti_control | String |
+| ie_qos_capability | String |
+| ie_qos_map_set | String |
+| ie_qos_traffic_capability | String |
+| ie_quiet | String |
+| ie_quiet_channel | String |
+| ie_rann | String |
+| ie_rcpi | String |
+| ie_request | String |
+| ie_ric_data | String |
+| ie_ric_descriptor | String |
+| ie_rm_enabled_capacities | String |
+| ie_roaming_consortium | String |
+| ie_rsn | String |
+| ie_rsni | String |
+| ie_schedule | String |
+| ie_secondary_channel_offset | String |
+| ie_ssid | String |
+| ie_ssid_list | String |
+| ie_supported_channels | String |
+| ie_supported_operating_classes | String |
+| ie_supported_rates | String |
+| ie_tclas | String |
+| ie_tclas_processing | String |
+| ie_tfs_request | String |
+| ie_tfs_response | String |
+| ie_tim | String |
+| ie_tim_broadcast_request | String |
+| ie_tim_broadcast_response | String |
+| ie_time_advertisement | String |
+| ie_time_zone | String |
+| ie_timeout_interval | String |
+| ie_tpc_report | String |
+| ie_tpc_request | String |
+| ie_tpu_buffer_status | String |
+| ie_ts_delay | String |
+| ie_tspec | String |
+| ie_uapsd_coexistence | String |
+| ie_vendor_specific | String |
+| ie_vht_capabilities | String |
+| ie_vht_operation | String |
+| ie_vht_transmit_power_envelope | String |
+| ie_wakeup_schedule | String |
+| ie_wide_bandwidth_channel_switch | String |
+| ie_wnm_sleep_mode | String |
+| is_group | Integer |
+| legacy_length | Integer |
+| listen_interval | Integer |
+| mcs | Integer |
+| moredata | Integer |
+| moreflag | Integer |
+| not_counding | Integer |
+| number | Integer |
+| order | Integer |
+| protectedframe | Integer |
+| protocol | Integer |
+| pwrmgmt | Integer |
+| radio | String (the entire [radio header](#the-radio-header)) |
+| rate | Integer |
+| reason | Integer |
+| retry | Integer |
+| rssi | Signed Integer |
+| rxend_state | Integer |
+| sgi | Integer |
+| sig_mode | Integer |
+| smoothing | Integer |
+| srcmac | String |
+| srcmac_hex | String |
+| status | Integer |
+| stbc | Integer |
+| subtype | Integer |
+| timestamp | String |
+| tods | Integer |
+| type | Integer |
+
+
+If you don't know what some of the attributes are, then you probably need to read the IEEE 802.11 specifications and other supporting material.
+
+#### Example
+```
+print ("SSID", packet.ie_ssid)
+```
+
+## The Radio Header
+
+The Radio Header has been mentioned above as a 12 byte structure. The layout is shown below. The only comments are in Chinese.
+
+```
+struct {
+    signed rssi:8;//表示该包的信号强度
+    unsigned rate:4;
+    unsigned is_group:1;
+    unsigned:1;
+    unsigned sig_mode:2;//表示该包是否是11n 的包,0 表示非11n,非0 表示11n
+    unsigned legacy_length:12;//如果不是11n 的包,它表示包的长度
+    unsigned damatch0:1;
+    unsigned damatch1:1;
+    unsigned bssidmatch0:1;
+    unsigned bssidmatch1:1;
+    unsigned MCS:7;//如果是11n 的包,它表示包的调制编码序列,有效值:0-76
+    unsigned CWB:1;//如果是11n 的包,它表示是否为HT40 的包
+    unsigned HT_length:16;//如果是11n 的包,它表示包的长度
+    unsigned Smoothing:1;
+    unsigned Not_Sounding:1;
+    unsigned:1;
+    unsigned Aggregation:1;
+    unsigned STBC:2;
+    unsigned FEC_CODING:1;//如果是11n 的包,它表示是否为LDPC 的包
+    unsigned SGI:1;
+    unsigned rxend_state:8;
+    unsigned ampdu_cnt:8;
+    unsigned channel:4;//表示该包所在的信道
+    unsigned:12;
+}
+```

+ 1 - 0
mkdocs.yml

@@ -94,6 +94,7 @@ pages:
         - 'ucg': 'en/modules/ucg.md'
         - 'websocket': 'en/modules/websocket.md'
         - 'wifi': 'en/modules/wifi.md'
+        - 'wifi.monitor': 'en/modules/wifi_monitor.md'
         - 'wps': 'en/modules/wps.md'
         - 'ws2801': 'en/modules/ws2801.md'
         - 'ws2812': 'en/modules/ws2812.md'