summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorISHIDA Wataru <ishida.wataru@lab.ntt.co.jp>2014-05-04 14:49:59 +0000
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2014-05-10 01:14:56 +0900
commit7c1410ccf607957a8b4a5ad62e8fbeea2d29872a (patch)
tree03d2e49ced4e22b133dfe0a58350ef361aa9b14f
parenteb488e3cb3f24ee9947fbb682fb48030f62e6c47 (diff)
bgp: add ssh client
add ssh client which has basic bash keybinds and tab complement. to use, you have to create ssh key at first then specify the location of the key in ryu configuration files. configuration file will be like this. > ryu.conf > == > [DEFAULT] > cli_ssh_hostkey=/home/user/.ssh/id_rsa after this, run operator/ssh.py with application.py $ ryu-manager --config-file=ryu.conf application.py operator/ssh.py you can login by $ ssh ryu@localhost -p 4990 Signed-off-by: ISHIDA Wataru <ishida.wataru@lab.ntt.co.jp> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r--ryu/services/protocols/bgp/operator/command.py2
-rw-r--r--ryu/services/protocols/bgp/operator/commands/set.py8
-rw-r--r--ryu/services/protocols/bgp/operator/commands/show/__init__.py11
-rw-r--r--ryu/services/protocols/bgp/operator/commands/show/memory.py2
-rw-r--r--ryu/services/protocols/bgp/operator/commands/show/rib.py10
-rw-r--r--ryu/services/protocols/bgp/operator/commands/show/route_formatter_mixin.py11
-rw-r--r--ryu/services/protocols/bgp/operator/internal_api.py30
-rw-r--r--ryu/services/protocols/bgp/operator/ssh.py472
8 files changed, 529 insertions, 17 deletions
diff --git a/ryu/services/protocols/bgp/operator/command.py b/ryu/services/protocols/bgp/operator/command.py
index bd384f0c..64449eff 100644
--- a/ryu/services/protocols/bgp/operator/command.py
+++ b/ryu/services/protocols/bgp/operator/command.py
@@ -42,7 +42,7 @@ class Command(object):
help_msg = ''
param_help_msg = None
command = ''
- cli_resp_line_template = '{0}: {1}\n\n'
+ cli_resp_line_template = '{0}: {1}\n'
def __init__(self, api=None, parent=None,
help_formatter=default_help_formatter,
diff --git a/ryu/services/protocols/bgp/operator/commands/set.py b/ryu/services/protocols/bgp/operator/commands/set.py
index b28a80ac..c6c9c5dc 100644
--- a/ryu/services/protocols/bgp/operator/commands/set.py
+++ b/ryu/services/protocols/bgp/operator/commands/set.py
@@ -3,6 +3,7 @@ import logging
from ryu.services.protocols.bgp.operator.command import Command
from ryu.services.protocols.bgp.operator.command import CommandsResponse
from ryu.services.protocols.bgp.operator.command import STATUS_OK
+from ryu.services.protocols.bgp.operator.command import STATUS_ERROR
from ryu.services.protocols.bgp.operator.commands.responses import \
WrongParamResp
@@ -19,6 +20,9 @@ class LoggingCmd(Command):
'level': self.Level
}
+ def action(self, params):
+ return CommandsResponse(STATUS_ERROR, 'Command incomplete')
+
class On(Command):
command = 'on'
help_msg = 'turn-on the logging at the current level'
@@ -56,10 +60,10 @@ class LoggingCmd(Command):
class SetCmd(Command):
- help_msg = 'allows to set runtime settings'
+ help_msg = 'set runtime settings'
command = 'set'
subcommands = {'logging': LoggingCmd}
def action(self, params):
- return CommandsResponse(STATUS_OK, True)
+ return CommandsResponse(STATUS_ERROR, 'Command incomplete')
diff --git a/ryu/services/protocols/bgp/operator/commands/show/__init__.py b/ryu/services/protocols/bgp/operator/commands/show/__init__.py
index 388a1e7a..f0c30726 100644
--- a/ryu/services/protocols/bgp/operator/commands/show/__init__.py
+++ b/ryu/services/protocols/bgp/operator/commands/show/__init__.py
@@ -1,6 +1,7 @@
from ryu.services.protocols.bgp.operator.command import Command
from ryu.services.protocols.bgp.operator.command import CommandsResponse
from ryu.services.protocols.bgp.operator.command import STATUS_OK
+from ryu.services.protocols.bgp.operator.command import STATUS_ERROR
from ryu.services.protocols.bgp.operator.commands.show import count
from ryu.services.protocols.bgp.operator.commands.show import importmap
from ryu.services.protocols.bgp.operator.commands.show import memory
@@ -26,7 +27,7 @@ class ShowCmd(Command):
}
def action(self, params):
- return CommandsResponse(STATUS_OK, None)
+ return CommandsResponse(STATUS_ERROR, 'Command incomplete')
class Count(count.Count):
pass
@@ -51,6 +52,10 @@ class ShowCmd(Command):
help_msg = 'shows if logging is on/off and current logging level.'
def action(self, params):
- ret = {'logging': self.api.check_logging(),
- 'level': self.api.check_logging_level()}
+ if self.api.check_logging():
+ ret = {'logging': self.api.check_logging(),
+ 'level': self.api.check_logging_level()}
+ else:
+ ret = {'logging': self.api.check_logging(),
+ 'level': None}
return CommandsResponse(STATUS_OK, ret)
diff --git a/ryu/services/protocols/bgp/operator/commands/show/memory.py b/ryu/services/protocols/bgp/operator/commands/show/memory.py
index c519adff..97e75738 100644
--- a/ryu/services/protocols/bgp/operator/commands/show/memory.py
+++ b/ryu/services/protocols/bgp/operator/commands/show/memory.py
@@ -48,7 +48,7 @@ class Memory(Command):
'summary': []}
for class_name, s in size.items():
- # Calculate size in MB
+ # Calculate size in MB
size_mb = s / 1000000
# We are only interested in class which take-up more than a MB
if size_mb > 0:
diff --git a/ryu/services/protocols/bgp/operator/commands/show/rib.py b/ryu/services/protocols/bgp/operator/commands/show/rib.py
index 24740247..34d4a18a 100644
--- a/ryu/services/protocols/bgp/operator/commands/show/rib.py
+++ b/ryu/services/protocols/bgp/operator/commands/show/rib.py
@@ -5,6 +5,7 @@ from ryu.services.protocols.bgp.operator.command import CommandsResponse
from ryu.services.protocols.bgp.operator.command import STATUS_ERROR
from ryu.services.protocols.bgp.operator.command import STATUS_OK
+from ryu.services.protocols.bgp.base import ActivityException
from ryu.services.protocols.bgp.operator.commands.responses import \
WrongParamResp
@@ -50,9 +51,12 @@ class Rib(RibBase):
if len(params) != 0:
return WrongParamResp()
ret = {}
- for family in self.supported_families:
- ret[family] = self.api.get_single_rib_routes(family)
- return CommandsResponse(STATUS_OK, ret)
+ try:
+ for family in self.supported_families:
+ ret[family] = self.api.get_single_rib_routes(family)
+ return CommandsResponse(STATUS_OK, ret)
+ except ActivityException, e:
+ return CommandsResponse(STATUS_ERROR, e)
@classmethod
def cli_resp_formatter(cls, resp):
diff --git a/ryu/services/protocols/bgp/operator/commands/show/route_formatter_mixin.py b/ryu/services/protocols/bgp/operator/commands/show/route_formatter_mixin.py
index ff69e069..2f58f681 100644
--- a/ryu/services/protocols/bgp/operator/commands/show/route_formatter_mixin.py
+++ b/ryu/services/protocols/bgp/operator/commands/show/route_formatter_mixin.py
@@ -17,6 +17,10 @@ class RouteFormatterMixin(object):
def _append_path_info(buff, path, is_best, show_prefix):
aspath = path.get('aspath')
+ origin = path.get('origin')
+ if origin:
+ aspath.append(origin)
+
bpr = path.get('bpr')
next_hop = path.get('nexthop')
med = path.get('metric')
@@ -31,9 +35,10 @@ class RouteFormatterMixin(object):
prefix = path.get('prefix')
# Append path info to String buffer.
- buff.write(' {0:<3s} {1:<32s} {2:<20s} {3:<20s} {4:<10s} {5:<}\n'.
- format(path_status, prefix, next_hop, bpr, str(med),
- ', '.join(map(str, aspath))))
+ buff.write(
+ ' {0:<3s} {1:<32s} {2:<20s} {3:<20s} {4:<10s} {5:<}\n'.
+ format(path_status, prefix, next_hop, bpr, str(med),
+ ' '.join(map(str, aspath))))
for dist in dest_list:
for idx, path in enumerate(dist.get('paths')):
diff --git a/ryu/services/protocols/bgp/operator/internal_api.py b/ryu/services/protocols/bgp/operator/internal_api.py
index b83ce0d3..c98ab69f 100644
--- a/ryu/services/protocols/bgp/operator/internal_api.py
+++ b/ryu/services/protocols/bgp/operator/internal_api.py
@@ -7,6 +7,11 @@ from ryu.lib.packet.bgp import RF_IPv6_UC
from ryu.lib.packet.bgp import RF_IPv4_VPN
from ryu.lib.packet.bgp import RF_IPv6_VPN
from ryu.lib.packet.bgp import RF_RTC_UC
+from ryu.lib.packet.bgp import BGP_ATTR_TYPE_ORIGIN
+from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH
+from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MULTI_EXIT_DISC
+from ryu.lib.packet.bgp import BGP_ATTR_ORIGIN_IGP
+from ryu.lib.packet.bgp import BGP_ATTR_ORIGIN_EGP
from ryu.services.protocols.bgp.base import add_bgp_error_metadata
from ryu.services.protocols.bgp.base import BGPSException
@@ -94,11 +99,27 @@ class InternalApi(object):
'prefix': dst.nlri.formatted_nlri_str}
def _path_to_dict(dst, path):
- aspath = path.get_pattr(BGP_ATTR_TYPE_AS_PATH).path_seg_list
- if aspath is None or len(aspath) == 0:
+
+ path_seg_list = path.get_pattr(BGP_ATTR_TYPE_AS_PATH).path_seg_list
+
+ if type(path_seg_list) == list:
+ aspath = []
+ for as_path_seg in path_seg_list:
+ for as_num in as_path_seg:
+ aspath.append(as_num)
+ else:
aspath = ''
- nexthop = path.nexthop
+ origin = path.get_pattr(BGP_ATTR_TYPE_ORIGIN).value
+
+ if origin == BGP_ATTR_ORIGIN_IGP:
+ origin = 'i'
+ elif origin == BGP_ATTR_ORIGIN_EGP:
+ origin = 'e'
+ else:
+ origin = None
+
+ nexthop = path.nexthop.value
# Get the MED path attribute
med = path.get_pattr(BGP_ATTR_TYPE_MULTI_EXIT_DISC)
med = med.value if med else ''
@@ -109,7 +130,8 @@ class InternalApi(object):
'prefix': path.nlri.formatted_nlri_str,
'nexthop': nexthop,
'metric': med,
- 'aspath': aspath}
+ 'aspath': aspath,
+ 'origin': origin}
for path in dst.known_path_list:
ret['paths'].append(_path_to_dict(dst, path))
diff --git a/ryu/services/protocols/bgp/operator/ssh.py b/ryu/services/protocols/bgp/operator/ssh.py
new file mode 100644
index 00000000..84858d77
--- /dev/null
+++ b/ryu/services/protocols/bgp/operator/ssh.py
@@ -0,0 +1,472 @@
+# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
+# Copyright (C) 2013 YAMAMOTO Takashi <yamamoto at valinux co jp>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# a management cli application.
+
+import logging
+import paramiko
+import sys
+from copy import copy
+from oslo.config import cfg
+
+from ryu.lib import hub
+from ryu import version
+from ryu.base import app_manager
+from ryu.services.protocols.bgp.operator.command import Command
+from ryu.services.protocols.bgp.operator.command import CommandsResponse
+from ryu.services.protocols.bgp.operator.commands.root import RootCmd
+from ryu.services.protocols.bgp.operator.internal_api import InternalApi
+from ryu.services.protocols.bgp.operator.command import STATUS_OK
+
+LOG = logging.getLogger('bgpspeaker.cli')
+
+CONF = cfg.CONF
+CONF.register_opts([
+ cfg.ListOpt('cli-transports', default=[], help='cli transports to enable'),
+ cfg.StrOpt('cli-ssh-host', default='localhost',
+ help='cli ssh listen host'),
+ cfg.IntOpt('cli-ssh-port', default=4990, help='cli ssh listen port'),
+ cfg.StrOpt('cli-ssh-hostkey', help='cli ssh host key file'),
+ cfg.StrOpt('cli-ssh-username', default='ryu', help='cli ssh username'),
+ cfg.StrOpt('cli-ssh-password', default='ryu', help='cli ssh password')
+])
+
+
+class SshServer(paramiko.ServerInterface):
+ TERM = "ansi"
+ PROMPT = "bgpd> "
+ WELCOME = """
+Hello, this is Ryu BGP speaker (version %s).
+""" % version
+
+ class HelpCmd(Command):
+ help_msg = 'show this help'
+ command = 'help'
+
+ def action(self, params):
+ return self.parent_cmd.question_mark()[0]
+
+ class QuitCmd(Command):
+ help_msg = 'exit this session'
+ command = 'quit'
+
+ def action(self, params):
+ self.api.sshserver.end_session()
+ return CommandsResponse(STATUS_OK, True)
+
+ def __init__(self, sock, addr):
+ super(SshServer, self).__init__()
+
+ # tweak InternalApi and RootCmd for non-bgp related commands
+ self.api = InternalApi(log_handler=logging.StreamHandler(sys.stderr))
+ setattr(self.api, 'sshserver', self)
+ self.root = RootCmd(self.api)
+ self.root.subcommands['help'] = self.HelpCmd
+ self.root.subcommands['quit'] = self.QuitCmd
+
+ transport = paramiko.Transport(sock)
+ transport.load_server_moduli()
+ host_key = paramiko.RSAKey.from_private_key_file(CONF.cli_ssh_hostkey)
+ transport.add_server_key(host_key)
+ self.transport = transport
+ transport.start_server(server=self)
+
+ def check_auth_none(self, username):
+ return paramiko.AUTH_SUCCESSFUL
+
+ def check_auth_password(self, username, password):
+ if username == CONF.cli_ssh_username and \
+ password == CONF.cli_ssh_password:
+ return paramiko.AUTH_SUCCESSFUL
+ return paramiko.AUTH_FAILED
+
+ def check_channel_request(self, kind, chanid):
+ if kind == 'session':
+ return paramiko.OPEN_SUCCEEDED
+ return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
+
+ def check_channel_shell_request(self, chan):
+ hub.spawn(self._handle_shell_request)
+ return True
+
+ def check_channel_pty_request(self, chan, term, width, height,
+ pixelwidth, pixelheight, modes):
+ LOG.debug("termtype: %s" % (term, ))
+ self.TERM = term
+ return True
+
+ def check_channel_window_change_request(self, chan, width, height, pwidth,
+ pheight):
+ LOG.info("channel window change")
+ return True
+
+ def _is_echoable(self, c):
+ return not (c < chr(0x20) or c == chr(0x7F))
+
+ def _is_enter(self, c):
+ return c == chr(0x0d)
+
+ def _is_eof(self, c):
+ return c == chr(0x03)
+
+ def _is_esc(self, c):
+ return c == chr(0x1b)
+
+ def _is_hist(self, c):
+ return c == chr(0x10) or c == chr(0x0e)
+
+ def _is_del(self, c):
+ return c == chr(0x04) or c == chr(0x08) or c == chr(0x15) \
+ or c == chr(0x17) or c == chr(0x0c) or c == chr(0x7f)
+
+ def _is_curmov(self, c):
+ return c == chr(0x01) or c == chr(0x02) or c == chr(0x05) \
+ or c == chr(0x06)
+
+ def _is_cmpl(self, c):
+ return c == chr(0x09)
+
+ def _handle_csi_seq(self):
+ c = self.chan.recv(1)
+ if c == 'A':
+ self._lookup_hist_up()
+ elif c == 'B':
+ self._lookup_hist_down()
+ elif c == 'C':
+ self._movcursor(self.curpos+1)
+ elif c == 'D':
+ self._movcursor(self.curpos-1)
+ else:
+ LOG.error("unknown CSI sequence. do nothing: %c" % c)
+
+ def _handle_esc_seq(self):
+ c = self.chan.recv(1)
+ if c == '[':
+ self._handle_csi_seq()
+ else:
+ LOG.error("non CSI sequence. do nothing")
+
+ def _send_csi_seq(self, cmd):
+ self.chan.send('\x1b[' + cmd)
+
+ def _movcursor(self, curpos):
+ if self.prompted and curpos < len(self.PROMPT):
+ self.curpos = len(self.PROMPT)
+ elif self.prompted and curpos > (len(self.PROMPT) + len(self.buf)):
+ self.curpos = len(self.PROMPT) + len(self.buf)
+ else:
+ self._send_csi_seq('%dG' % (curpos + 1))
+ self.curpos = curpos
+
+ def _clearscreen(self, prompt=None):
+ if not prompt and self.prompted:
+ prompt = self.PROMPT
+ # clear screen
+ self._send_csi_seq('2J')
+ # move cursor to the top
+ self._send_csi_seq('d')
+ # redraw prompt and buf
+ self._refreshline(prompt=prompt)
+
+ def _clearline(self, prompt=None):
+ if not prompt and self.prompted:
+ prompt = self.PROMPT
+ self.prompted = False
+ self._movcursor(0)
+ self._send_csi_seq('2K')
+ if prompt:
+ self.prompted = True
+ self.chan.send(prompt)
+ self._movcursor(len(prompt))
+ self.buf = []
+
+ def _refreshline(self, prompt=None):
+ if not prompt and self.prompted:
+ prompt = self.PROMPT
+ buf = copy(self.buf)
+ curpos = copy(self.curpos)
+ self._clearline(prompt=prompt)
+ self.chan.send(''.join(buf))
+ self.buf = buf
+ self.curpos = curpos
+ self._movcursor(curpos)
+
+ def _refreshnewline(self, prompt=None):
+ if not prompt and self.prompted:
+ prompt = self.PROMPT
+ buf = copy(self.buf)
+ curpos = copy(self.curpos)
+ self._startnewline(prompt)
+ self.chan.send(''.join(buf))
+ self.buf = buf
+ self.curpos = curpos
+ self._movcursor(curpos)
+
+ def _startnewline(self, prompt=None, buf=''):
+ if not prompt and self.prompted:
+ prompt = self.PROMPT
+ if type(buf) == str:
+ buf = list(buf)
+ if self.chan:
+ self.buf = buf
+ if prompt:
+ self.chan.send('\n\r' + prompt + ''.join(buf))
+ self.curpos = len(prompt) + len(buf)
+ self.prompted = True
+ else:
+ self.chan.send('\n\r' + ''.join(buf))
+ self.curpos = len(buf)
+ self.prompted = False
+
+ def _lookup_hist_up(self):
+ if len(self.history) == 0:
+ return
+ self.buf = self.history[self.histindex]
+ self.curpos = self.promptlen + len(self.buf)
+ self._refreshline()
+ if self.histindex + 1 < len(self.history):
+ self.histindex += 1
+
+ def _lookup_hist_down(self):
+ if self.histindex > 0:
+ self.histindex -= 1
+ self.buf = self.history[self.histindex]
+ self.curpos = self.promptlen + len(self.buf)
+ self._refreshline()
+ else:
+ self._clearline()
+
+ def _do_cmpl(self, buf, is_exec=False):
+ cmpleter = self.root
+ is_spaced = buf[-1] == ' ' if len(buf) > 0 else False
+ cmds = [tkn.strip() for tkn in ''.join(buf).split()]
+ ret = []
+
+ for i, cmd in enumerate(cmds):
+ subcmds = cmpleter.subcommands
+ matches = [x for x in subcmds.keys() if x.startswith(cmd)]
+
+ if len(matches) == 1:
+ cmpled_cmd = matches[0]
+ cmpleter = subcmds[cmpled_cmd](self.api)
+
+ if is_exec:
+ ret.append(cmpled_cmd)
+ continue
+
+ if (i+1) == len(cmds):
+ if is_spaced:
+ result, cmd = cmpleter('?')
+ result = result.value.replace('\n', '\n\r').rstrip()
+ self.prompted = False
+ buf = copy(buf)
+ self._startnewline(buf=result)
+ self.prompted = True
+ self._startnewline(buf=buf)
+ else:
+ self.buf = buf[:(-1 * len(cmd))] + \
+ list(cmpled_cmd + ' ')
+ self.curpos += len(cmpled_cmd) - len(cmd) + 1
+ self._refreshline()
+ else:
+ self.prompted = False
+ buf = copy(self.buf)
+ if len(matches) == 0:
+ if cmpleter.param_help_msg:
+ self.prompted = True
+ ret.append(cmd)
+ continue
+ else:
+ self._startnewline(buf='Error: Not implemented')
+ else:
+ if (i+1) < len(cmds):
+ self._startnewline(buf='Error: Ambiguous command')
+ else:
+ self._startnewline(buf=', '.join(matches))
+ ret = False
+ self.prompted = True
+ if not is_exec:
+ self._startnewline(buf=buf)
+ break
+
+ return ret
+
+ def _execute_cmd(self, cmds):
+ result, cmd = self.root(cmds)
+ LOG.debug("result: %s" % str(result))
+ self.prompted = False
+ self._startnewline()
+ output = result.value.replace('\n', '\n\r').rstrip()
+ self.chan.send(output)
+ self.prompted = True
+ return result.status
+
+ def end_session(self):
+ self._startnewline(prompt=False, buf='bye.\n\r')
+ self.chan.close()
+
+ def _handle_shell_request(self):
+ LOG.info("session start")
+ chan = self.transport.accept(20)
+ if not chan:
+ LOG.info("transport.accept timed out")
+ return
+
+ self.chan = chan
+ self.buf = []
+ self.curpos = 0
+ self.history = []
+ self.histindex = 0
+ self.prompted = True
+ self.chan.send(self.WELCOME)
+ self._startnewline()
+
+ while True:
+ c = self.chan.recv(1)
+
+ if len(c) == 0:
+ break
+
+ LOG.debug("ord:%d, hex:0x%x" % (ord(c), ord(c)))
+ self.promptlen = len(self.PROMPT) if self.prompted else 0
+ if c == '?':
+ cmpleter = self.root
+ cmds = [tkn.strip() for tkn in ''.join(self.buf).split()]
+
+ for i, cmd in enumerate(cmds):
+ subcmds = cmpleter.subcommands
+ matches = [x for x in subcmds.keys() if x.startswith(cmd)]
+ if len(matches) == 1:
+ cmpled_cmd = matches[0]
+ cmpleter = subcmds[cmpled_cmd](self.api)
+
+ result, cmd = cmpleter('?')
+ result = result.value.replace('\n', '\n\r').rstrip()
+ self.prompted = False
+ buf = copy(self.buf)
+ self._startnewline(buf=result)
+ self.prompted = True
+ self._startnewline(buf=buf)
+ elif self._is_echoable(c):
+ self.buf.insert(self.curpos - self.promptlen, c)
+ self.curpos += 1
+ self._refreshline()
+ elif self._is_esc(c):
+ self._handle_esc_seq()
+ elif self._is_eof(c):
+ self.end_session()
+ elif self._is_curmov(c):
+ # <C-a>
+ if c == chr(0x01):
+ self._movcursor(self.promptlen)
+ # <C-b>
+ elif c == chr(0x02):
+ self._movcursor(self.curpos-1)
+ # <C-e>
+ elif c == chr(0x05):
+ self._movcursor(self.promptlen+len(self.buf))
+ # <C-f>
+ elif c == chr(0x06):
+ self._movcursor(self.curpos+1)
+ else:
+ LOG.error("unknown cursor move cmd.")
+ continue
+ elif self._is_hist(c):
+ # <C-p>
+ if c == chr(0x10):
+ self._lookup_hist_up()
+ # <C-n>
+ elif c == chr(0x0e):
+ self._lookup_hist_down()
+ elif self._is_del(c):
+ # <C-d>
+ if c == chr(0x04):
+ if self.curpos < (self.promptlen + len(self.buf)):
+ self.buf.pop(self.curpos - self.promptlen)
+ self._refreshline()
+ # <C-h> or delete
+ elif c == chr(0x08) or c == chr(0x7f):
+ if self.curpos > self.promptlen:
+ self.buf.pop(self.curpos - self.promptlen - 1)
+ self.curpos -= 1
+ self._refreshline()
+ # <C-u>
+ elif c == chr(0x15):
+ self._clearline()
+ # <C-w>
+ elif c == chr(0x17):
+ pos = self.curpos - self.promptlen
+ i = pos
+ flag = False
+ for c in reversed(self.buf[:pos]):
+ if flag and c == ' ':
+ break
+ if c != ' ':
+ flag = True
+ i -= 1
+ del self.buf[i:pos]
+ self.curpos = self.promptlen + i
+ self._refreshline()
+ # <C-l>
+ elif c == chr(0x0c):
+ self._clearscreen()
+ elif self._is_cmpl(c):
+ self._do_cmpl(self.buf)
+ elif self._is_enter(c):
+ if len(''.join(self.buf).strip()) != 0:
+ # cmd line interpretation
+ cmds = self._do_cmpl(self.buf, is_exec=True)
+ if cmds:
+ self.history.insert(0, self.buf)
+ self.histindex = 0
+ self._execute_cmd(cmds)
+ else:
+ LOG.debug("blank buf. just start a new line.")
+ self._startnewline()
+
+ LOG.debug("curpos: %d, buf: %s, prompted: %s" % (self.curpos,
+ self.buf,
+ self.prompted))
+
+ LOG.info("session end")
+
+
+class SshServerFactory(object):
+ def __init__(self, *args, **kwargs):
+ super(SshServerFactory, self).__init__(*args, **kwargs)
+
+ def streamserver_handle(self, sock, addr):
+ SshServer(sock, addr)
+
+
+class Cli(app_manager.RyuApp):
+ def __init__(self, *args, **kwargs):
+ super(Cli, self).__init__(*args, **kwargs)
+ something_started = False
+
+ def start(self):
+ LOG.info("starting ssh server at %s:%d",
+ CONF.cli_ssh_host, CONF.cli_ssh_port)
+ t = hub.spawn(self._ssh_thread)
+ something_started = True
+ return t
+
+ def _ssh_thread(self):
+ factory = SshServerFactory()
+ server = hub.StreamServer((CONF.cli_ssh_host,
+ CONF.cli_ssh_port),
+ factory.streamserver_handle)
+ server.serve_forever()