Skip to content

Commit 2ffd5ba

Browse files
Peter Markirobertsipka
authored andcommitted
Create an ssh server to emulate ssh connection (#202)
JSRemoteTest-DCO-1.0-Signed-off-by: Peter Marki [email protected]
1 parent e2f9962 commit 2ffd5ba

File tree

12 files changed

+209
-17
lines changed

12 files changed

+209
-17
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ services:
88
- docker
99

1010
before_install:
11-
- if [[ "$RUN_DOCKER" == "yes" ]]; then docker pull iotjs/js_remote_test:0.4; fi
11+
- if [[ "$RUN_DOCKER" == "yes" ]]; then docker pull iotjs/js_remote_test:0.5; fi
1212

1313
install:
1414
- pip install pylint

install-deps.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ sudo apt-get install -y binutils-arm-linux-gnueabi
2929
sudo apt-get install -y mosquitto
3030
sudo apt-get install -y autotools-dev automake
3131

32-
sudo pip install paramiko pyserial pyrebase
32+
sudo pip install paramiko pyserial pyrebase twisted

jstest/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from jstest.builder.builder import Builder
1616
from jstest.common import console, paths, symbol_resolver, utils
17-
from jstest.emulate import pseudo_terminal
17+
from jstest.emulate import pseudo_terminal, twisted_server
1818
from jstest.flasher import flasher
1919
from jstest.testresult import TestResult
2020
from jstest.testrunner.testrunner import TestRunner

jstest/__main__.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
import jstest
2323
from jstest import Builder, TestResult, TestRunner
24-
from jstest import flasher, paths, pseudo_terminal, utils
24+
from jstest import flasher, paths, pseudo_terminal, twisted_server, utils
2525

2626

2727
def parse_options():
@@ -83,11 +83,18 @@ def parse_options():
8383
action='store_true', default=False,
8484
help='display less verbose output')
8585

86+
parser.add_argument('--emulate',
87+
default=False, action='store_true',
88+
help='emulate the connection')
89+
8690
group = parser.add_argument_group("Secure Shell communication")
8791

8892
group.add_argument('--username',
8993
metavar='USER',
90-
help='specify the username to login to the device.')
94+
help='specify the username to login to the device')
95+
96+
group.add_argument('--password',
97+
help='specify the password to login to the device')
9198

9299
group.add_argument('--ip',
93100
metavar='IPADDR',
@@ -111,9 +118,6 @@ def parse_options():
111118
type=int, default=115200,
112119
help='specify the baud rate (default: %(default)s)')
113120

114-
group.add_argument('--emulate',
115-
default=False, action='store_true',
116-
help='Emulate the serial connection.')
117121

118122
group = parser.add_argument_group("Telnet communication")
119123

@@ -144,7 +148,14 @@ def adjust_options(options):
144148
options.no_flash = True
145149

146150
if options.device in ['rpi2', 'artik530']:
147-
options.no_test = True
151+
options.username = 'js-remote-test'
152+
options.password = 'jerry'
153+
options.ip = '127.0.0.1'
154+
options.port = 2022
155+
options.remote_workdir = 'emulated_workdir'
156+
options.sshclient_no_exec_command = True
157+
twisted_server.run()
158+
atexit.register(twisted_server.stop)
148159

149160
else:
150161
options.device_id = pseudo_terminal.open_pseudo_terminal(options.device)

