diff --git a/CHANGES b/CHANGES index ce219f8..438a9ea 100644 --- a/CHANGES +++ b/CHANGES @@ -180,4 +180,5 @@ v 0.1.6 Continue with STA functionality have tested further, phyadd,devadd will work (i.e. not rename the card) under the following conditions a) the type is not managed or b) there is no other device currently associated with the new card's phy. Have also determined - that integrated cards are also suspect to this bug? \ No newline at end of file + that integrated cards are also suspect to this bug? + o made pyric error messages less ambiguous \ No newline at end of file diff --git a/TODO b/TODO index ab54432..570f4f2 100644 --- a/TODO +++ b/TODO @@ -19,15 +19,11 @@ 26) need to parse dumps (NLM_F_DUMP), for now we're good with link etc, so long as the card is connected but it may come up eventually especially if we want to add scan results - 29) figure out how to parse the information element in pyw.link - there's some - good shit in it including sometimes the router os and type + 29) figure out how to parse the information element in pyw.link - sometimes + the router os and type are included o in link, NL80211_BSS_BEACON_IES vs NL80211_BSS_INFORMATION_ELEMENT - BEACON_IES may not always be presenet but appears to contain more + BEACON_IES may not always be present but appears to contain more 31) add VHT processing to sta_info bitrate 39) parsing wiphy_bands (should we add the below?) o _HT_MCS_SET: a 16-bit attr containing the MCS set as defined in 802.11n - o _HT_CAPA: as in the HT information IE - 42) is there a simple way to set the format strings as constants keeping in - mind that things like strings require a length in the format string - 46) max-tx for bands is showing 15 when iwconfig shows 30 - 47) add an explain() to errors? or hard-code additional error reporting? + o _HT_CAPA: as in the HT information IE \ No newline at end of file diff --git a/pyric/__init__.py b/pyric/__init__.py index 9c2486d..601c7bc 100644 --- a/pyric/__init__.py +++ b/pyric/__init__.py @@ -19,8 +19,11 @@ contributors may be used to endorse or promote products derived from this software without specific prior written permission. -Defines the Pyric error class and constants for some errors. All pyric errors -will follow the 2-tuple form of EnvironmentError +Defines the PyRIC error class and constants for some errors. All pyric errors +will follow the 2-tuple form of EnvironmentError. Also defines constansts for +PyPI packaging. + +WARNING: DO NOT import * Requires: linux (3.x or 4.x kernel) @@ -32,9 +35,6 @@ includes: /nlhelp /lib /net /utils pyw.py changes: See CHANGES in top-level directory - - WARNING: DO NOT import * - """ __name__ = 'pyric' @@ -46,27 +46,33 @@ __email__ = 'wraith.wireless@yandex.com' __status__ = 'Production' -# define pyric exceptions -# all exceptions are tuples t=(error code,error message) -# we use error codes defined in errno, adding -1 to define the undefined error -# EUNDEF. I don't like importing all from errno but it provides conformity in -# error handling i.e modules using pyric.error do not need to call pyric.EUNDEF -# and errno.EINVAL but can call pyric.EUNDEF and pyric.EINVAL +""" + define pyric exceptions + all exceptions are tuples t=(error code,error message) + we use error codes defined in errno, using EUNDEF = -1 to define an undefined + error I don't like importing all from errno but it provides conformity in + error handling i.e modules using pyric.error do not need to call pyric.EUNDEF + and errno.EINVAL but can call pyric.EUNDEF and pyric.EINVAL +""" EUNDEF = -1 # undefined error from errno import * # make all errno errors pyric errors errorcode[EUNDEF] = "EUNDEF" # add ours to errorcode dicts -class error(EnvironmentError): pass - -# BELOW IS STILL A WORK IN PRGORESS -#def strerror(errno): -# import os -# if errno < 0: return "Undefined error" -# elif errno == EPERM: return "Superuser privileges required" -# elif errno == EINVAL: return "Invalid parameter" -# else: -# return os.strerror(errno) -# device busy if setting channel when card is down -# no open files if attempt to create a virtual card with same name as orginal +class error(EnvironmentError): + def __init__(self,errno,errmsg=None): + if not errmsg: errmsg = strerror(errno) + EnvironmentError.__init__(self,errno,errmsg) + +def strerror(errno): + import os + if errno < 0: return "Undefined error" + elif errno == EPERM: return "Superuser privileges required" + elif errno == EINVAL: return "Invalid parameter" + elif errno == EBUSY: + msg = "{0}. Make sure Card is up and no other devices share the same phy" + return msg.format(os.strerror(EBUSY)) + elif errno == ENFILE: return "There are no available netlink sockets" + else: + return os.strerror(errno) # for setup.py use # redefine version for easier access diff --git a/pyric/lib/libio.py b/pyric/lib/libio.py index a6d3b20..2f19232 100644 --- a/pyric/lib/libio.py +++ b/pyric/lib/libio.py @@ -64,7 +64,7 @@ def io_transfer(iosock,flag,ifreq): return ioctl(iosock.fileno(),flag,ifreq) except (AttributeError,struct.error) as e: # either sock is not valid or a bad value passed to ifreq - if e.message.find('fileno'): raise error(errno.ENOTSOCK,"bad socket") + if e.message.find('fileno'): raise error(errno.ENOTSOCK,"Bad socket") else: raise error(errno.EINVAL,e) except IOError as e: # generally device cannot be found sort but can also be diff --git a/pyric/lib/libnl.py b/pyric/lib/libnl.py index aeaddb4..6fe7599 100644 --- a/pyric/lib/libnl.py +++ b/pyric/lib/libnl.py @@ -249,10 +249,10 @@ def nl_recvmsg(sock): # on success, just return the orginal message if e.errno == nlh.NLE_SUCCESS: pass else: raise - if sock.seq != msg.seq: raise error(errno.EBADMSG,"seq. # out of order") + if sock.seq != msg.seq: raise error(errno.EBADMSG,"Seq. # out of order") return msg except socket.timeout: - raise error(-1,"socket timed out") + raise error(-1,"Socket timed out") #except socket.error as e: # this became in issue in python 3 # raise error(errno.ENOTSOCK,e) except error as e: @@ -354,7 +354,7 @@ def nltype(self): return self['type'] @nltype.setter def nltype(self,v): - if v < 0: raise error(errno.ERANGE,"nltype {0} is invalid".format(v)) + if v < 0: raise error(errno.ERANGE,"Netlink type {0} is invalid".format(v)) self['type'] = v @property @@ -368,7 +368,7 @@ def seq(self): return self['seq'] @seq.setter def seq(self,v): - if v < 1: raise error(errno.ERANGE,"invalid seq. number") + if v < 1: raise error(errno.ERANGE,"Invalid seq. number") self['seq'] = v @property @@ -376,7 +376,7 @@ def pid(self): return self['pid'] @pid.setter def pid(self,v): - if v < 1: raise error(errno.ERANGE,"invalid port id") + if v < 1: raise error(errno.ERANGE,"Invalid port id") self['pid'] = v @property @@ -384,7 +384,7 @@ def cmd(self): return self['cmd'] @cmd.setter def cmd(self,v): - if v < 0: raise error(errno.ERANGE,"invalid cmd") + if v < 0: raise error(errno.ERANGE,"Invalid cmd") self['cmd'] = v @property @@ -444,7 +444,7 @@ def nlmsg_fromstream(stream,override=False): raise error(abs(e),strerror(abs(e))) c,_,_ = struct.unpack_from(genlh.genl_genlmsghdr,stream,nlh.NLMSGHDRLEN) except struct.error as e: - raise error(-1,"error parsing headers: {0}".format(e)) + raise error(-1,"Error parsing headers: {0}".format(e)) # create a new message with hdr values then parse the attributes msg = nlmsg_new(t,c,s,p,fs) @@ -575,7 +575,7 @@ def nla_parse_set(aset,etype): elif etype == nlh.NLA_U32: fmt = "I" elif etype == nlh.NLA_U64: fmt = "Q" else: - raise error(errno.EINVAL,"set elements are not valid datatype") + raise error(errno.EINVAL,"Set elements are not valid datatype") esize = struct.calcsize(fmt) ss = [] @@ -588,7 +588,7 @@ def nla_parse_set(aset,etype): ss.append(s) idx += esize except struct.error: - raise error(errno.EINVAL,"set elements failed to unpack") + raise error(errno.EINVAL,"Set elements failed to unpack") return ss def nla_put(msg,v,a,d): @@ -599,7 +599,7 @@ def nla_put(msg,v,a,d): :param a: attribute type :param d: attribute datatype """ - if d > nlh.NLA_TYPE_MAX: raise error(errno.ERANGE,"value type is invalid") + if d > nlh.NLA_TYPE_MAX: raise error(errno.ERANGE,"Value type is invalid") msg['attrs'].append((a,v,d)) # nla_put_* append data of specified datatype @@ -626,7 +626,7 @@ def nla_putat(msg,i,v,a,d): :param a: attribute type :param d: attribute datatype """ - if d > nlh.NLA_TYPE_MAX: raise error(errno.ERANGE,"invalid datatype") + if d > nlh.NLA_TYPE_MAX: raise error(errno.ERANGE,"Invalid datatype") msg['attrs'][i] = (a,v,d) def nla_pop(msg,i): diff --git a/pyric/pyw.py b/pyric/pyw.py index 1861f56..4915680 100644 --- a/pyric/pyw.py +++ b/pyric/pyw.py @@ -159,7 +159,7 @@ def iswireless(dev, *argv): return _iostub_(iswireless, dev) try: - # if the call succeeds, found to be wireless + # if the call succeeds, dev is found to be wireless _ = io.io_transfer(iosock, sioch.SIOCGIWNAME, ifh.ifreq(dev)) return True except AttributeError as e: @@ -167,7 +167,7 @@ def iswireless(dev, *argv): except io.error as e: # ENODEV or ENOTSUPP means not wireless, reraise any others if e.errno == pyric.ENODEV or e.errno == pyric.EOPNOTSUPP: return False - else: raise pyric.error(e.errno, e.strerror) + else: raise pyric.error(e.errno) def phylist(): """ :returns: a list of tuples t = (physical indexe, physical name) """ @@ -636,7 +636,7 @@ def isblocked(card): idx = rfkill.getidx(card.phy) return rfkill.soft_blocked(idx), rfkill.hard_blocked(idx) except AttributeError: - raise pyric.error(pyric.ENODEV, "Card is no longer regsitered") + raise pyric.error(pyric.ENODEV, "Card is no longer registered") def block(card): """ @@ -658,7 +658,7 @@ def unblock(card): idx = rfkill.getidx(card.phy) rfkill.rfkill_unblock(idx) except AttributeError: - raise pyric.error(pyric.ENODEV, "Card no longer registered") + raise pyric.error(pyric.ENODEV, "Card is no longer registered") ################################################################################ #### RADIO PROPERTIES #### @@ -715,7 +715,7 @@ def pwrsaveset(card, on, *argv): except AttributeError: raise pyric.error(pyric.EINVAL, "Invalid Card") except ValueError: - raise pyric.error(pyric.EINVAL, "Invalid parameter on") + raise pyric.error(pyric.EINVAL, "Invalid parameter {0} for on".format(on)) except nl.error as e: raise pyric.error(e.errno, e.strerror) @@ -747,9 +747,9 @@ def covclassset(card, cc, *argv): if cc < wlan.COV_CLASS_MIN or cc > wlan.COV_CLASS_MAX: # this can work 'incorrectly' on non-int values but these will # be caught later during conversion - msg = "Cov class must be integer {0}-{1}".format(wlan.COV_CLASS_MIN, - wlan.COV_CLASS_MAX) - raise pyric.error(pyric.EINVAL, msg) + emsg = "Cov class must be integer {0}-{1}".format(wlan.COV_CLASS_MIN, + wlan.COV_CLASS_MAX) + raise pyric.error(pyric.EINVAL, emsg) try: nlsock = argv[0] @@ -767,7 +767,7 @@ def covclassset(card, cc, *argv): except AttributeError: raise pyric.error(pyric.EINVAL, "Invalid Card") except ValueError: - raise pyric.error(pyric.EINVAL, "Invalid value for Cov. Class") + raise pyric.error(pyric.EINVAL, "Invalid value {0} for Cov. Class".format(cc)) except nl.error as e: raise pyric.error(e.errno, e.strerror) @@ -798,9 +798,9 @@ def retryshortset(card, lim, *argv): if lim < wlan.RETRY_MIN or lim > wlan.RETRY_MAX: # this can work 'incorrectly' on non-int values but these will # be caught later during conversion - msg = "Retry short must be integer {0}-{1}".format(wlan.RETRY_MIN, - wlan.RETRY_MAX) - raise pyric.error(pyric.EINVAL, msg) + emsg = "Retry short must be integer {0}-{1}".format(wlan.RETRY_MIN, + wlan.RETRY_MAX) + raise pyric.error(pyric.EINVAL, emsg) try: nlsock = argv[0] @@ -818,7 +818,7 @@ def retryshortset(card, lim, *argv): except AttributeError: raise pyric.error(pyric.EINVAL, "Invalid Card") except ValueError: - raise pyric.error(pyric.EINVAL, "Invalid parameter value for lim") + raise pyric.error(pyric.EINVAL, "Invalid value {0} for lim".format(lim)) except nl.error as e: raise pyric.error(e.errno, e.strerror) @@ -849,9 +849,9 @@ def retrylongset(card, lim, *argv): if lim < wlan.RETRY_MIN or lim > wlan.RETRY_MAX: # this can work 'incorrectly' on non-int values but these will # be caught later during conversion - msg = "Retry long must be integer {0}-{1}".format(wlan.RETRY_MIN, - wlan.RETRY_MAX) - raise pyric.error(pyric.EINVAL, msg) + emsg = "Retry long must be integer {0}-{1}".format(wlan.RETRY_MIN, + wlan.RETRY_MAX) + raise pyric.error(pyric.EINVAL, emsg) try: nlsock = argv[0] @@ -869,7 +869,7 @@ def retrylongset(card, lim, *argv): except AttributeError: raise pyric.error(pyric.EINVAL, "Invalid Card") except ValueError: - raise pyric.error(pyric.EINVAL, "Invalid parameter value for lim") + raise pyric.error(pyric.EINVAL, "Invalid value {0} for lim".format(lim)) except nl.error as e: raise pyric.error(e.errno, e.strerror) @@ -900,9 +900,9 @@ def rtsthreshset(card, thresh, *argv): if thresh == 'off': thresh = wlan.RTS_THRESH_OFF elif thresh == wlan.RTS_THRESH_OFF: pass elif thresh < wlan.RTS_THRESH_MIN or thresh > wlan.RTS_THRESH_MAX: - msg = "Thresh must be 'off' or integer {0}-{1}".format(wlan.RTS_THRESH_MIN, - wlan.RTS_THRESH_MAX) - raise pyric.error(pyric.EINVAL, msg) + emsg = "Thresh must be 'off' or integer {0}-{1}".format(wlan.RTS_THRESH_MIN, + wlan.RTS_THRESH_MAX) + raise pyric.error(pyric.EINVAL, emsg) try: nlsock = argv[0] @@ -920,7 +920,7 @@ def rtsthreshset(card, thresh, *argv): except AttributeError: raise pyric.error(pyric.EINVAL, "Invalid Card") except ValueError: - raise pyric.error(pyric.EINVAL, "Invalid parameter value for thresh") + raise pyric.error(pyric.EINVAL, "Invalid value {0} for thresh".format(thresh)) except nl.error as e: raise pyric.error(e.errno, e.strerror) @@ -951,9 +951,9 @@ def fragthreshset(card, thresh, *argv): if thresh == 'off': thresh = wlan.FRAG_THRESH_OFF elif thresh == wlan.FRAG_THRESH_OFF: pass elif thresh < wlan.FRAG_THRESH_MIN or thresh > wlan.FRAG_THRESH_MAX: - msg = "Thresh must be 'off' or integer {0}-{1}".format(wlan.FRAG_THRESH_MIN, - wlan.FRAG_THRESH_MAX) - raise pyric.error(pyric.EINVAL, msg) + emsg = "Thresh must be 'off' or integer {0}-{1}".format(wlan.FRAG_THRESH_MIN, + wlan.FRAG_THRESH_MAX) + raise pyric.error(pyric.EINVAL, emsg) try: nlsock = argv[0] @@ -1121,7 +1121,7 @@ def devinfo(card, *argv): dev = None # appease pycharm try: - # if we have a Card object, pull at dev,ifindex. otherwise get ifindex + # if we have a Card, pull out ifindex. otherwise get ifindex from dev try: dev = card.dev idx = card.idx @@ -1138,13 +1138,16 @@ def devinfo(card, *argv): rmsg = nl.nl_recvmsg(nlsock) except io.error as e: # if we get a errno -19, it means ifindex failed & there is no device dev - if e.errno == pyric.ENODEV: - raise pyric.error(pyric.ENODEV, "No device {0} found".format(dev)) raise pyric.error(e.errno, e.strerror) except nl.error as e: - # if we get a errno -19, it means ifindex succeeded but netlink failed - # most likely because the given device does not support nl80211 + # if we get a errno -19, it is mostly likely because the card does + # not support nl80211. However check to ensure the card hasn't been + # unplugged. if e.errno == pyric.ENODEV: + try: + _ = _ifindex_(dev) + except io.error as e: + raise pyric.error(e.errno, "{0}. Check Card".format(e.strerror)) raise pyric.error(pyric.EPROTONOSUPPORT, "Device does not support nl80211") raise pyric.error(e.errno, e.strerror) @@ -1294,7 +1297,7 @@ def txset(card, setting, lvl, *argv): _ = nl.nl_recvmsg(nlsock) except ValueError: # converting to mBm - raise pyric.error(pyric.EINVAL, "Invalid txpwr {0}".format(lvl)) + raise pyric.error(pyric.EINVAL, "Invalid value {0} for txpwr".format(lvl)) except AttributeError: raise pyric.error(pyric.EINVAL, "Invalid Card") except nl.error as e: @@ -1352,8 +1355,8 @@ def chset(card, ch, chw=None, *argv): :param chw: channel width oneof {[None|'HT20'|'HT40-'|'HT40+'} :param argv: netlink socket at argv[0] (or empty) NOTE: - Can throw a device busy for several reason. Card should be "up" when - setting channel + Can throw a device busy for several reason. 1) Card is down, 2) Another + device is sharing the phy and wpa_supplicant/Network Manage is using it """ try: nlsock = argv[0] @@ -1386,8 +1389,8 @@ def freqset(card, rf, chw=None, *argv): :param chw: channel width oneof {[None|'HT20'|'HT40-'|'HT40+'} :param argv: netlink socket at argv[0] (or empty) NOTE: - Can throw a device busy for several reason. Card should be "up" when - setting channel + Can throw a device busy for several reason. 1) Card is down, 2) Another + device is sharing the phy and wpa_supplicant/Network Manage is using it """ try: nlsock = argv[0] @@ -1405,10 +1408,11 @@ def freqset(card, rf, chw=None, *argv): nl.nl_sendmsg(nlsock, msg) _ = nl.nl_recvmsg(nlsock) except ValueError: - raise pyric.error(pyric.EINVAL, "Invalide channel width") + raise pyric.error(pyric.EINVAL, "Invalid channel width") except AttributeError: raise pyric.error(pyric.EINVAL, "Invalid Card") except nl.error as e: + if e.errno == pyric.EBUSY: raise pyric.error(e.errno,pyric.strerror(e.errno)) raise pyric.error(e.errno, e.strerror) #### INTERFACE & MODE RELATED #### @@ -1548,6 +1552,7 @@ def devadd(card, vdev, mode, flags=None, *argv): :returns: the new Card NOTE: the new Card will be 'down' """ + if iswireless(vdev): raise pyric.error(pyric.ENOTUNIQ,"{0} already exists".format(vdev)) if mode not in IFTYPES: raise pyric.error(pyric.EINVAL, 'Invalid mode') if flags: if mode != 'monitor': @@ -1822,15 +1827,17 @@ def link(card, *argv): if idx == nl80211h.NL80211_BSS_SIGNAL_MBM: info['rss'] = struct.unpack_from('i', attr, 0)[0] / 100 if idx == nl80211h.NL80211_BSS_INFORMATION_ELEMENTS: - # hacking the proprietary info element attribute: (it should - # be a nested attribute itself, but I have currently no way of - # knowing what the individual indexes would mean - # "\x06\x00\x00SSID..... - # '\x06\x00' is the ie index & the ssid is the first element - # (from what I've seen). This is not nested. Not sure if the - # length is the first two bytes or just the second Get the length - # of the ssid which is the 3rd,4th byte, then unpack the string - # starting at the fifth byte up to the specified length + """ + hack the proprietary info element attribute: (it should + be a nested attribute itself, but I have currently no way of + knowing what the individual indexes would mean + \x06\x00\x00SSID..... + '\x06\x00' is the ie index & the ssid is the first element + (from what I've seen). This is not nested. Not sure if the + length is the first two bytes or just the second Get the length + of the ssid which is the 3rd,4th byte, then unpack the string + starting at the fifth byte up to the specified length + """ try: l = struct.unpack_from('>H', attr, 0)[0] # have to change the format info['ssid'] = struct.unpack_from('{0}s'.format(l), attr, 2)[0] diff --git a/pyric/utils/rfkill.py b/pyric/utils/rfkill.py index 05500e6..cbbb4bd 100644 --- a/pyric/utils/rfkill.py +++ b/pyric/utils/rfkill.py @@ -74,6 +74,7 @@ def rfkill_list(): try: stream = fin.read(rfkh.RFKILLEVENTLEN) if _PY3_: + # noinspection PyArgumentList stream = bytes(stream,'ascii') if len(stream) < rfkh.RFKILLEVENTLEN: raise IOError('python 3') idx,t,op,s,h = struct.unpack(rfkh.rfk_rfkill_event,stream)