/** * 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 * * 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 `Source' 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, the /proc filesystem should always be used together with option * `Interface', to limit found interfaces to madwifi interfaces only. **/ #include "collectd.h" #include "plugin.h" #include "utils/common/common.h" #include "utils/ignorelist/ignorelist.h" #include #include #if !KERNEL_LINUX #error "No applicable input method." #endif #include "madwifi.h" #include 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", "Source", "WatchAdd", "WatchRemove", "WatchSet", "MiscAdd", "MiscRemove", "MiscSet"}; static int config_keys_num = STATIC_ARRAY_SIZE(config_keys); static ignorelist_t *ignorelist; static int use_sysfs = 1; static int init_state; static inline int item_watched(int i) { assert(i >= 0); assert((size_t)i < (STATIC_ARRAY_SIZE(watch_items) * 32)); return watch_items[i / 32] & FLAG(i); } static inline int item_summed(int i) { assert(i >= 0); assert((size_t)i < (STATIC_ARRAY_SIZE(misc_items) * 32)); return misc_items[i / 32] & FLAG(i); } static inline void watchlist_add(uint32_t *wl, int item) { assert(item >= 0); assert(item < WL_LEN * 32); wl[item / 32] |= FLAG(item); } static inline void watchlist_remove(uint32_t *wl, int item) { assert(item >= 0); assert(item < WL_LEN * 32); wl[item / 32] &= ~FLAG(item); } static inline void watchlist_set(uint32_t *wl, uint32_t val) { for (int 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 = STATIC_ARRAY_SIZE(specs); for (int 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) { size_t max = STATIC_ARRAY_SIZE(specs); for (size_t i = 0; i < STATIC_ARRAY_SIZE(bounds); i++) bounds[i] = 0; watchlist_set(watch_items, 0); watchlist_set(misc_items, 0); for (size_t 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 (size_t i = 0; i < STATIC_ARRAY_SIZE(bounds); i++) bounds[i]++; return 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, IS_TRUE(value) ? 0 : 1); else if (strcasecmp(key, "Source") == 0) { if (strcasecmp(value, "ProcFS") == 0) use_sysfs = 0; else if (strcasecmp(value, "SysFS") == 0) use_sysfs = 1; else { ERROR("madwifi plugin: The argument of the `Source' " "option must either be `SysFS' or " "`ProcFS'."); return -1; } } 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, size_t len) { value_list_t vl = VALUE_LIST_INIT; vl.values = val; vl.values_len = len; 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 != NULL) && (ti2 != NULL)) snprintf(vl.type_instance, sizeof(vl.type_instance), "%s-%s", ti1, ti2); else if ((ti1 != NULL) && (ti2 == NULL)) sstrncpy(vl.type_instance, ti1, sizeof(vl.type_instance)); plugin_dispatch_values(&vl); } static void submit_derive(const char *dev, const char *type, const char *ti1, const char *ti2, derive_t value) { submit(dev, type, ti1, ti2, &(value_t){.derive = value}, 1); } static void submit_derive2(const char *dev, const char *type, const char *ti1, const char *ti2, derive_t val1, derive_t val2) { value_t values[] = { {.derive = val1}, {.derive = val2}, }; submit(dev, type, ti1, ti2, values, STATIC_ARRAY_SIZE(values)); } static void submit_gauge(const char *dev, const char *type, const char *ti1, const char *ti2, gauge_t value) { submit(dev, type, ti1, ti2, &(value_t){.gauge = value}, 1); } static void submit_antx(const char *dev, const char *name, u_int32_t *vals, int vals_num) { char ti2[16]; for (int i = 0; i < vals_num; i++) { if (vals[i] == 0) continue; snprintf(ti2, sizeof(ti2), "%i", i); submit_derive(dev, "ath_stat", name, ti2, (derive_t)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; assert(which >= 1); assert(((size_t)which) < STATIC_ARRAY_SIZE(bounds)); for (int 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_derive(dev, type_name, specs[i].name, mac, val); if (item_summed(i)) misc += val; } if (misc != 0) submit_derive(dev, type_name, misc_name, mac, misc); } static int process_athstats(int sk, const char *dev) { struct ifreq ifr; struct ath_stats stats; int status; sstrncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); ifr.ifr_data = (void *)&stats; status = ioctl(sk, SIOCGATHSTATS, &ifr); if (status < 0) { /* Silent, because not all interfaces support all ioctls. */ DEBUG("madwifi plugin: Sending IO-control " "SIOCGATHSTATS to device %s " "failed with status %i.", dev, status); return status; } /* 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, STATIC_ARRAY_SIZE(stats.ast_ant_rx)); if (item_watched(STAT_AST_ANT_TX)) submit_antx(dev, "ast_ant_tx", stats.ast_ant_tx, STATIC_ARRAY_SIZE(stats.ast_ant_tx)); /* All other ath statistics */ process_stat_struct(ATH_STAT, &stats, dev, NULL, "ath_stat", "ast_misc"); return 0; } static int process_80211stats(int sk, const char *dev) { struct ifreq ifr; struct ieee80211_stats stats; int status; sstrncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); ifr.ifr_data = (void *)&stats; status = ioctl(sk, SIOCG80211STATS, &ifr); if (status < 0) { /* Silent, because not all interfaces support all ioctls. */ DEBUG("madwifi plugin: Sending IO-control " "SIOCG80211STATS to device %s " "failed with status %i.", dev, status); return status; } process_stat_struct(IFA_STAT, &stats, dev, NULL, "ath_stat", "is_misc"); return 0; } static int process_station(int sk, const char *dev, struct ieee80211req_sta_info *si) { static char mac[DATA_MAX_NAME_LEN]; struct ieee80211req_sta_stats stats; const struct ieee80211_nodestats *ns = &stats.is_stats; int status; 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); struct iwreq iwr = {.u.data.pointer = (void *)&stats, .u.data.length = sizeof(stats)}; sstrncpy(iwr.ifr_name, dev, sizeof(iwr.ifr_name)); memcpy(stats.is_u.macaddr, si->isi_macaddr, IEEE80211_ADDR_LEN); status = ioctl(sk, IEEE80211_IOCTL_STA_STATS, &iwr); if (status < 0) { /* Silent, because not all interfaces support all ioctls. */ DEBUG("madwifi plugin: Sending IO-control " "IEEE80211_IOCTL_STA_STATS to device %s " "failed with status %i.", dev, status); return status; } /* These two stats are handled as a special case as they are a pair of 64bit values */ if (item_watched(STAT_NODE_OCTETS)) submit_derive2(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_derive(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"); return 0; } static int process_stations(int sk, const char *dev) { uint8_t buf[24 * 1024] = {0}; uint8_t *cp; int nodes; size_t len; int status; struct iwreq iwr = {.u.data.pointer = (void *)buf, .u.data.length = sizeof(buf)}; sstrncpy(iwr.ifr_name, dev, sizeof(iwr.ifr_name)); status = ioctl(sk, IEEE80211_IOCTL_STA_INFO, &iwr); if (status < 0) { /* Silent, because not all interfaces support all ioctls. */ DEBUG("madwifi plugin: Sending IO-control " "IEEE80211_IOCTL_STA_INFO to device %s " "failed with status %i.", dev, status); return status; } 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); return 0; } static int process_device(int sk, const char *dev) { int num_success = 0; int status; status = process_athstats(sk, dev); if (status == 0) num_success++; status = process_80211stats(sk, dev); if (status == 0) num_success++; status = process_stations(sk, dev); if (status == 0) num_success++; return (num_success == 0) ? -1 : 0; } static int check_devname(const char *dev) { char buf[PATH_MAX]; char buf2[PATH_MAX]; int i; if (dev[0] == '.') return 0; snprintf(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'; if (strstr(buf2, "/drivers/ath_") == NULL) return 0; return 1; } static int sysfs_iterate(int sk) { struct dirent *de; DIR *nets; int status; int num_success; int num_fail; nets = opendir("/sys/class/net/"); if (nets == NULL) { WARNING("madwifi plugin: opening /sys/class/net failed"); return -1; } num_success = 0; num_fail = 0; while ((de = readdir(nets))) { if (check_devname(de->d_name) == 0) continue; if (ignorelist_match(ignorelist, de->d_name) != 0) continue; status = process_device(sk, de->d_name); if (status != 0) { ERROR("madwifi plugin: Processing interface " "%s failed.", de->d_name); num_fail++; } else { num_success++; } } /* while (readdir) */ closedir(nets); if ((num_success == 0) && (num_fail != 0)) return -1; return 0; } static int procfs_iterate(int sk) { char buffer[1024]; char *device, *dummy; FILE *fh; int status; int num_success; int num_fail; if ((fh = fopen("/proc/net/dev", "r")) == NULL) { WARNING("madwifi plugin: opening /proc/net/dev failed"); return -1; } num_success = 0; num_fail = 0; while (fgets(buffer, sizeof(buffer), fh) != NULL) { dummy = strchr(buffer, ':'); if (dummy == NULL) continue; dummy[0] = 0; device = buffer; while (device[0] == ' ') device++; if (device[0] == 0) continue; if (ignorelist_match(ignorelist, device) != 0) continue; status = process_device(sk, device); if (status != 0) { ERROR("madwifi plugin: Processing interface " "%s failed.", device); num_fail++; } else { num_success++; } } /* while (fgets) */ fclose(fh); if ((num_success == 0) && (num_fail != 0)) return -1; return 0; } static int madwifi_read(void) { int rv; int sk; if (init_state == 0) madwifi_real_init(); init_state = 2; sk = socket(AF_INET, SOCK_DGRAM, 0); if (sk < 0) return -1; /* 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); }