jstest/emulate/private.key

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEowIBAAKCAQEAtxV6wowrKyEL4duGt3miJRoPd1AUc3kr6F8MA3j5t8e2G4+1
3+
F9CKhG0ja1LFyTv1pTc1pPjxe4qwgLYyhh9pDaf3ElmEiOsWWu9Uk71lhxfDpd+m
4+
Z8Oa9vUiczWPvU5DyqCcjYnjqOX4sJuZe3Osm/Wo61JNWsfsf+84mUOTbB8dsB+r
5+
0SDLx/T5V+Tc9R+aAllDPJippje8kXhWxfBYfuLtdqYf7Ks92Ye2m4nH3OmdDuKG
6+
xy+jPQu5qOOWwSnka+m/TchRYlZgZydBXbGk7WsDgnZ5jFr0KCKzMeS0th2tECfE
7+
EGwV8QQIxgtBtnJo7UE6CLhpwaLm9eYz+IehDQIDAQABAoIBACnWPrc2UKcKZiy2
8+
fZJvuR3BVsdtBT9d8SqojZY9jtLwUCz8KeXHQeABEJb10zyo9vlKJHgOayBQEQVY
9+
nTMI+nikaVFII4EmN3WQO0OHhCvawlqlQOF1UXQdkEHDe5VGzTcAfMXbPKrb4nh5
10+
Fhmf1VHOUUt1Azg7d0/E2qorRwTttGeb+TmxlNEt3tb4fXXyXpEtB12Ts/F5dE9x
11+
FHmAdc38KroiLDnHt1mssGHa0kQ86fkRL8LzwgH00scF/UqQRF0Jk2LgUFOuucz+
12+
4PKXM8GTp1ezf8X2xkJxXXhD6CcEGGf4i/609sSfqMW4rOI+oWCLTRlArbUOEuec
13+
Y6F68AECgYEA2wCwG+bdJPJzHvlcpozGJOPBcsuXXYUK6n7rZdT/hsZVGlK3861h
14+
qWBJR5csA7FTiK48ELjfECXuy+cC6hk7dt0+ZtegVYxcORL/Wlrsem/tOYluVH/D
15+
hP4/w6QQHwldxaC9VBI+YVRkKQbjwV+hb++Tufjz+1gzf9SHJGdUR3kCgYEA1gNl
16+
J5zzilemOo9dMlTu/LeOpH/bzSKWS1s15l38C3T2+ZjFCdrYDm7IG5+Gcq6b2pDe
17+
pvFDXJC0pId4q0dtFCrgH+gk66F7BGSw9pNQi28keV5RCueOeSAu+LBEttsJNouS
18+
Bx3jfgVES2pX3fbkml9gI4oPzKw+CTihmos1PTUCgYATuPYjLSFRSHxRl8deQGM/
19+
wzsSqX8SLv8Sqydr3ki8zHOxkS17xmt7I262Ack28+s/7eD+6Ic+HwxoVH+QsYEH
20+
bVlHTbfkvGU5xpo0eue9BwNoIRZMic0D1xnK9qV1BhTpzVX/kj0H6t+ySiqMqZ4S
21+
RJMbNm5Sflj09CDPiHawkQKBgQCm3ZDSeSre1AZ1JHEbHGQWwZxDoLml0XD0IpL9
22+
Ioyx9inXZQlE7NCHOFNoTlLLOl+k5E3mRrkqBF4E5ufsTE29aszDtAKNgqPjQgtE
23+
xooHncoHo8V2xiMtSC23k+CP3mrpj5t1VxWYncypLKqEE8wcnX7dVvmIsUfRcsl2
24+
MVJ6fQKBgFndag1Je9yKdXAqVMEFZ1tsrc2QA4V3OxhsTCxy/tdkAuFCUAQfpgRR
25+
n7UHwYTy2c1/MywGKlKUXQn8KovOXfbwzmom18YXB7y1jbLAiGONKzryA79cR9gx
26+
xy+R6b/W4vK8E2rnDTTsCUFqsy2y+c5xZT0wCqK1SE7mvUbiR6Yp
27+
-----END RSA PRIVATE KEY-----

jstest/emulate/public.key

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC3FXrCjCsrIQvh24a3eaIlGg93UBRzeSvoXwwDePm3x7Ybj7UX0IqEbSNrUsXJO/WlNzWk+PF7irCAtjKGH2kNp/cSWYSI6xZa71STvWWHF8Ol36Znw5r29SJzNY+9TkPKoJyNieOo5fiwm5l7c6yb9ajrUk1ax+x/7ziZQ5NsHx2wH6vRIMvH9PlX5Nz1H5oCWUM8mKmmN7yReFbF8Fh+4u12ph/sqz3Zh7abicfc6Z0O4obHL6M9C7mo45bBKeRr6b9NyFFiVmBnJ0FdsaTtawOCdnmMWvQoIrMx5LS2Ha0QJ8QQbBXxBAjGC0G2cmjtQToIuGnBoub15jP4h6EN marpeter@marpeter-H61M-S2V-B3

jstest/emulate/ssh-passwords

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
js-remote-test:jerry

