1#!/usr/bin/env python 2# 3# Copyright (c) 2022, The OpenThread Authors. 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are met: 8# 1. Redistributions of source code must retain the above copyright 9# notice, this list of conditions and the following disclaimer. 10# 2. Redistributions in binary form must reproduce the above copyright 11# notice, this list of conditions and the following disclaimer in the 12# documentation and/or other materials provided with the distribution. 13# 3. Neither the name of the copyright holder nor the 14# names of its contributors may be used to endorse or promote products 15# derived from this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27# POSSIBILITY OF SUCH DAMAGE. 28# 29""" 30>> Thread Host Controller Interface 31>> Device : OpenThread_BR_Sim THCI 32>> Class : OpenThread_BR_Sim 33""" 34 35import ipaddress 36import logging 37import paramiko 38import pipes 39import sys 40import time 41 42from THCI.IThci import IThci 43from THCI.OpenThread import watched 44from THCI.OpenThread_BR import OpenThread_BR 45from simulation.config import load_config 46 47logging.getLogger('paramiko').setLevel(logging.WARNING) 48 49config = load_config() 50 51 52class SSHHandle(object): 53 # Unit: second 54 KEEPALIVE_INTERVAL = 30 55 56 def __init__(self, ip, port, username, password, docker_name): 57 self.ip = ip 58 self.port = int(port) 59 self.username = username 60 self.password = password 61 self.docker_name = docker_name 62 self.__handle = None 63 64 self.__connect() 65 66 def __connect(self): 67 self.close() 68 69 self.__handle = paramiko.SSHClient() 70 self.__handle.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 71 try: 72 self.__handle.connect(self.ip, port=self.port, username=self.username, password=self.password) 73 except paramiko.AuthenticationException: 74 if not self.password: 75 self.__handle.get_transport().auth_none(self.username) 76 else: 77 raise Exception('Password error') 78 79 # Avoid SSH disconnection after idle for a long time 80 self.__handle.get_transport().set_keepalive(self.KEEPALIVE_INTERVAL) 81 82 def close(self): 83 if self.__handle is not None: 84 self.__handle.close() 85 self.__handle = None 86 87 def bash(self, cmd, timeout): 88 # It is necessary to quote the command when there is stdin/stdout redirection 89 cmd = pipes.quote(cmd) 90 91 retry = 3 92 for i in range(retry): 93 try: 94 stdin, stdout, stderr = self.__handle.exec_command('docker exec %s bash -c %s' % 95 (self.docker_name, cmd), 96 timeout=timeout) 97 stdout._set_mode('rb') 98 99 sys.stderr.write(stderr.read()) 100 output = [r.rstrip() for r in stdout.readlines()] 101 return output 102 103 except paramiko.SSHException: 104 if i < retry - 1: 105 print('SSH connection is lost, try reconnect after 1 second.') 106 time.sleep(1) 107 self.__connect() 108 else: 109 raise ConnectionError('SSH connection is lost') 110 111 112class OpenThread_BR_Sim(OpenThread_BR): 113 114 def _getHandle(self): 115 self.log('SSH connecting ...') 116 return SSHHandle(self.ssh_ip, self.telnetPort, self.telnetUsername, self.telnetPassword, self.docker_name) 117 118 @watched 119 def _parseConnectionParams(self, params): 120 discovery_add = params.get('SerialPort') 121 if '@' not in discovery_add: 122 raise ValueError('%r in the field `add` is invalid' % discovery_add) 123 124 self.docker_name, self.ssh_ip = discovery_add.split('@') 125 self.tag, self.node_id = self.docker_name.split('_') 126 self.node_id = int(self.node_id) 127 # Let it crash if it is an invalid IP address 128 ipaddress.ip_address(self.ssh_ip) 129 130 self.connectType = 'ip' 131 self.telnetIp = self.port = discovery_add 132 133 global config 134 ssh = config['ssh'] 135 self.telnetPort = ssh['port'] 136 self.telnetUsername = ssh['username'] 137 self.telnetPassword = ssh['password'] 138 139 self.extraParams = { 140 'cmd-start-otbr-agent': 'service otbr-agent start', 141 'cmd-stop-otbr-agent': 'service otbr-agent stop', 142 'cmd-restart-otbr-agent': 'service otbr-agent restart', 143 'cmd-restart-radvd': 'service radvd stop; service radvd start', 144 } 145 146 147assert issubclass(OpenThread_BR_Sim, IThci) 148