Code

madwifi plugin: Plugin for detailed information from the MadWifi driver.
[collectd.git] / src / madwifi.c
diff --git a/src/madwifi.c b/src/madwifi.c
new file mode 100644 (file)
index 0000000..478341e
--- /dev/null
@@ -0,0 +1,851 @@
+/**
+ * collectd - src/madwifi.c
+ * Copyright (C) 2009  Ondrej 'SanTiago' Zajicek
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Ondrej 'SanTiago' Zajicek <santiago@crfreenet.org>
+ *
+ *   based on some code from interfaces.c (collectd) and Madwifi driver
+ **/
+
+
+/**
+ * There are several data streams provided by Madwifi plugin, some are 
+ * connected to network interface, some are connected to each node
+ * associated to that interface. Nodes represents other sides in
+ * wireless communication, for example on network interface in AP mode,
+ * there is one node for each associated station. Node data streams
+ * contain MAC address of the node as the last part  of the type_instance
+ * field.
+ *
+ * Inteface data streams:
+ *     ath_nodes       The number of associated nodes
+ *     ath_stat        Device statistic counters
+ *
+ * Node data streams:
+ *     node_octets     RX and TX data count (octets/bytes)
+ *     node_rssi       Received RSSI of the node
+ *     node_tx_rate    Reported TX rate to that node
+ *     node_stat       Node statistic counters
+ *
+ * Both statistic counters have type instances for each counter returned
+ * by Madwifi. See madwifi.h for content of ieee80211_nodestats, 
+ * ieee80211_stats and ath_stats structures. Type instances use the same
+ * name as fields in these structures (like ns_rx_dup). Some fields are
+ * not reported, because they are not counters (like ns_tx_deauth_code
+ * or ast_tx_rssi). Fields ns_rx_bytes and ns_tx_bytes are reported as
+ * node_octets data stream instead of type instance of node_stat.
+ * Statistics are not logged when they are zero.
+ * 
+ * There are two sets of these counters - the first 'WatchList' is a
+ * set of counters that are individually logged. The second 'MiscList'
+ * is a set of counters that are summed together and the sum is logged.
+ * By default, the most important statistics are in the WatchList and 
+ * many error statistics are in MiscList. There are also many statistics
+ * that are not in any of these sets, so they are not monitored by default.
+ * It is possible to alter these lists using configuration options:
+ *
+ *     WatchAdd X      Adds X to WachList
+ *     WatchRemove X   Removes X from WachList
+ *     WatchSet All    Adds all statistics to WatchList
+ *     WatchSet None   Removes all statistics from WachList
+ *
+ * There are also Misc* variants fo these options, they modifies MiscList
+ * instead of WatchList.
+ *
+ * Example:
+ *
+ *     WatchSet None
+ *     WatchAdd node_octets
+ *     WatchAdd node_rssi
+ *     WatchAdd is_rx_acl
+ *     WatchAdd is_scan_active
+ *
+ * That causes that just the four mentioned data streams are logged.
+ *
+ *
+ * By default, madwifi plugin enumerates network interfaces using /sys
+ * filesystem. Configuration option DisableSysfs can change this to use
+ * /proc filesystem (which is useful for example when running on Linux
+ * 2.4). But without /sys filesystem, Madwifi plugin cannot check whether
+ * given interface is madwifi interface and there are private ioctls used,
+ * which may do something completely different on non-madwifi devices.
+ * Therefore, option DisableSysfs should be used together with option
+ * Interface, to limit found interfaces to madwifi interfaces only.
+ **/
+
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_ignorelist.h"
+
+#include <dirent.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#if !KERNEL_LINUX
+# error "No applicable input method."
+#endif
+
+#include <linux/wireless.h>
+#include "madwifi.h"
+
+
+
+struct stat_spec {
+       uint16_t flags;
+       uint16_t offset;
+       const char *name;
+};
+
+
+#define OFFSETOF(s, i) ((size_t)&((s *)0)->i)
+
+#define FLAG(i)  (((uint32_t) 1) << ((i) % 32))
+
+#define SPC_STAT 0
+#define NOD_STAT 1
+#define IFA_STAT 2
+#define ATH_STAT 3
+#define SRC_MASK 3
+
+/* By default, the item is disabled */
+#define D 0
+
+/* By default, the item is logged */
+#define LOG 4
+
+/* By default, the item is summed with other such items and logged together */
+#define SU 8
+
+#define SS_STAT(flags, name) { flags | SPC_STAT, 0, #name }
+#define NS_STAT(flags, name) { flags | NOD_STAT, OFFSETOF(struct ieee80211_nodestats, name), #name }
+#define IS_STAT(flags, name) { flags | IFA_STAT, OFFSETOF(struct ieee80211_stats, name), #name }
+#define AS_STAT(flags, name) { flags | ATH_STAT, OFFSETOF(struct ath_stats, name), #name }
+
+
+/*
+ * (Module-)Global variables
+ */
+
+/* Indices of special stats in specs array */
+#define STAT_NODE_OCTETS       0
+#define STAT_NODE_RSSI         1
+#define STAT_NODE_TX_RATE      2
+#define STAT_ATH_NODES         3
+#define STAT_NS_RX_BEACONS     4
+#define STAT_AST_ANT_RX                5
+#define STAT_AST_ANT_TX                6
+
+static struct stat_spec specs[] = {
+
+/* Special statistics */
+SS_STAT(LOG, node_octets),             /* rx and tx data count (bytes) */
+SS_STAT(LOG, node_rssi),               /* received RSSI of the node */
+SS_STAT(LOG, node_tx_rate),            /* used tx rate to the node */
+SS_STAT(LOG, ath_nodes),               /* the number of associated nodes */
+SS_STAT(D,   ns_rx_beacons),           /* rx beacon frames */
+SS_STAT(LOG, ast_ant_rx),              /* rx frames with antenna */
+SS_STAT(LOG, ast_ant_tx),              /* tx frames with antenna */
+
+/* Node statistics */
+NS_STAT(LOG, ns_rx_data),              /* rx data frames */
+NS_STAT(LOG, ns_rx_mgmt),              /* rx management frames */
+NS_STAT(LOG, ns_rx_ctrl),              /* rx control frames */
+NS_STAT(D,   ns_rx_ucast),             /* rx unicast frames */
+NS_STAT(D,   ns_rx_mcast),             /* rx multi/broadcast frames */
+NS_STAT(D,   ns_rx_proberesp),         /* rx probe response frames */
+NS_STAT(LOG, ns_rx_dup),               /* rx discard because it's a dup */
+NS_STAT(SU,  ns_rx_noprivacy),         /* rx w/ wep but privacy off */
+NS_STAT(SU,  ns_rx_wepfail),           /* rx wep processing failed */
+NS_STAT(SU,  ns_rx_demicfail),         /* rx demic failed */
+NS_STAT(SU,  ns_rx_decap),             /* rx decapsulation failed */
+NS_STAT(SU,  ns_rx_defrag),            /* rx defragmentation failed */
+NS_STAT(D,   ns_rx_disassoc),          /* rx disassociation */
+NS_STAT(D,   ns_rx_deauth),            /* rx deauthentication */
+NS_STAT(SU,  ns_rx_decryptcrc),                /* rx decrypt failed on crc */
+NS_STAT(SU,  ns_rx_unauth),            /* rx on unauthorized port */
+NS_STAT(SU,  ns_rx_unencrypted),       /* rx unecrypted w/ privacy */
+NS_STAT(LOG, ns_tx_data),              /* tx data frames */
+NS_STAT(LOG, ns_tx_mgmt),              /* tx management frames */
+NS_STAT(D,   ns_tx_ucast),             /* tx unicast frames */
+NS_STAT(D,   ns_tx_mcast),             /* tx multi/broadcast frames */
+NS_STAT(D,   ns_tx_probereq),          /* tx probe request frames */
+NS_STAT(D,   ns_tx_uapsd),             /* tx on uapsd queue */
+NS_STAT(SU,  ns_tx_novlantag),         /* tx discard due to no tag */
+NS_STAT(SU,  ns_tx_vlanmismatch),      /* tx discard due to of bad tag */
+NS_STAT(D,   ns_tx_eosplost),          /* uapsd EOSP retried out */
+NS_STAT(D,   ns_ps_discard),           /* ps discard due to of age */
+NS_STAT(D,   ns_uapsd_triggers),       /* uapsd triggers */
+NS_STAT(LOG, ns_tx_assoc),             /* [re]associations */
+NS_STAT(LOG, ns_tx_auth),              /* [re]authentications */
+NS_STAT(D,   ns_tx_deauth),            /* deauthentications */
+NS_STAT(D,   ns_tx_disassoc),          /* disassociations */
+NS_STAT(D,   ns_psq_drops),            /* power save queue drops */
+
+/* Iface statistics */
+IS_STAT(SU,  is_rx_badversion),                /* rx frame with bad version */
+IS_STAT(SU,  is_rx_tooshort),          /* rx frame too short */
+IS_STAT(LOG, is_rx_wrongbss),          /* rx from wrong bssid */
+IS_STAT(LOG, is_rx_dup),               /* rx discard due to it's a dup */
+IS_STAT(SU,  is_rx_wrongdir),          /* rx w/ wrong direction */
+IS_STAT(D,   is_rx_mcastecho),         /* rx discard due to of mcast echo */
+IS_STAT(SU,  is_rx_notassoc),          /* rx discard due to sta !assoc */
+IS_STAT(SU,  is_rx_noprivacy),         /* rx w/ wep but privacy off */
+IS_STAT(SU,  is_rx_unencrypted),       /* rx w/o wep and privacy on */
+IS_STAT(SU,  is_rx_wepfail),           /* rx wep processing failed */
+IS_STAT(SU,  is_rx_decap),             /* rx decapsulation failed */
+IS_STAT(D,   is_rx_mgtdiscard),                /* rx discard mgt frames */
+IS_STAT(D,   is_rx_ctl),               /* rx discard ctrl frames */
+IS_STAT(D,   is_rx_beacon),            /* rx beacon frames */
+IS_STAT(D,   is_rx_rstoobig),          /* rx rate set truncated */
+IS_STAT(SU,  is_rx_elem_missing),      /* rx required element missing*/
+IS_STAT(SU,  is_rx_elem_toobig),       /* rx element too big */
+IS_STAT(SU,  is_rx_elem_toosmall),     /* rx element too small */
+IS_STAT(LOG, is_rx_elem_unknown),      /* rx element unknown */
+IS_STAT(SU,  is_rx_badchan),           /* rx frame w/ invalid chan */
+IS_STAT(SU,  is_rx_chanmismatch),      /* rx frame chan mismatch */
+IS_STAT(SU,  is_rx_nodealloc),         /* rx frame dropped */
+IS_STAT(LOG, is_rx_ssidmismatch),      /* rx frame ssid mismatch  */
+IS_STAT(SU,  is_rx_auth_unsupported),  /* rx w/ unsupported auth alg */
+IS_STAT(SU,  is_rx_auth_fail),         /* rx sta auth failure */
+IS_STAT(SU,  is_rx_auth_countermeasures),/* rx auth discard due to CM */
+IS_STAT(SU,  is_rx_assoc_bss),         /* rx assoc from wrong bssid */
+IS_STAT(SU,  is_rx_assoc_notauth),     /* rx assoc w/o auth */
+IS_STAT(SU,  is_rx_assoc_capmismatch), /* rx assoc w/ cap mismatch */
+IS_STAT(SU,  is_rx_assoc_norate),      /* rx assoc w/ no rate match */
+IS_STAT(SU,  is_rx_assoc_badwpaie),    /* rx assoc w/ bad WPA IE */
+IS_STAT(LOG, is_rx_deauth),            /* rx deauthentication */
+IS_STAT(LOG, is_rx_disassoc),          /* rx disassociation */
+IS_STAT(SU,  is_rx_badsubtype),                /* rx frame w/ unknown subtype*/
+IS_STAT(SU,  is_rx_nobuf),             /* rx failed for lack of buf */
+IS_STAT(SU,  is_rx_decryptcrc),                /* rx decrypt failed on crc */
+IS_STAT(D,   is_rx_ahdemo_mgt),                /* rx discard ahdemo mgt frame*/
+IS_STAT(SU,  is_rx_bad_auth),          /* rx bad auth request */
+IS_STAT(SU,  is_rx_unauth),            /* rx on unauthorized port */
+IS_STAT(SU,  is_rx_badkeyid),          /* rx w/ incorrect keyid */
+IS_STAT(D,   is_rx_ccmpreplay),                /* rx seq# violation (CCMP), */
+IS_STAT(D,   is_rx_ccmpformat),                /* rx format bad (CCMP), */
+IS_STAT(D,   is_rx_ccmpmic),           /* rx MIC check failed (CCMP), */
+IS_STAT(D,   is_rx_tkipreplay),                /* rx seq# violation (TKIP), */
+IS_STAT(D,   is_rx_tkipformat),                /* rx format bad (TKIP), */
+IS_STAT(D,   is_rx_tkipmic),           /* rx MIC check failed (TKIP), */
+IS_STAT(D,   is_rx_tkipicv),           /* rx ICV check failed (TKIP), */
+IS_STAT(D,   is_rx_badcipher),         /* rx failed due to of key type */
+IS_STAT(D,   is_rx_nocipherctx),       /* rx failed due to key !setup */
+IS_STAT(D,   is_rx_acl),               /* rx discard due to of acl policy */
+IS_STAT(D,   is_rx_ffcnt),             /* rx fast frames */
+IS_STAT(SU,  is_rx_badathtnl),         /* driver key alloc failed */
+IS_STAT(SU,  is_tx_nobuf),             /* tx failed for lack of buf */
+IS_STAT(SU,  is_tx_nonode),            /* tx failed for no node */
+IS_STAT(SU,  is_tx_unknownmgt),                /* tx of unknown mgt frame */
+IS_STAT(SU,  is_tx_badcipher),         /* tx failed due to of key type */
+IS_STAT(SU,  is_tx_nodefkey),          /* tx failed due to no defkey */
+IS_STAT(SU,  is_tx_noheadroom),                /* tx failed due to no space */
+IS_STAT(D,   is_tx_ffokcnt),           /* tx fast frames sent success */
+IS_STAT(D,   is_tx_fferrcnt),          /* tx fast frames sent success */
+IS_STAT(D,   is_scan_active),          /* active scans started */
+IS_STAT(D,   is_scan_passive),         /* passive scans started */
+IS_STAT(D,   is_node_timeout),         /* nodes timed out inactivity */
+IS_STAT(D,   is_crypto_nomem),         /* no memory for crypto ctx */
+IS_STAT(D,   is_crypto_tkip),          /* tkip crypto done in s/w */
+IS_STAT(D,   is_crypto_tkipenmic),     /* tkip en-MIC done in s/w */
+IS_STAT(D,   is_crypto_tkipdemic),     /* tkip de-MIC done in s/w */
+IS_STAT(D,   is_crypto_tkipcm),                /* tkip counter measures */
+IS_STAT(D,   is_crypto_ccmp),          /* ccmp crypto done in s/w */
+IS_STAT(D,   is_crypto_wep),           /* wep crypto done in s/w */
+IS_STAT(D,   is_crypto_setkey_cipher), /* cipher rejected key */
+IS_STAT(D,   is_crypto_setkey_nokey),  /* no key index for setkey */
+IS_STAT(D,   is_crypto_delkey),                /* driver key delete failed */
+IS_STAT(D,   is_crypto_badcipher),     /* unknown cipher */
+IS_STAT(D,   is_crypto_nocipher),      /* cipher not available */
+IS_STAT(D,   is_crypto_attachfail),    /* cipher attach failed */
+IS_STAT(D,   is_crypto_swfallback),    /* cipher fallback to s/w */
+IS_STAT(D,   is_crypto_keyfail),       /* driver key alloc failed */
+IS_STAT(D,   is_crypto_enmicfail),     /* en-MIC failed */
+IS_STAT(SU,  is_ibss_capmismatch),     /* merge failed-cap mismatch */
+IS_STAT(SU,  is_ibss_norate),          /* merge failed-rate mismatch */
+IS_STAT(D,   is_ps_unassoc),           /* ps-poll for unassoc. sta */
+IS_STAT(D,   is_ps_badaid),            /* ps-poll w/ incorrect aid */
+IS_STAT(D,   is_ps_qempty),            /* ps-poll w/ nothing to send */
+
+/* Atheros statistics */
+AS_STAT(D,   ast_watchdog),            /* device reset by watchdog */
+AS_STAT(D,   ast_hardware),            /* fatal hardware error interrupts */
+AS_STAT(D,   ast_bmiss),               /* beacon miss interrupts */
+AS_STAT(D,   ast_rxorn),               /* rx overrun interrupts */
+AS_STAT(D,   ast_rxeol),               /* rx eol interrupts */
+AS_STAT(D,   ast_txurn),               /* tx underrun interrupts */
+AS_STAT(D,   ast_mib),                 /* mib interrupts */
+AS_STAT(D,   ast_tx_packets),          /* packet sent on the interface */
+AS_STAT(D,   ast_tx_mgmt),             /* management frames transmitted */
+AS_STAT(LOG, ast_tx_discard),          /* frames discarded prior to assoc */
+AS_STAT(SU,  ast_tx_invalid),          /* frames discarded due to is device gone */
+AS_STAT(SU,  ast_tx_qstop),            /* tx queue stopped because it's full */
+AS_STAT(SU,  ast_tx_encap),            /* tx encapsulation failed */
+AS_STAT(SU,  ast_tx_nonode),           /* tx failed due to of no node */
+AS_STAT(SU,  ast_tx_nobuf),            /* tx failed due to of no tx buffer (data), */
+AS_STAT(SU,  ast_tx_nobufmgt),         /* tx failed due to of no tx buffer (mgmt),*/
+AS_STAT(LOG, ast_tx_xretries),         /* tx failed due to of too many retries */
+AS_STAT(SU,  ast_tx_fifoerr),          /* tx failed due to of FIFO underrun */
+AS_STAT(SU,  ast_tx_filtered),         /* tx failed due to xmit filtered */
+AS_STAT(LOG, ast_tx_shortretry),       /* tx on-chip retries (short), */
+AS_STAT(LOG, ast_tx_longretry),                /* tx on-chip retries (long), */
+AS_STAT(SU,  ast_tx_badrate),          /* tx failed due to of bogus xmit rate */
+AS_STAT(D,   ast_tx_noack),            /* tx frames with no ack marked */
+AS_STAT(D,   ast_tx_rts),              /* tx frames with rts enabled */
+AS_STAT(D,   ast_tx_cts),              /* tx frames with cts enabled */
+AS_STAT(D,   ast_tx_shortpre),         /* tx frames with short preamble */
+AS_STAT(LOG, ast_tx_altrate),          /* tx frames with alternate rate */
+AS_STAT(D,   ast_tx_protect),          /* tx frames with protection */
+AS_STAT(SU,  ast_rx_orn),              /* rx failed due to of desc overrun */
+AS_STAT(LOG, ast_rx_crcerr),           /* rx failed due to of bad CRC */
+AS_STAT(SU,  ast_rx_fifoerr),          /* rx failed due to of FIFO overrun */
+AS_STAT(SU,  ast_rx_badcrypt),         /* rx failed due to of decryption */
+AS_STAT(SU,  ast_rx_badmic),           /* rx failed due to of MIC failure */
+AS_STAT(LOG, ast_rx_phyerr),           /* rx PHY error summary count */
+AS_STAT(SU,  ast_rx_tooshort),         /* rx discarded due to frame too short */
+AS_STAT(SU,  ast_rx_toobig),           /* rx discarded due to frame too large */
+AS_STAT(SU,  ast_rx_nobuf),            /* rx setup failed due to of no skbuff */
+AS_STAT(D,   ast_rx_packets),          /* packet recv on the interface */
+AS_STAT(D,   ast_rx_mgt),              /* management frames received */
+AS_STAT(D,   ast_rx_ctl),              /* control frames received */
+AS_STAT(D,   ast_be_xmit),             /* beacons transmitted */
+AS_STAT(SU,  ast_be_nobuf),            /* no skbuff available for beacon */
+AS_STAT(D,   ast_per_cal),             /* periodic calibration calls */
+AS_STAT(D,   ast_per_calfail),         /* periodic calibration failed */
+AS_STAT(D,   ast_per_rfgain),          /* periodic calibration rfgain reset */
+AS_STAT(D,   ast_rate_calls),          /* rate control checks */
+AS_STAT(D,   ast_rate_raise),          /* rate control raised xmit rate */
+AS_STAT(D,   ast_rate_drop),           /* rate control dropped xmit rate */
+AS_STAT(D,   ast_ant_defswitch),       /* rx/default antenna switches */
+AS_STAT(D,   ast_ant_txswitch)         /* tx antenna switches */
+};
+
+/* Bounds between SS, NS, IS and AS stats in stats array */
+static int bounds[4];
+
+#define WL_LEN 6
+/* Bitmasks for logged and error items */
+static uint32_t watch_items[WL_LEN];
+static uint32_t misc_items[WL_LEN];
+
+
+static const char *config_keys[] =
+{
+       "Interface",
+       "IgnoreSelected",
+       "DisableSysfs",
+       "WatchAdd",
+       "WatchRemove",
+       "WatchSet",
+       "MiscAdd",
+       "MiscRemove",
+       "MiscSet",
+       NULL
+};
+static int config_keys_num = 9;
+
+static ignorelist_t *ignorelist;
+
+static int use_sysfs = 1;
+static int init_state = 0;
+
+static inline int item_watched(int i)
+{
+       return watch_items[i / 32] & FLAG (i);
+}
+
+static inline int item_summed(int i)
+{
+       return misc_items[i / 32] & FLAG (i);
+}
+
+static inline void watchlist_add (uint32_t *wl, int item)
+{
+       wl[item / 32] |= FLAG (item);
+}
+
+static inline void watchlist_remove (uint32_t *wl, int item)
+{
+       wl[item / 32] &= ~FLAG (item);
+}
+
+static inline void watchlist_set (uint32_t *wl, uint32_t val)
+{
+       int i;
+       for (i = 0; i < WL_LEN; i++)
+               wl[i] = val;
+}
+
+/* This is horribly inefficient, but it is called only during configuration */
+static int watchitem_find (const char *name)
+{
+       int max = sizeof (specs) / sizeof (struct stat_spec);
+       int i;
+
+       for (i = 0; i < max; i++)
+               if (strcasecmp (name, specs[i].name) == 0)
+                       return i;
+
+       return -1;
+}
+
+
+/* Collectd hooks */
+
+/* We need init function called before madwifi_config */
+
+static int madwifi_real_init (void)
+{
+       int max = sizeof (specs) / sizeof (struct stat_spec);
+       int i;
+
+       for (i = 0; i < 4; i++)
+               bounds[i] = 0;
+
+       watchlist_set(watch_items, 0);
+       watchlist_set(misc_items, 0);
+
+       for (i = 0; i < max; i++)
+       {
+               bounds[specs[i].flags & SRC_MASK] = i;
+
+               if (specs[i].flags & LOG)
+                       watch_items[i / 32] |= FLAG (i);
+
+               if (specs[i].flags & SU)
+                       misc_items[i / 32] |= FLAG (i);
+       }
+
+       for (i = 0; i < 4; i++)
+               bounds[i]++;
+
+       return (0);
+}
+
+static int bool_arg (const char *value)
+{
+       return ((strcasecmp (value, "True") == 0)
+               || (strcasecmp (value, "Yes") == 0)
+               || (strcasecmp (value, "On") == 0));
+}
+
+static int madwifi_config (const char *key, const char *value)
+{
+       if (init_state != 1)
+               madwifi_real_init();
+       init_state = 1;
+
+       if (ignorelist == NULL)
+               ignorelist = ignorelist_create (/* invert = */ 1);
+
+       if (strcasecmp (key, "Interface") == 0)
+               ignorelist_add (ignorelist, value);
+
+       else if (strcasecmp (key, "IgnoreSelected") == 0)
+               ignorelist_set_invert (ignorelist, ! bool_arg(value));
+
+       else if (strcasecmp (key, "DisableSysfs") == 0)
+               use_sysfs = ! bool_arg(value);
+
+       else if (strcasecmp (key, "WatchSet") == 0)
+       {
+               if (strcasecmp (value, "All") == 0)
+                       watchlist_set (watch_items, 0xFFFFFFFF);
+               else if (strcasecmp (value, "None") == 0)
+                       watchlist_set (watch_items, 0);
+               else return -1;
+       }
+
+       else if (strcasecmp (key, "WatchAdd") == 0)
+       {
+               int id = watchitem_find (value);
+
+               if (id < 0)
+                       return (-1);
+               else
+                       watchlist_add (watch_items, id);
+       }
+
+       else if (strcasecmp (key, "WatchRemove") == 0)
+       {
+               int id = watchitem_find (value);
+
+               if (id < 0)
+                       return (-1);
+               else
+                       watchlist_remove (watch_items, id);
+       }
+
+       else if (strcasecmp (key, "MiscSet") == 0)
+       {
+               if (strcasecmp (value, "All") == 0)
+                       watchlist_set (misc_items, 0xFFFFFFFF);
+               else if (strcasecmp (value, "None") == 0)
+                       watchlist_set (misc_items, 0);
+               else return -1;
+       }
+
+       else if (strcasecmp (key, "MiscAdd") == 0)
+       {
+               int id = watchitem_find (value);
+
+               if (id < 0)
+                       return (-1);
+               else
+                       watchlist_add (misc_items, id);
+       }
+
+       else if (strcasecmp (key, "MiscRemove") == 0)
+       {
+               int id = watchitem_find (value);
+
+               if (id < 0)
+                       return (-1);
+               else
+                       watchlist_remove (misc_items, id);
+       }
+
+       else
+               return (-1);
+
+       return (0);
+}
+
+
+static void submit (const char *dev, const char *type, const char *ti1,
+                       const char *ti2, value_t *val, int len)
+{
+       value_list_t vl = VALUE_LIST_INIT;
+
+       vl.values = val;
+       vl.values_len = len;
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "madwifi", sizeof (vl.plugin));
+       sstrncpy (vl.plugin_instance, dev, sizeof (vl.plugin_instance));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+
+       if (ti1 && !ti2)
+               sstrncpy (vl.type_instance, ti1, sizeof (vl.type_instance));
+
+       if (ti1 && ti2)
+               ssnprintf (vl.type_instance, sizeof (vl.type_instance), "%s-%s", ti1, ti2);
+
+       plugin_dispatch_values (&vl);
+}
+
+static void submit_counter (const char *dev, const char *type, const char *ti1,
+                               const char *ti2, counter_t val)
+{
+       value_t item;
+       item.counter = val;
+       submit (dev, type, ti1, ti2, &item, 1);
+}
+
+static void submit_counter2 (const char *dev, const char *type, const char *ti1,
+                               const char *ti2, counter_t val1, counter_t val2)
+{
+       value_t items[2];
+       items[0].counter = val1;
+       items[1].counter = val2;
+       submit (dev, type, ti1, ti2, items, 2);
+}
+
+static void submit_gauge (const char *dev, const char *type, const char *ti1,
+                               const char *ti2, gauge_t val)
+{
+       value_t item;
+       item.gauge = val;
+       submit (dev, type, ti1, ti2, &item, 1);
+}
+
+static void submit_antx (const char *dev, const char *name, u_int32_t *vals)
+{
+       char no[2] = {0, 0};
+       int i;
+
+       for (i = 0; i < 8; i++)
+               if (vals[i])
+               {
+                       no[0] = '0' + i;
+                       submit_counter (dev, "ath_stat", name, no, vals[i]);
+               }
+}
+
+static inline void
+macaddr_to_str (char *buf, size_t bufsize, const uint8_t mac[IEEE80211_ADDR_LEN])
+{
+       snprintf (buf, bufsize, "%02x:%02x:%02x:%02x:%02x:%02x",
+               mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+}
+
+static void
+process_stat_struct (int which, const void *ptr, const char *dev, const char *mac,
+                        const char *type_name, const char *misc_name)
+{
+       uint32_t misc = 0;
+       int i;
+
+       for (i = bounds[which - 1]; i < bounds[which]; i++)
+       {
+               uint32_t val = *(uint32_t *)(((char *) ptr) + specs[i].offset) ;
+
+               if (item_watched (i) && (val != 0))
+                       submit_counter (dev, type_name, specs[i].name, mac, val);
+
+               if (item_summed (i))
+                       misc += val;
+       }
+       
+       if (misc != 0)
+               submit_counter (dev, type_name, misc_name, mac, misc);
+
+}
+
+static void
+process_athstats (int sk, const char *dev)
+{
+       struct ifreq ifr;
+       struct ath_stats stats;
+
+       strncpy (ifr.ifr_name, dev, sizeof (ifr.ifr_name));
+       ifr.ifr_data = (void *) &stats;
+       if (ioctl (sk, SIOCGATHSTATS, &ifr) < 0)
+               return;
+
+       /* These stats are handled as a special case, because they are
+          eight values each */
+
+       if (item_watched (STAT_AST_ANT_RX))
+               submit_antx (dev, "ast_ant_rx", stats.ast_ant_rx);
+
+       if (item_watched (STAT_AST_ANT_TX))
+               submit_antx (dev, "ast_ant_tx", stats.ast_ant_tx);
+
+       /* All other ath statistics */
+       process_stat_struct (ATH_STAT, &stats, dev, NULL, "ath_stat", "ast_misc");
+}
+
+static void
+process_80211stats (int sk, const char *dev)
+{
+       struct ifreq ifr;
+       struct ieee80211_stats stats;
+       strncpy (ifr.ifr_name, dev, sizeof (ifr.ifr_name));
+       ifr.ifr_data = (void *) &stats;
+       if (ioctl(sk, SIOCG80211STATS, &ifr) < 0)
+               return;
+
+       process_stat_struct (IFA_STAT, &stats, dev, NULL, "ath_stat", "is_misc");
+}
+
+
+static void
+process_station (int sk, const char *dev, struct ieee80211req_sta_info *si)
+{
+       struct iwreq iwr;
+       static char mac[DATA_MAX_NAME_LEN];
+       struct ieee80211req_sta_stats stats;
+       const struct ieee80211_nodestats *ns = &stats.is_stats;
+
+       macaddr_to_str (mac, sizeof (mac), si->isi_macaddr);
+
+       if (item_watched (STAT_NODE_TX_RATE))
+               submit_gauge (dev, "node_tx_rate", mac, NULL,
+                       (si->isi_rates[si->isi_txrate] & IEEE80211_RATE_VAL) / 2);
+
+       if (item_watched (STAT_NODE_RSSI))
+               submit_gauge (dev, "node_rssi", mac, NULL, si->isi_rssi);
+
+       memset (&iwr, 0, sizeof (iwr));
+       strncpy(iwr.ifr_name, dev, sizeof (iwr.ifr_name));
+       iwr.u.data.pointer = (void *) &stats;
+       iwr.u.data.length = sizeof (stats);
+       memcpy(stats.is_u.macaddr, si->isi_macaddr, IEEE80211_ADDR_LEN);
+       if (ioctl(sk, IEEE80211_IOCTL_STA_STATS, &iwr) < 0)
+               return;
+
+       /* These two stats are handled as a special case as they are
+          a pair of 64bit values */
+       if (item_watched (STAT_NODE_OCTETS))
+               submit_counter2 (dev, "node_octets", mac, NULL,
+                       ns->ns_rx_bytes, ns->ns_tx_bytes);
+
+       /* This stat is handled as a special case, because it is stored
+          as uin64_t, but we will ignore upper half */
+       if (item_watched (STAT_NS_RX_BEACONS))
+               submit_counter (dev, "node_stat", "ns_rx_beacons", mac,
+                       (ns->ns_rx_beacons & 0xFFFFFFFF));
+
+       /* All other node statistics */
+       process_stat_struct (NOD_STAT, ns, dev, mac, "node_stat", "ns_misc");
+}
+
+static void
+process_stations (int sk, const char *dev)
+{
+       uint8_t buf[24*1024];
+       struct iwreq iwr;
+       uint8_t *cp;
+       int len, nodes;
+
+       memset (&iwr, 0, sizeof (iwr));
+       strncpy (iwr.ifr_name, dev, sizeof (iwr.ifr_name));
+       iwr.u.data.pointer = (void *) buf;
+       iwr.u.data.length = sizeof (buf);
+       if (ioctl (sk, IEEE80211_IOCTL_STA_INFO, &iwr) < 0)
+               return;
+
+       len = iwr.u.data.length;
+
+       cp = buf;
+       nodes = 0;
+       while (len >= sizeof (struct ieee80211req_sta_info))
+       {
+               struct ieee80211req_sta_info *si = (void *) cp;
+               process_station(sk, dev, si);
+               cp += si->isi_len;
+               len -= si->isi_len;
+               nodes++;
+       }
+
+       if (item_watched (STAT_ATH_NODES))
+               submit_gauge (dev, "ath_nodes", NULL, NULL, nodes);
+}
+
+static void
+process_device (int sk, const char *dev)
+{
+       process_athstats (sk, dev);
+       process_80211stats (sk, dev);
+       process_stations (sk, dev);
+}
+
+static int
+check_devname (const char *dev)
+{
+       char buf[256];
+       char buf2[256];
+       int i;
+
+       if (dev[0] == '.')
+               return 0;
+       
+       ssnprintf (buf, sizeof (buf), "/sys/class/net/%s/device/driver", dev);
+       buf[sizeof (buf) - 1] = 0;
+
+       i = readlink (buf, buf2, sizeof (buf2) - 1);
+       if (i < 0)
+               return 0;
+       buf2[i] = 0;
+
+       return (strstr (buf2, "/drivers/ath_") != NULL);
+}
+
+static int
+sysfs_iterate(int sk)
+{
+       struct dirent *de;
+
+       DIR *nets = opendir ("/sys/class/net/");
+       if (nets == NULL)
+       {
+               WARNING ("madwifi plugin: opening /sys/class/net failed");
+               return (-1);
+       }
+
+       while ((de = readdir (nets)))
+               if (check_devname (de->d_name) &&
+                   (ignorelist_match (ignorelist, de->d_name) == 0))
+                       process_device (sk, de->d_name);
+
+       closedir(nets);
+
+       return 0;
+}
+
+static int
+procfs_iterate(int sk)
+{
+       char buffer[1024];
+       char *device, *dummy;
+       FILE *fh;
+       
+       if ((fh = fopen ("/proc/net/dev", "r")) == NULL)
+       {
+               WARNING ("madwifi plugin: opening /proc/net/dev failed");
+               return (-1);
+       }
+
+       while (fgets (buffer, 1024, fh) != NULL)
+       {
+               if (!(dummy = strchr(buffer, ':')))
+                       continue;
+               dummy[0] = '\0';
+
+               device = buffer;
+               while (device[0] == ' ')
+                       device++;
+
+               if (device[0] == '\0')
+                       continue;
+
+               if (ignorelist_match (ignorelist, device) == 0)
+                       process_device (sk, device);
+       }
+
+       fclose(fh);
+       return 0;
+}
+
+static int madwifi_read (void)
+{
+       if (init_state == 0)
+               madwifi_real_init();
+       init_state = 2;
+
+       int sk = socket(AF_INET, SOCK_DGRAM, 0);
+       if (sk < 0)
+               return (-1);
+
+       int rv;
+
+/* procfs iteration is not safe because it does not check whether given
+   interface is madwifi interface and there are private ioctls used, which
+   may do something completely different on non-madwifi devices.   
+   Therefore, it is not used unless explicitly enabled (and should be used
+   together with ignorelist). */
+
+       if (use_sysfs)
+               rv = sysfs_iterate(sk);
+       else
+               rv = procfs_iterate(sk);
+
+       close(sk);
+
+       return rv;
+}
+
+void module_register (void)
+{
+       plugin_register_config ("madwifi", madwifi_config,
+                       config_keys, config_keys_num);
+
+       plugin_register_read ("madwifi", madwifi_read);
+}