| #!/usr/bin/env python |
| # |
| # Copyright (c) 2022, The OpenThread Authors. |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are met: |
| # 1. Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # 2. Redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution. |
| # 3. Neither the name of the copyright holder nor the |
| # names of its contributors may be used to endorse or promote products |
| # derived from this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| # POSSIBILITY OF SUCH DAMAGE. |
| # |
| """ |
| >> Thread Host Controller Interface |
| >> Device : OpenThread_BR_Sim THCI |
| >> Class : OpenThread_BR_Sim |
| """ |
| |
| import ipaddress |
| import logging |
| import paramiko |
| import pipes |
| import sys |
| import time |
| |
| from THCI.IThci import IThci |
| from THCI.OpenThread import watched |
| from THCI.OpenThread_BR import OpenThread_BR |
| from simulation.config import load_config |
| |
| logging.getLogger('paramiko').setLevel(logging.WARNING) |
| |
| config = load_config() |
| |
| |
| class SSHHandle(object): |
| # Unit: second |
| KEEPALIVE_INTERVAL = 30 |
| |
| def __init__(self, ip, port, username, password, docker_name): |
| self.ip = ip |
| self.port = int(port) |
| self.username = username |
| self.password = password |
| self.docker_name = docker_name |
| self.__handle = None |
| |
| self.__connect() |
| |
| def __connect(self): |
| self.close() |
| |
| self.__handle = paramiko.SSHClient() |
| self.__handle.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
| try: |
| self.__handle.connect(self.ip, port=self.port, username=self.username, password=self.password) |
| except paramiko.AuthenticationException: |
| if not self.password: |
| self.__handle.get_transport().auth_none(self.username) |
| else: |
| raise Exception('Password error') |
| |
| # Avoid SSH disconnection after idle for a long time |
| self.__handle.get_transport().set_keepalive(self.KEEPALIVE_INTERVAL) |
| |
| def close(self): |
| if self.__handle is not None: |
| self.__handle.close() |
| self.__handle = None |
| |
| def bash(self, cmd, timeout): |
| # It is necessary to quote the command when there is stdin/stdout redirection |
| cmd = pipes.quote(cmd) |
| |
| retry = 3 |
| for i in range(retry): |
| try: |
| stdin, stdout, stderr = self.__handle.exec_command('docker exec %s bash -c %s' % |
| (self.docker_name, cmd), |
| timeout=timeout) |
| stdout._set_mode('rb') |
| |
| sys.stderr.write(stderr.read()) |
| output = [r.rstrip() for r in stdout.readlines()] |
| return output |
| |
| except paramiko.SSHException: |
| if i < retry - 1: |
| print('SSH connection is lost, try reconnect after 1 second.') |
| time.sleep(1) |
| self.__connect() |
| else: |
| raise ConnectionError('SSH connection is lost') |
| |
| |
| class OpenThread_BR_Sim(OpenThread_BR): |
| |
| def _getHandle(self): |
| self.log('SSH connecting ...') |
| return SSHHandle(self.ssh_ip, self.telnetPort, self.telnetUsername, self.telnetPassword, self.docker_name) |
| |
| @watched |
| def _parseConnectionParams(self, params): |
| discovery_add = params.get('SerialPort') |
| if '@' not in discovery_add: |
| raise ValueError('%r in the field `add` is invalid' % discovery_add) |
| |
| self.docker_name, self.ssh_ip = discovery_add.split('@') |
| self.tag, self.node_id = self.docker_name.split('_') |
| self.node_id = int(self.node_id) |
| # Let it crash if it is an invalid IP address |
| ipaddress.ip_address(self.ssh_ip) |
| |
| self.connectType = 'ip' |
| self.telnetIp = self.port = discovery_add |
| |
| global config |
| ssh = config['ssh'] |
| self.telnetPort = ssh['port'] |
| self.telnetUsername = ssh['username'] |
| self.telnetPassword = ssh['password'] |
| |
| self.extraParams = { |
| 'cmd-start-otbr-agent': 'service otbr-agent start', |
| 'cmd-stop-otbr-agent': 'service otbr-agent stop', |
| 'cmd-restart-otbr-agent': 'service otbr-agent restart', |
| 'cmd-restart-radvd': 'service radvd stop; service radvd start', |
| } |
| |
| |
| assert issubclass(OpenThread_BR_Sim, IThci) |