From 5d0a7ea19f72b49445f4a7b775cf4635ee2677e2 Mon Sep 17 00:00:00 2001 From: Eugeni Dodonov Date: Tue, 29 Sep 2009 19:27:12 -0300 Subject: monitoring wireless signal too --- src/_native.c | 110 ++++++++++++++++++++++++++++---- src/monitor.py | 26 ++++++-- src/net_monitor | 194 ++++++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 271 insertions(+), 59 deletions(-) (limited to 'src') diff --git a/src/_native.c b/src/_native.c index 5524c66..8336679 100644 --- a/src/_native.c +++ b/src/_native.c @@ -5,6 +5,7 @@ #include #include #include +#include #ifndef __user #define __user #endif @@ -19,12 +20,12 @@ /* * Constants fof WE-9->15 */ -#define IW15_MAX_FREQUENCIES 16 -#define IW15_MAX_BITRATES 8 -#define IW15_MAX_TXPOWER 8 -#define IW15_MAX_ENCODING_SIZES 8 -#define IW15_MAX_SPY 8 -#define IW15_MAX_AP 8 +#define IW15_MAX_FREQUENCIES 16 +#define IW15_MAX_BITRATES 8 +#define IW15_MAX_TXPOWER 8 +#define IW15_MAX_ENCODING_SIZES 8 +#define IW15_MAX_SPY 8 +#define IW15_MAX_AP 8 /* * Struct iw_range up to WE-15 @@ -89,17 +90,17 @@ union iw_range_raw #define iwr_off(f) ( ((char *) &(((struct iw_range *) NULL)->f)) - \ (char *) NULL) -typedef struct iw_range iwrange; +typedef struct iw_range iwrange; /*------------------------------------------------------------------*/ /* * Wrapper to extract some Wireless Parameter out of the driver */ static inline int -iw_get_ext(int skfd, /* Socket to the kernel */ - const char * ifname, /* Device name */ - int request, /* WE ID */ - struct iwreq * pwrq) /* Fixed part of the request */ +iw_get_ext(int skfd, /* Socket to the kernel */ + const char * ifname, /* Device name */ + int request, /* WE ID */ + struct iwreq * pwrq) /* Fixed part of the request */ { /* Set device name */ strncpy(pwrq->ifr_name, ifname, IFNAMSIZ); @@ -186,6 +187,62 @@ iw_get_range_info(int skfd, const char *ifname, iwrange * range) return(0); } +/*------------------------------------------------------------------*/ +/* + * Display an Ethernet address in readable format. + */ +void +iw_ether_ntop(const struct ether_addr * eth, + char * buf) +{ + sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", + eth->ether_addr_octet[0], eth->ether_addr_octet[1], + eth->ether_addr_octet[2], eth->ether_addr_octet[3], + eth->ether_addr_octet[4], eth->ether_addr_octet[5]); +} + +/*------------------------------------------------------------------*/ +/* + * Compare two ethernet addresses + */ +static inline int +iw_ether_cmp(const struct ether_addr* eth1, const struct ether_addr* eth2) +{ + return memcmp(eth1, eth2, sizeof(*eth1)); +} + +/*------------------------------------------------------------------*/ +/* + * Display an Wireless Access Point Socket Address in readable format. + * Note : 0x44 is an accident of history, that's what the Orinoco/PrismII + * chipset report, and the driver doesn't filter it. + */ +char * +iw_sawap_ntop(const struct sockaddr * sap, + char * buf) +{ + const struct ether_addr ether_zero = {{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}; + const struct ether_addr ether_bcast = {{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }}; + const struct ether_addr ether_hack = {{ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44 }}; + const struct ether_addr * ether_wap = (const struct ether_addr *) sap->sa_data; + + if(!iw_ether_cmp(ether_wap, ðer_zero)) + sprintf(buf, "Not-Associated"); + else + if(!iw_ether_cmp(ether_wap, ðer_bcast)) + sprintf(buf, "Invalid"); + else + if(!iw_ether_cmp(ether_wap, ðer_hack)) + sprintf(buf, "None"); + else + iw_ether_ntop(ether_wap, buf); + return(buf); +} + +/**************************************************************************/ +/* PYTHON BINDINGS / +/*************************************************************************/ + static PyObject * wifi_get_max_quality(PyObject *self, PyObject *args) { @@ -214,10 +271,41 @@ static PyObject * return Py_BuildValue("i", max_quality); } +static PyObject * + wifi_get_ap(PyObject *self, PyObject *args) +{ + const char *iface; + int max_quality; + int fd, err; + struct iwreq wrq; + char buffer[32]; + + if (!PyArg_ParseTuple(args, "s", &iface)) + return NULL; + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + fprintf (stderr, "couldn't open socket\n"); + return NULL; + } + + /* Get AP address */ + err = iw_get_ext(fd, iface, SIOCGIWAP, &wrq); + + if (err < 0) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + return Py_BuildValue("s", iw_sawap_ntop(&wrq.u.ap_addr, buffer)); +} + /* python module details */ static PyMethodDef net_monitor_Methods[] = { {"wifi_get_max_quality", wifi_get_max_quality, METH_VARARGS, "Find maximum quality value for a wireless interface."}, + {"wifi_get_ap", wifi_get_ap, METH_VARARGS, + "Find AP address for an interface."}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/src/monitor.py b/src/monitor.py index 12acf1a..b2eebd9 100644 --- a/src/monitor.py +++ b/src/monitor.py @@ -1,14 +1,16 @@ #!/usr/bin/python """net_monitor: wifi monitoring""" -# native librari implements a few bits -import _native +import os import socket import fcntl import struct import traceback import array +# native librari implements a few bits +import _native + class Monitor: # based on http://svn.pardus.org.tr/pardus/tags/pardus-1.0/system/base/wireless-tools/comar/link.py @@ -38,13 +40,16 @@ class Monitor: res = self.ioctl(func, data) return res except: - traceback.print_exc() return None def wifi_get_max_quality(self, iface): """Gets maximum quality value""" return _native.wifi_get_max_quality(iface) + def wifi_get_ap(self, iface): + """Gets access point address""" + return _native.wifi_get_ap(iface) + def wifi_get_essid(self, iface): """Get current essid for an interface""" buffer = array.array('c', '\0' * 16) @@ -56,6 +61,8 @@ class Monitor: def wifi_get_mode(self, iface): """Get current mode from an interface""" result = self.wifi_ioctl(iface, self.SIOCGIWMODE) + if not result: + return _("Unknown") mode = struct.unpack("i", result[16:20])[0] return self.modes[mode] @@ -74,7 +81,7 @@ class Monitor: bitrate = float(m) * 10**e return bitrate else: - return -1 + return 0 def get_status(self, ifname): try: @@ -88,9 +95,10 @@ class Monitor: status = _("Unknown") return status - def readwireless(self): + def wireless_stats(self): """Check if device is wireless and get its details if necessary""" try: + stats = {} with open("/proc/net/wireless") as fd: ifaces = fd.readlines()[2:] for line in ifaces: @@ -100,12 +108,18 @@ class Monitor: iface, params = line.split(":", 1) iface = iface.strip() params = params.replace(".", "").split() - return {} + link = int(params[1]) + stats[iface] = link + return stats except: # something bad happened traceback.print_exc() return {} + def has_wireless(self, iface): + """Checks if device has wireless capabilities""" + return os.access("/sys/class/net/%s/wireless" % iface, os.R_OK) + def get_address(self, ifname): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) mac=_("No physical address") diff --git a/src/net_monitor b/src/net_monitor index ecbbb3e..a54fd94 100755 --- a/src/net_monitor +++ b/src/net_monitor @@ -40,18 +40,17 @@ class LoadGraph: bandwidth load. """ # we don't want to allow the window to shrink below this height - min_height = 70 padding = { "left" : 50, "right" : 10, "top" : 10, "bottom" : 10 } colors = ( "bg", "bg_outer", "in", "out" ) - def __init__(self, widget, hist, size): + def __init__(self, widget, hist, size, min_height=70, axes_text=_("Bytes"), draw_both=True, max=None): """ widget => GtkDrawingArea we paint into hist => a list of integers containing the hist of incoming traffic width, height => initial size of the GtkDrawingArea widget """ # set the minimum height of the widget - widget.set_size_request(-1, 70) + widget.set_size_request(-1, min_height) # the object holding the history and its size self.__hist = hist @@ -62,6 +61,16 @@ class LoadGraph: self.__str_mid = "" # gets computed later self.__str_max = "" # gets computer later + # axes descriptions + self.axes_text = axes_text + + # are we drawing both values? + self.draw_both = draw_both + + # minimum and maximum values + self.min = min + self.max = max + # size of the GtkDrawingArea self.__rect = self.__inner = gtk.gdk.Rectangle() self.maxval = 0 # maximum value in the history @@ -113,7 +122,8 @@ class LoadGraph: # draw the actual bandwidth curves self.__draw_bw(self.__in, self.__colors["fg_in"]) - self.__draw_bw(self.__out, self.__colors["fg_out"]) + if self.draw_both: + self.__draw_bw(self.__out, self.__colors["fg_out"]) # draw minimum, middle and max numbers self.__draw_num(self.__inner.height + self.__inner.y, self.__str_min, (255, 255, 255)) @@ -152,7 +162,6 @@ class LoadGraph: return [self.__inner.height + self.__inner.y] * len(hist) for item in hist: - if item <= 100: item = 0 # treshold to get rid of really small peaks converted.append((self.__inner.height - int(item / self.__mesh_y)) + self.__inner.y) return converted @@ -170,15 +179,18 @@ class LoadGraph: def __get_max(self): """ Finds the maximum value in both incoming and outgoing queue """ - if self.__hist["in"]: - maxin = max(self.__hist["in"]) - else: - maxin = 0 - if self.__hist["out"]: - maxout = max(self.__hist["out"]) + if self.max != None: + self.maxval = self.max else: - maxout = 0 - self.maxval = max(maxin, maxout) + if self.__hist["in"]: + maxin = max(self.__hist["in"]) + else: + maxin = 0 + if self.__hist["out"]: + maxout = max(self.__hist["out"]) + else: + maxout = 0 + self.maxval = max(maxin, maxout) def __text_size(self): """ Computes the size of the text and thus the left border """ @@ -186,8 +198,11 @@ class LoadGraph: if val == 0 and self.maxval != 0: val = self.maxval - self.__str_max = "%d %s" % (val, _("Bytes")) - self.__str_mid = "%d %s" % (val/2, _("Bytes")) + self.__str_max = "%d %s" % (val, self.axes_text) + if val != val/2: + self.__str_mid = "%d %s" % (val/2, self.axes_text) + else: + self.__str_mid = "" LoadGraph.padding["left"] = self.__context.text_extents(self.__str_max)[2] + 10 def __on_size(self, rect): @@ -254,6 +269,7 @@ class MonitorGui: self.ifaces = self.monitor.readnet() self.enabled_ifaces = [] + self.wireless_ifaces = filter(self.monitor.has_wireless, self.ifaces.keys()) sorted_ifaces = self.ifaces.keys() sorted_ifaces.sort() @@ -287,8 +303,9 @@ class MonitorGui: def update(self, interval=1): """Updates traffic counters (interval is in seconds)""" + # TODO: move it to Monitor()? net=self.monitor.readnet() - self.monitor.readwireless() + wifi_stats = self.monitor.wireless_stats() for iface in self.ifaces: status = self.monitor.get_status(iface) old_data_in = self.ifaces[iface]['data_in'] @@ -296,6 +313,29 @@ class MonitorGui: total_in = self.ifaces[iface]['total_in'] total_out = self.ifaces[iface]['total_out'] data_in, data_out = self.monitor.get_traffic(iface, net) + # is it a wireless interface? + if iface in self.wireless_ifaces: + essid = self.monitor.wifi_get_essid(iface) + mode = self.monitor.wifi_get_mode(iface) + bitrate = self.monitor.wifi_get_bitrate(iface) + ap = self.monitor.wifi_get_ap(iface) + link = wifi_stats.get(iface, 0) + # calculate link quality + if "max_quality" in self.ifaces[iface]: + max_quality = self.ifaces[iface]["max_quality"] + if max_quality != 0: + quality = link * 100.0 / max_quality + else: + quality = 0 + else: + quality = 0 + + else: + essid = None + mode = None + bitrate = None + ap = None + quality = 0 # is it the first measure? if old_data_in == 0 and old_data_out == 0: old_data_in = data_in @@ -313,22 +353,49 @@ class MonitorGui: # speed speed_in = diff_in / interval speed_out = diff_out / interval - # calculating histogram - histogram_in = self.ifaces[iface]['histogram']['in'] - histogram_out = self.ifaces[iface]['histogram']['out'] - if histogram_in: - histo_in = reduce(lambda x, y: x+y, histogram_in) / len(histogram_in) - else: - histo_in = 0 - if histogram_out: - histo_out = reduce(lambda x, y: x+y, histogram_out) / len(histogram_in) - else: - histo_out = 0 # update saved values self.ifaces[iface]['data_in'] = data_in self.ifaces[iface]['data_out'] = data_out self.ifaces[iface]['total_in'] = total_in self.ifaces[iface]['total_out'] = total_out + # updating graphs + for histogram, graph, value_in, value_out in [ + ('histogram', 'graph', speed_in, speed_out), + ('link_histogram', 'link_graph', quality, quality) + ]: + if histogram not in self.ifaces[iface] or graph not in self.ifaces[iface]: + # skip invalid graphs + continue + # calculating histogram + hist_in = self.ifaces[iface][histogram]['in'] + hist_out = self.ifaces[iface][histogram]['out'] + if hist_in: + histo_in = reduce(lambda x, y: x+y, hist_in) / len(hist_in) + else: + histo_in = 0 + if hist_out: + histo_out = reduce(lambda x, y: x+y, hist_out) / len(hist_in) + else: + histo_out = 0 + hist_in.append(value_in) + if len(hist_in) > HISTOGRAM_SIZE: + del hist_in[0] + hist_out.append(value_out) + if len(hist_out) > HISTOGRAM_SIZE: + del hist_out[0] + graph = self.ifaces[iface][graph] + graph.update() + # calculate average network traffic + hist_in = self.ifaces[iface]['histogram']['in'] + hist_out = self.ifaces[iface]['histogram']['out'] + if hist_in: + histo_in = reduce(lambda x, y: x+y, hist_in) / len(hist_in) + else: + histo_in = 0 + if hist_out: + histo_out = reduce(lambda x, y: x+y, hist_out) / len(hist_in) + else: + histo_out = 0 # update widgets ip, mac = self.monitor.get_address(iface) for widget, value in [('widget_in', total_in), @@ -340,22 +407,13 @@ class MonitorGui: ('widget_ip_address', ip), ('widget_status', status), ('widget_hw_address', mac), + ('widget_essid', essid), + ('widget_mode', mode), + ('widget_bitrate', bitrate), + ('widget_ap', ap), ]: if widget in self.ifaces[iface]: self.ifaces[iface][widget].set_text(str(value)) - else: - print "%s not found in %s" % (widget, iface) - # updating graph - hist_in = self.ifaces[iface]['histogram']['in'] - hist_in.append(speed_in) - if len(hist_in) > HISTOGRAM_SIZE: - del hist_in[0] - hist_out = self.ifaces[iface]['histogram']['out'] - hist_out.append(speed_out) - if len(hist_out) > HISTOGRAM_SIZE: - del hist_out[0] - graph = self.ifaces[iface]['graph'] - graph.update() gobject.timeout_add(interval * 1000, self.update) def check_network_accounting(self, iface): @@ -415,7 +473,7 @@ class MonitorGui: def build_iface_stat(self, iface): """Builds graphical view for interface""" - traf_vbox = gtk.VBox() + traf_vbox = gtk.VBox(spacing=5) # graph draw = gtk.DrawingArea() traf_vbox.pack_start(draw) @@ -454,7 +512,7 @@ class MonitorGui: # traffic frame = gtk.Frame(_("Traffic statistics")) - traf_vbox.pack_start(frame) + traf_vbox.pack_start(frame, False, False) hbox = gtk.HBox(spacing=5) frame.add(hbox) # building pairs of boxes @@ -494,6 +552,58 @@ class MonitorGui: self.ifaces[iface]["widget_histo_out"] = histo_out self.build_widget_pair(vbox, histo_in_h, histo_out_h) + # wireless statistics + if iface in self.wireless_ifaces: + frame = gtk.Frame(_("Wireless properties")) + traf_vbox.pack_start(frame) + wifi_vbox = gtk.VBox() + hbox = gtk.HBox(spacing=5) + wifi_vbox.pack_start(hbox, False, False) + frame.add(wifi_vbox) + # building pairs of boxes + vbox_left = gtk.VBox(spacing=5) + vbox_right = gtk.VBox(spacing=5) + hbox.add(vbox_left) + hbox.add(vbox_right) + # shortcut for pair of boxes + vbox = (vbox_left, vbox_right) + + # essid + essid_h, essid = self.build_value_pair(sizegroup1, _("Network ID:")) + self.ifaces[iface]["widget_essid"] = essid + # mode + mode_h, mode = self.build_value_pair(sizegroup2, _("Operating mode:")) + self.ifaces[iface]["widget_mode"] = mode + self.build_widget_pair(vbox, essid_h, mode_h) + + # bitrate + bitrate_h, bitrate = self.build_value_pair(sizegroup2, _("Bitrate:")) + self.ifaces[iface]["widget_bitrate"] = bitrate + # AP + ap_h, ap = self.build_value_pair(sizegroup2, _("Access point or cell:")) + self.ifaces[iface]["widget_ap"] = ap + self.build_widget_pair(vbox, bitrate_h, ap_h) + + # caching quality values + self.ifaces[iface]["max_quality"] = self.monitor.wifi_get_max_quality(iface) + + # link quality graph + draw = gtk.DrawingArea() + histogram = {"in": [], "out": []} + graph = LoadGraph(draw, histogram, HISTOGRAM_SIZE, min_height=50, axes_text="%", draw_both=False, max=100) + draw.connect('expose_event', graph.on_expose) + self.ifaces[iface]['link_graph'] = graph + self.ifaces[iface]['link_histogram'] = histogram + + # adding link quality graph + label = gtk.Label() + label.set_markup("%s" % _("Connection quality")) + label.set_property("xalign", 0.0) + + wifi_vbox.pack_start(label, False, False) + wifi_vbox.pack_start(draw) + + # statistics button if self.check_network_accounting(iface): button = gtk.Button(_("Show detailed network statistics")) -- cgit v1.2.1