From a853ffc784ea63f97904848a63e964802c744336 Mon Sep 17 00:00:00 2001 From: Martin Turon Date: Thu, 22 Sep 2016 22:58:37 -0700 Subject: [PATCH] spinel-cli: Add ipaddr removal to prefix change handler. (#669) Fixes Cert_5_6_06 and Cert_5_6_08. Fixes #517. Collapse fcs16 into Hdlc class. Add and correct subcommand completion handlers. Move prefix handler to separate worker thread and task queue. Channelize blocking spinel property change handlers to allow parallel reuse across multiple threads. --- tests/scripts/Makefile.am | 2 - tools/spinel-cli/spinel-cli.py | 324 ++++++++++++++++++++++----------- 2 files changed, 213 insertions(+), 113 deletions(-) mode change 100644 => 100755 tools/spinel-cli/spinel-cli.py diff --git a/tests/scripts/Makefile.am b/tests/scripts/Makefile.am index 825b6da40..c476595e1 100644 --- a/tests/scripts/Makefile.am +++ b/tests/scripts/Makefile.am @@ -236,8 +236,6 @@ TESTS = \ $(NULL) XFAIL_NCP_TESTS = \ - thread-cert/Cert_5_6_06_NetworkDataExpiration.py \ - thread-cert/Cert_5_6_08_ContextManagement.py \ thread-cert/Cert_9_2_13_EnergyScan.py \ thread-cert/Cert_9_2_14_PanIdQuery.py \ $(NULL) diff --git a/tools/spinel-cli/spinel-cli.py b/tools/spinel-cli/spinel-cli.py old mode 100644 new mode 100755 index ca539a8e8..d33bb5d39 --- a/tools/spinel-cli/spinel-cli.py +++ b/tools/spinel-cli/spinel-cli.py @@ -31,19 +31,20 @@ spinel-cli.py vailable commands (type help for more information): ============================================================ -channel diag-sleep ifconfig q -child diag-start ipaddr quit -childtimeout diag-stats keysequence releaserouterid -clear diag-stop leaderdata rloc16 -contextreusedelay discover leaderweight route -counter eidcache masterkey router -debug enabled mode routerupgradethreshold -debug-mem exit netdataregister scan -diag extaddr networkidtimeout state -diag-channel extpanid networkname thread -diag-power h panid v -diag-repeat help ping version -diag-send history prefix whitelist +channel diag-sleep keysequence q +child diag-start leaderdata quit +childtimeout diag-stats leaderweight releaserouterid +clear diag-stop masterkey rloc16 +commissioner discover mode route +contextreusedelay eidcache ncp-ll64 router +counter exit ncp-ml64 routerupgradethreshold +debug extaddr ncp-tun scan +debug-mem extpanid netdataregister state +diag h networkidtimeout thread +diag-channel help networkname tun +diag-power history panid v +diag-repeat ifconfig ping version +diag-send ipaddr prefix whitelist """ __copyright__ = "Copyright (c) 2016 The OpenThread Authors." @@ -59,6 +60,7 @@ DEBUG_LOG_TX = 0 DEBUG_LOG_RX = 0 DEBUG_LOG_HDLC = 0 DEBUG_LOG_PKT = DEBUG_ENABLE +DEBUG_LOG_SERIAL = DEBUG_ENABLE DEBUG_LOG_PROP = DEBUG_ENABLE DEBUG_LOG_TUN = 0 DEBUG_CMD_RESPONSE = 0 @@ -104,6 +106,7 @@ from copy import copy from struct import pack from struct import unpack from select import select +from collections import namedtuple from collections import defaultdict logging.getLogger("scapy.runtime").setLevel(logging.ERROR) @@ -630,32 +633,30 @@ HDLC_FCS_INIT = 0xFFFF HDLC_FCS_POLY = 0x8408 HDLC_FCS_GOOD = 0xF0B8 -def mkfcstab(): - P = HDLC_FCS_POLY - - def valiter(): - for b in range(256): - v = b - i = 8 - while i: - v = (v >> 1) ^ P if v & 1 else v >> 1 - i -= 1 - - yield v & 0xFFFF - - return tuple(valiter()) - -fcstab = mkfcstab() - - -def fcs16(byte, fcs): - fcs = (fcs >> 8) ^ fcstab[(fcs ^ byte) & 0xff] - return fcs - class Hdlc(IStream): def __init__(self, stream): self.stream = stream + self.fcstab = self.mkfcstab() + + def mkfcstab(self): + P = HDLC_FCS_POLY + + def valiter(): + for b in range(256): + v = b + i = 8 + while i: + v = (v >> 1) ^ P if v & 1 else v >> 1 + i -= 1 + + yield v & 0xFFFF + + return tuple(valiter()) + + def fcs16(self, byte, fcs): + fcs = (fcs >> 8) ^ self.fcstab[(fcs ^ byte) & 0xff] + return fcs def collect(self): fcs = HDLC_FCS_INIT @@ -678,10 +679,7 @@ class Hdlc(IStream): if DEBUG_LOG_HDLC: raw.append(b) b ^= 0x20 packet.append(b) - fcs = fcs16(b, fcs) - #print("State: "+str(b)+ " FCS: 0x"+hexify_int(fcs)) - - #print("Fcs: 0x"+hexify_int(fcs)) + fcs = self.fcs16(b, fcs) if DEBUG_LOG_HDLC: logger.debug("RX Hdlc: "+str(map(hexify_int,raw))) @@ -705,7 +703,7 @@ class Hdlc(IStream): packet.append(HDLC_FLAG) for b in payload: b = ord(b) - fcs = fcs16(b, fcs) + fcs = self.fcs16(b, fcs) packet = self.encode_b(b, packet) fcs ^= 0xffff; @@ -875,21 +873,45 @@ class SpinelPropertyHandler(SpinelCodec): def THREAD_STABLE_NETWORK_DATA(self, payload): pass def THREAD_STABLE_NETWORK_DATA_VERSION(self, payload): pass - def handle_prefix_change(self, prefix, prefixlen, stable, flags, isLocal): + def __init__(self): + self.autoAddresses = set() + + self.__queue_prefix = Queue.Queue() + self.prefix_thread = threading.Thread(target=self.__run_prefix_handler) + self.prefix_thread.setDaemon(True) + self.prefix_thread.start() + + def handle_ipaddr_remove(self, ipaddr): + valid = 1 + preferred = 1 + flags = 0 + prefix_len = 64 # always use /64 + + arr = ipaddr.ip.packed + arr += pack('B', prefix_len) + arr += pack('>>> new PREFIX add ipaddr: "+ipaddr + print "\n>>>> new PREFIX add ipaddr: "+ipaddr valid = 1 preferred = 1 flags = 0 - prefix = ipaddress.IPv6Interface(unicode(ipaddr)) - arr = prefix.ip.packed + ipaddr = ipaddress.IPv6Interface(unicode(ipaddrStr)) + self.autoAddresses.add(ipaddr) + + arr = ipaddr.ip.packed arr += pack('B', prefixlen) arr += pack('= 22): - (structlen) = unpack('>H', pay[:2]) - pay = pay[2:] - (prefix, prefixlen, stable, flags, isLocal) = unpack('16sBBBB', pay[:20]) - self.handle_prefix_change(prefix, prefixlen, stable, flags, isLocal) - pay = pay[20:] - + def handle_prefix_change(self, payload): + """ Automatically ipaddr add / remove addresses for each new prefix. """ + # As done by cli.cpp Interpreter::HandleNetifStateChanged + + # First parse payload and extract slaac prefix information. + pay = payload + Prefix = namedtuple("Prefix", "prefix prefixlen stable flags isLocal") + prefixes = [] + slaacPrefixSet = set() + while (len(pay) >= 22): + (structlen) = unpack('>H', pay[:2]) + pay = pay[2:] + prefix = Prefix(*unpack('16sBBBB', pay[:20])) + if (prefix.flags & kThreadPrefixSlaacFlag): + net6 = ipaddress.IPv6Network(prefix.prefix) + net6 = net6.supernet(new_prefix=prefix.prefixlen) + slaacPrefixSet.add(net6) + prefixes.append(prefix) + pay = pay[20:] + + for prefix in prefixes: + self.handle_ipaddr_insert(*prefix) + + if DEBUG_LOG_PROP: + print "\n========= PREFIX ============" + print "ipaddrs: "+str(self.autoAddresses) + print "slaac prefix set: "+str(slaacPrefixSet) + print "==============================\n" + # ==> ipaddrs - query current addresses # # for ipaddr in ipaddrs: # if lifetime > 0 and not in slaac prefixes # ==> remove + for ipaddr in self.autoAddresses: + if not any(ipaddr in prefix for prefix in slaacPrefixSet): + self.handle_ipaddr_remove(ipaddr) # for slaac prefix in prefixes: # if no ipaddr with lifetime > 0 in prefix: # ==> add + + def __run_prefix_handler(self): + while 1: + payload = self.__queue_prefix.get(True) + self.handle_prefix_change(payload) + self.__queue_prefix.task_done() + + def THREAD_ON_MESH_NETS(self, payload): + if FEATURE_USE_SLACC: + # Kick prefix handler thread to allow serial rx thread to work. + self.__queue_prefix.put_nowait(payload) + return self.parse_D(payload) @@ -965,8 +1019,6 @@ class SpinelPropertyHandler(SpinelCodec): class SpinelCommandHandler(SpinelCodec): def handle_prop(self, name, payload, tid): - global gWpanApi - (prop_op, prop_len) = self.parse_i(payload) try: @@ -979,17 +1031,20 @@ class SpinelCommandHandler(SpinelCodec): # Generic output if isinstance(prop_value, basestring): prop_str = hexify_str(prop_value) + logger.debug("PROP_VALUE_%s [tid=%d]: %s = %s" % + (name, (tid & 0xF), prop_name, prop_str)) else: prop_str = str(prop_value) - logger.debug("PROP_VALUE_"+name+": "+prop_name+ - " = "+prop_str) + logger.debug("PROP_VALUE_%s [tid=%d]: %s = %s" % + (name, (tid & 0xF), prop_name, prop_str)) # Extend output for certain properties. if (prop_op == SPINEL_PROP_LAST_STATUS): logger.debug(SPINEL_LAST_STATUS_MAP[prop_value]) - elif ((prop_op == SPINEL_PROP_STREAM_NET) or + if DEBUG_LOG_PKT: + if ((prop_op == SPINEL_PROP_STREAM_NET) or (prop_op == SPINEL_PROP_STREAM_NET_INSECURE)): logger.debug("PROP_VALUE_"+name+": "+prop_name) pkt = IPv6(prop_value[2:]) @@ -998,6 +1053,7 @@ class SpinelCommandHandler(SpinelCodec): elif (prop_op == SPINEL_PROP_STREAM_DEBUG): logger.debug("DEBUG: "+prop_value) + global gWpanApi if gWpanApi: gWpanApi.queue_add(prop_op, prop_value, tid) else: @@ -1124,7 +1180,7 @@ class TunInterface(): self.ifconfig("up") #self.ifconfig("inet6 add fd00::1/64") - self.__start_reader() + self.__start_tun_thread() def __init_osx(self): logger.info("TUN: Starting osx "+self.ifname) @@ -1186,7 +1242,7 @@ class TunInterface(): if DEBUG_LOG_TUN: logger.debug("\nTUN: TX ("+str(len(packet))+") "+hexify_str(packet)) - def __run_read_thread(self): + def __run_tun_thread(self): while self.fd: try: r = select([self.fd],[],[])[0][0] @@ -1205,11 +1261,11 @@ class TunInterface(): os.close(self.fd) self.fd = None - def __start_reader(self): + def __start_tun_thread(self): """Start reader thread""" self._reader_alive = True # start serial->console thread - self.receiver_thread = threading.Thread(target=self.__run_read_thread) + self.receiver_thread = threading.Thread(target=self.__run_tun_thread) self.receiver_thread.setDaemon(True) self.receiver_thread.start() @@ -1231,10 +1287,10 @@ class WpanApi(SpinelCodec): self.rx_pkt = [] # Fire up threads + self.tid_filter = set() self.__queue_prop = defaultdict(Queue.Queue) self.queue_register() self.__start_reader() - self.tid_filter = SPINEL_HEADER_DEFAULT def __del__(self): self._reader_alive = False @@ -1249,7 +1305,7 @@ class WpanApi(SpinelCodec): def transact(self, cmd_id, payload = "", tid=SPINEL_HEADER_DEFAULT): pkt = self.encode(cmd_id, payload, tid) - if DEBUG_LOG_PKT: + if DEBUG_LOG_SERIAL: msg = "TX Pay: (%i) %s " % (len(pkt), hexify_bytes(pkt)) logger.debug(msg) @@ -1257,7 +1313,7 @@ class WpanApi(SpinelCodec): self.serial_tx(pkt) def parse_rx(self, pkt): - if DEBUG_LOG_PKT: + if DEBUG_LOG_SERIAL: msg = "RX Pay: (%i) %s " % (len(pkt), str(map(hexify_int,pkt))) logger.debug(msg) @@ -1310,11 +1366,10 @@ class WpanApi(SpinelCodec): self.tid = tid def queue_register(self, tid=SPINEL_HEADER_DEFAULT): + self.tid_filter.add(tid) return self.__queue_prop[tid] def queue_wait_prepare(self, prop_id, tid=SPINEL_HEADER_DEFAULT): - self.tid_filter = tid - self.prop_filter = prop_id self.queue_clear(tid) def queue_add(self, prop, value, tid): @@ -1328,7 +1383,7 @@ class WpanApi(SpinelCodec): pkt.plen, pkt.src, pkt.seq, pkt.hlim, timedelta) return - if (tid != self.tid_filter) or (prop != self.prop_filter): return + if (tid not in self.tid_filter): return item = self.PropertyItem(prop, value, tid) self.__queue_prop[tid].put_nowait(item) @@ -1339,11 +1394,12 @@ class WpanApi(SpinelCodec): def queue_wait_for_prop(self, prop, tid=SPINEL_HEADER_DEFAULT, timeout=TIMEOUT_PROP): try: item = self.__queue_prop[tid].get(True, timeout) + self.__queue_prop[tid].task_done() except: return None while (item): - if (item.tid == self.tid_filter) and (item.prop == prop): + if (item.tid in self.tid_filter) and (item.prop == prop): return item if (self.__queue_prop[tid].empty()): return None @@ -1373,16 +1429,27 @@ class WpanApi(SpinelCodec): self.transact(SPINEL_CMD_PROP_VALUE_SET, pay) - def prop_insert_async(self, prop_id, value, format='B', tid=SPINEL_HEADER_DEFAULT): + def prop_change_async(self, cmd, prop_id, value, format='B', + tid=SPINEL_HEADER_DEFAULT): pay = self.encode_i(prop_id) if format != None: pay += pack(format, value) - cmd = SPINEL_CMD_PROP_VALUE_INSERT self.transact(cmd, pay, tid) - def __prop_change_value(self, cmd, prop_id, value, format='B', tid=SPINEL_HEADER_DEFAULT): + def prop_insert_async(self, prop_id, value, format='B', + tid=SPINEL_HEADER_DEFAULT): + self.prop_change_async(SPINEL_CMD_PROP_VALUE_INSERT, prop_id, + value, format, tid) + + def prop_remove_async(self, prop_id, value, format='B', + tid=SPINEL_HEADER_DEFAULT): + self.prop_change_async(SPINEL_CMD_PROP_VALUE_REMOVE, prop_id, + value, format, tid) + + def __prop_change_value(self, cmd, prop_id, value, format='B', + tid=SPINEL_HEADER_DEFAULT): """ Utility routine to change a property value over Spinel. """ - self.queue_wait_prepare(prop_id) + self.queue_wait_prepare(prop_id, tid) pay = self.encode_i(prop_id) if format != None: @@ -1397,36 +1464,59 @@ class WpanApi(SpinelCodec): def prop_get_value(self, prop_id, tid=SPINEL_HEADER_DEFAULT): """ Blocking routine to get a property value over Spinel. """ - self.queue_wait_prepare(prop_id) + if DEBUG_LOG_PROP: + handler = SPINEL_PROP_DISPATCH[prop_id] + prop_name = handler.__name__ + print "PROP_VALUE_GET [tid=%d]: %s" % (tid&0xF, prop_name) + return self.__prop_change_value(SPINEL_CMD_PROP_VALUE_GET, prop_id, + None, None, tid) - pay = self.encode_i(prop_id) - self.transact(SPINEL_CMD_PROP_VALUE_GET, pay, tid) - - result = self.queue_wait_for_prop(prop_id, tid) - if result: - return result.value - else: - return None - - def prop_set_value(self, prop_id, value, format='B', tid=SPINEL_HEADER_DEFAULT): + def prop_set_value(self, prop_id, value, format='B', + tid=SPINEL_HEADER_DEFAULT): """ Blocking routine to set a property value over Spinel. """ + if DEBUG_LOG_PROP: + handler = SPINEL_PROP_DISPATCH[prop_id] + prop_name = handler.__name__ + print "PROP_VALUE_SET [tid=%d]: %s" % (tid&0xF, prop_name) return self.__prop_change_value(SPINEL_CMD_PROP_VALUE_SET, prop_id, - value, format) + value, format, tid) - def prop_insert_value(self, prop_id, value, format='B', tid=SPINEL_HEADER_DEFAULT): + def prop_insert_value(self, prop_id, value, format='B', + tid=SPINEL_HEADER_DEFAULT): """ Blocking routine to insert a property value over Spinel. """ + if DEBUG_LOG_PROP: + handler = SPINEL_PROP_DISPATCH[prop_id] + prop_name = handler.__name__ + print "PROP_VALUE_INSERT [tid=%d]: %s" % (tid&0xF, prop_name) return self.__prop_change_value(SPINEL_CMD_PROP_VALUE_INSERT, prop_id, - value, format) + value, format, tid) - def prop_remove_value(self, prop_id, value, format='B', tid=SPINEL_HEADER_DEFAULT): + def prop_remove_value(self, prop_id, value, format='B', + tid=SPINEL_HEADER_DEFAULT): """ Blocking routine to remove a property value over Spinel. """ + if DEBUG_LOG_PROP: + handler = SPINEL_PROP_DISPATCH[prop_id] + prop_name = handler.__name__ + print "PROP_VALUE_REMOVE [tid=%d]: %s" % (tid&0xF, prop_name) return self.__prop_change_value(SPINEL_CMD_PROP_VALUE_REMOVE, prop_id, - value, format) + value, format, tid) + + def get_ipaddrs(self, tid=SPINEL_HEADER_DEFAULT): + v = self.prop_get_value(SPINEL_PROP_IPV6_ADDRESS_TABLE, tid) + # TODO: clean up table parsing to be less hard-coded magic. + if v == None: return None + sz = 0x1B + addrs = [v[i:i+sz] for i in xrange(0, len(v), sz)] + ipaddrs = [] + for addr in addrs: + addr = addr[2:18] + ipaddrs.append(ipaddress.IPv6Address(addr)) + return ipaddrs #========================================= -class WpanDiagsCmd(Cmd, SpinelCodec): +class SpinelCliCmd(Cmd, SpinelCodec): def __init__(self, device, nodeid, *a, **kw): @@ -1444,7 +1534,7 @@ class WpanDiagsCmd(Cmd, SpinelCodec): self.use_rawinput = 0 self.prompt = "" - WpanDiagsCmd.command_names.sort() + SpinelCliCmd.command_names.sort() self.historyFileName = os.path.expanduser("~/.spinel-cli-history") @@ -1463,9 +1553,8 @@ class WpanDiagsCmd(Cmd, SpinelCodec): readline.parse_and_bind("bind ^I rl_complete") self.nodeid = kw.get('nodeid','1') - self.prop_set_value(SPINEL_PROP_THREAD_RLOC16_DEBUG_PASSTHRU, 1) self.prop_set_value(SPINEL_PROP_IPv6_ICMP_PING_OFFLOAD, 1) - + self.prop_set_value(SPINEL_PROP_THREAD_RLOC16_DEBUG_PASSTHRU, 1) command_names = [ @@ -1548,7 +1637,7 @@ class WpanDiagsCmd(Cmd, SpinelCodec): return cmd, arg, line def completenames(self, text, *ignored): - return [ name + ' ' for name in WpanDiagsCmd.command_names \ + return [ name + ' ' for name in SpinelCliCmd.command_names \ if name.startswith(text) \ or self.shortCommandName(name).startswith(text) ] @@ -1650,7 +1739,7 @@ class WpanDiagsCmd(Cmd, SpinelCodec): else: self.log("No help on %s\n" % (line)) else: - self.print_topics("\nAvailable commands (type help for more information):", WpanDiagsCmd.command_names, 15, 80) + self.print_topics("\nAvailable commands (type help for more information):", SpinelCliCmd.command_names, 15, 80) def do_v(self, line): @@ -1723,7 +1812,8 @@ class WpanDiagsCmd(Cmd, SpinelCodec): Enables detail logging of bytes over the wire to the radio modem. Usage: debug <1=enable | 0=disable> """ - global DEBUG_ENABLE, DEBUG_LOG_PKT, DEBUG_LOG_PROP + global DEBUG_ENABLE, DEBUG_LOG_PROP + global DEBUG_LOG_PKT, DEBUG_LOG_SERIAL global DEBUG_LOG_TX, DEBUG_LOG_RX, DEBUG_LOG_HDLC if line != None and line != "": @@ -1733,11 +1823,13 @@ class WpanDiagsCmd(Cmd, SpinelCodec): DEBUG_ENABLE = level if level >= 1: DEBUG_LOG_PROP = 1 if level >= 2: DEBUG_LOG_PKT = 1 - if level >= 3: DEBUG_LOG_HDLC = 1 + if level >= 3: DEBUG_LOG_SERIAL = 1 + if level >= 4: DEBUG_LOG_HDLC = 1 else: DEBUG_ENABLE = 0 DEBUG_LOG_PROP = 0 DEBUG_LOG_PKT = 0 + DEBUG_LOG_SERIAL = 0 DEBUG_LOG_HDLC = 0 print "DEBUG_ENABLE = "+str(DEBUG_ENABLE) @@ -1987,6 +2079,10 @@ class WpanDiagsCmd(Cmd, SpinelCodec): """ self.handle_property(line, SPINEL_PROP_NET_XPANID, 'D') + def complete_ifconfig(self, text, line, begidx, endidx): + _SUB_COMMANDS = ('up', 'down') + return [i for i in _SUB_COMMANDS if i.startswith(text)] + def do_ifconfig(self, line): """ ifconfig up @@ -2034,7 +2130,7 @@ class WpanDiagsCmd(Cmd, SpinelCodec): print("Done") def complete_ipaddr(self, text, line, begidx, endidx): - _SUB_COMMANDS = ('add', 'del') + _SUB_COMMANDS = ('add', 'remove') return [i for i in _SUB_COMMANDS if i.startswith(text)] def do_ipaddr(self, line): @@ -2077,13 +2173,9 @@ class WpanDiagsCmd(Cmd, SpinelCodec): arr = prefix.ip.packed if args[0] == "": - v = self.prop_get_value(SPINEL_PROP_IPV6_ADDRESS_TABLE) - # TODO: clean up table parsing to be less hard-coded magic. - sz = 0x1B - addrs = [v[i:i+sz] for i in xrange(0, len(v), sz)] + addrs = self.wpanApi.get_ipaddrs() for addr in addrs: - addr = addr[2:18] - print str(ipaddress.IPv6Address(addr)) + print str(addr) elif args[0] == "add": arr += pack('B', prefix_len) @@ -2096,6 +2188,10 @@ class WpanDiagsCmd(Cmd, SpinelCodec): self.wpanApi.tun_if.addr_add(ipaddr) elif args[0] == "remove": + arr += pack('B', prefix_len) + arr += pack('