jstest/emulate/twisted_server.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Copyright 2018-present Samsung Electronics Co., Ltd. and other contributors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the 'License');
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an 'AS IS' BASIS
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import json
16+
import os
17+
from threading import Thread
18+
19+
from twisted.cred.portal import Portal
20+
from twisted.conch.ssh.factory import SSHFactory
21+
from twisted.internet import reactor
22+
from twisted.conch.ssh.keys import Key
23+
from twisted.cred.checkers import FilePasswordDB
24+
from twisted.conch.interfaces import IConchUser
25+
from twisted.conch.avatar import ConchUser
26+
from twisted.conch.ssh.channel import SSHChannel
27+
from twisted.internet.protocol import Protocol
28+
from twisted.conch.ssh.session import SSHSessionProcessProtocol, wrapProtocol
29+
30+
31+
class SimpleSession(SSHChannel):
32+
name = 'session'
33+
34+
# pylint: disable=invalid-name
35+
def dataReceived(self, data):
36+
cmd = str(data)
37+
38+
if 'iotjs_build_info' in cmd:
39+
result = {
40+
'builtins': {},
41+
'features': {},
42+
'stability': 'stable'
43+
}
44+
else:
45+
result = {
46+
'output': 'Hello from the emulated device',
47+
'memstat': {
48+
'heap-system': 'n/a',
49+
'heap-jerry': 'n/a',
50+
'stack': 'n/a'
51+
},
52+
'exitcode': int('fail' in cmd)
53+
}
54+
55+
self.write(json.dumps(result) + '\r\n')
56+
57+
# pylint: enable=invalid-name
58+
# pylint: disable=unused-argument
59+
def request_shell(self, data):
60+
protocol = Protocol()
61+
transport = SSHSessionProcessProtocol(self)
62+
protocol.makeConnection(transport)
63+
transport.makeConnection(wrapProtocol(protocol))
64+
self.client = transport
65+
return True
66+
67+
@staticmethod
68+
def request_pty_req(data):
69+
return True
70+
# pylint: enable=unused-argument
71+
72+
class SimpleRealm(object):
73+
@staticmethod
74+
# pylint: disable=invalid-name,unused-argument
75+
def requestAvatar(avatar_id, mind, *interfaces):
76+
user = ConchUser()
77+
user.channelLookup['session'] = SimpleSession
78+
return IConchUser, user, lambda: None
79+
# pylint: enable=invalid-name,unused-argument
80+
81+
def run():
82+
'''
83+
Run a threaded server.
84+
'''
85+
this_dir = os.path.dirname(__file__)
86+
with open(os.path.join(this_dir, 'private.key')) as private_blob_file:
87+
private_blob = private_blob_file.read()
88+
private_key = Key.fromString(data=private_blob)
89+
90+
with open(os.path.join(this_dir, 'public.key')) as public_blob_file:
91+
public_blob = public_blob_file.read()
92+
public_key = Key.fromString(data=public_blob)
93+
94+
factory = SSHFactory()
95+
factory.privateKeys = {'ssh-rsa': private_key}
96+
factory.publicKeys = {'ssh-rsa': public_key}
97+
98+
factory.portal = Portal(SimpleRealm())
99+
factory.portal.registerChecker(FilePasswordDB(os.path.join(this_dir, 'ssh-passwords')))
100+
101+
reactor.listenTCP(2022, factory, interface='127.0.0.1')
102+
thread = Thread(target=reactor.run, args=(False,))
103+
thread.daemon = True
104+
thread.start()
105+
106+
107+
def stop():
108+
'''
109+
Stop the threaded server.
110+
'''
111+
reactor.callFromThread(reactor.stop)

jstest/testrunner/devices/connections/sshcom.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,35 @@ class SSHConnection(object):
2424
'''
2525
def __init__(self, device_info):
2626
self.username = device_info['username']
27+
self.password = device_info['password']
2728
self.ip = device_info['ip']
2829
self.port = device_info['port']
2930
self.timeout = device_info['timeout']
31+
# FIXME: the exec_command method of paramiko.client.SSHClient cannot be used with the
32+
# emulated ssh server.
33+
self._no_exec_command = device_info['_no_exec_command']
3034

3135
# Note: add your SSH key to the known host file
3236
# to avoid getting password.
3337
self.ssh = paramiko.client.SSHClient()
34-
self.ssh.load_system_host_keys()
38+
if not self.password:
39+
self.ssh.load_system_host_keys()
3540
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
3641

3742
def open(self):
3843
'''
39-
Open the serial port.
44+
Open the ssh port.
4045
'''
41-
self.ssh.connect(hostname=self.ip, port=self.port, username=self.username)
46+
self.ssh.connect(hostname=self.ip, port=self.port, username=self.username,
47+
password=self.password, look_for_keys=bool(self.password))
48+
49+
if self._no_exec_command:
50+
self.chan = self.ssh.invoke_shell()
51+
self.chan_file = self.chan.makefile('r')
4252

4353
def close(self):
4454
'''
45-
Close the serial port.
55+
Close the ssh port.
4656
'''
4757
self.ssh.close()
4858

@@ -51,9 +61,31 @@ def exec_command(self, cmd):
5161
Send command over the serial port.
5262
'''
5363
try:
54-
_, stdout, _ = self.ssh.exec_command(cmd, timeout=self.timeout)
64+
if self._no_exec_command:
65+
self.send(cmd)
66+
response = self.receive()
67+
68+
else:
69+
_, stdout, _ = self.ssh.exec_command(cmd, timeout=self.timeout)
70+
71+
response = stdout.readline()
72+
73+
except socket.timeout:
74+
raise TimeoutException
5575

56-
return stdout.readline()
76+
return response
5777

78+
def send(self, cmd):
79+
'''
80+
Send data over the ssh channel.
81+
'''
82+
self.chan.send(cmd + '\n')
83+
84+
def receive(self):
85+
'''
86+
Receive data from the ssh channel.
87+
'''
88+
try:
89+
return self.chan_file.readline().strip('\r\n')
5890
except socket.timeout:
5991
raise TimeoutException

jstest/testrunner/devices/ssh_device.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ def __init__(self, env, os):
3333

3434
data = {
3535
'username': self.user,
36+
'password': env.options.password,
3637
'ip': self.ip,
3738
'port': self.port,
38-
'timeout': env.options.timeout
39+
'timeout': env.options.timeout,
40+
'_no_exec_command': hasattr(env.options, 'sshclient_no_exec_command')
3941
}
4042

4143
self.channel = SSHConnection(data)

0 commit comments

Comments
 (0)