paramiko實現(xiàn)SSH交互式命令執(zhí)行

背景

需要批量在路由器上進行配置浑测,與網(wǎng)元建立SSH連接芥颈,同時存在交互操作。比如:鍵入Configure角钩,進入配置模式成功后才可以鍵入后續(xù)指令吝沫。

在這個過程中遇到很多坑,在此分享递礼。

SSHClient

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=device, port=22, username=username, password=password)
stdin, stdout, stderr = ssh.exec_command("configure", bufsize=1024)
res, err = stdout.read(), stderr.read()
result = res if res else err
print(result)

SSHClient是最簡單的命令行執(zhí)行方式惨险,簡單的參數(shù)填寫,優(yōu)雅的結(jié)果處理脊髓,這很難不讓人趕緊上手一試辫愉。

但遺憾的是SSHClient不支持交互式命令執(zhí)行,其原因在于其exec_command方法每次執(zhí)行一條命令都會開啟一個新的“channel”将硝,從而開啟一個新的session恭朗,這相當于我們每執(zhí)行一次命令,都重新登錄了一次網(wǎng)元設(shè)備袋哼,這使得交互式無從談起冀墨。

def exec_command(
    self,
    command,
    bufsize=-1,
    timeout=None,
    get_pty=False,
    environment=None,
):
# 就是他,在這個open_session的說明中也有描述:Request a new channel to the server, 
# of type ``"session"``.
chan = self._transport.open_session(timeout=timeout)
if get_pty:
    chan.get_pty()
chan.settimeout(timeout)
if environment:
    chan.update_environment(environment)
chan.exec_command(command)
stdin = chan.makefile_stdin("wb", bufsize)
stdout = chan.makefile("r", bufsize)
stderr = chan.makefile_stderr("r", bufsize)
return stdin, stdout, stderr

所以我們要創(chuàng)建固定channel涛贯,從而創(chuàng)建固定session

交互式連接

# Create a new SSH session over an existing socket, or socket-like object.
trans = paramiko.Transport((devcie, 22))
trans.start_client()
trans.auth_password(username, password)

# 新建channel
channel = trans.open_session(timeout=1200)
# 獲取終端
channel.get_pty()
# 激活終端
channel.invoke_shell()
# 執(zhí)行命令
channel.send(command)
# 結(jié)果獲取
result = channel.recv(10240)
result = result.decode("utf-8")

產(chǎn)生的問題

以上是交互式連接的過程诽嘉,但事情并不是一帆風順的,在這個過程中還有兩個問題弟翘,這兩個問題都由交互結(jié)果獲取函數(shù)channel.recv引起虫腋,這將導(dǎo)致。

  1. 交互結(jié)果獲取存在延遲
  2. 錯誤處理困難

原因

def read(self, len=1024, buffer=None):
    return self._wrap_ssl_read(len, buffer)


def recv(self, len=1024, flags=0):
    if flags != 0:
        raise ValueError("non-zero flags not allowed in calls to recv")
    return self._wrap_ssl_read(len)


def _wrap_ssl_read(self, len, buffer=None):
    try:
        return self._ssl_io_loop(self.sslobj.read, len, buffer)
    except ssl.SSLError as e:
        if e.errno == ssl.SSL_ERROR_EOF and self.suppress_ragged_eofs:
            return 0  # eof, return 0.
        else:
            raise

        
def _ssl_io_loop(self, func, *args):
    """Performs an I/O loop between incoming/outgoing and the socket."""
    should_loop = True
    ret = None
    while should_loop:
        errno = None
        try:
            # 就是這里稀余,遞歸的入口
            ret = func(*args)
        except ssl.SSLError as e:
            if e.errno not in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE):
                # WANT_READ, and WANT_WRITE are expected, others are not.
                raise e
            errno = e.errno
        buf = self.outgoing.read()
        self.socket.sendall(buf)
        if errno is None:
            should_loop = False
        elif errno == ssl.SSL_ERROR_WANT_READ:
            buf = self.socket.recv(SSL_BLOCKSIZE)
            if buf:
                self.incoming.write(buf)
            else:
                self.incoming.write_eof()
    return ret

以上是channel.recv的實現(xiàn)過程悦冀,歸根結(jié)底還是_ssl_io_loop 這個函數(shù)會遞歸的獲取ssh交互結(jié)果,形成一種循環(huán)睛琳。這種循環(huán)的結(jié)束條件就是接受到交互結(jié)果盒蟆,或者是結(jié)果讀取異常踏烙。

如果我們在執(zhí)行命令之后立刻獲取結(jié)果,交互可能尚未產(chǎn)生結(jié)果历等,獲取失敗讨惩,結(jié)束循環(huán)(接收無效),channel.recv仿佛沒有執(zhí)行一樣寒屯。

這時我們就會想到荐捻,既然交互結(jié)果的獲取具有滯后性,那我們就編寫邏輯寡夹,使其等待或者循環(huán)等待处面。

這就會引發(fā)下一個問題,如果我們循環(huán)調(diào)用channel.recv時菩掏,必須在獲取到交互結(jié)果后結(jié)束我們的循環(huán)魂角,不論這個交互的結(jié)果是不是你期望的;否則會進入死循環(huán)患蹂,程序無法推進或颊。

建議

  1. 循環(huán)調(diào)用channel.recv以獲取交互結(jié)果。
  2. 全面的結(jié)果處理传于,結(jié)果的處理取決于所交互設(shè)備的響應(yīng)內(nèi)容囱挑,一旦獲取交互結(jié)果,不論是期望與否都要及時退出我們的循環(huán)沼溜;否則程序無法推進平挑。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市系草,隨后出現(xiàn)的幾起案子通熄,更是在濱河造成了極大的恐慌,老刑警劉巖找都,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唇辨,死亡現(xiàn)場離奇詭異,居然都是意外死亡能耻,警方通過查閱死者的電腦和手機赏枚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晓猛,“玉大人饿幅,你說我怎么就攤上這事〗渲埃” “怎么了栗恩?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長洪燥。 經(jīng)常有香客問我磕秤,道長乳乌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任亲澡,我火速辦了婚禮钦扭,結(jié)果婚禮上纫版,老公的妹妹穿的比我還像新娘床绪。我一直安慰自己,他們只是感情好其弊,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布癞己。 她就那樣靜靜地躺著,像睡著了一般梭伐。 火紅的嫁衣襯著肌膚如雪痹雅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天糊识,我揣著相機與錄音绩社,去河邊找鬼。 笑死赂苗,一個胖子當著我的面吹牛愉耙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拌滋,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼朴沿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了败砂?” 一聲冷哼從身側(cè)響起赌渣,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昌犹,沒想到半個月后坚芜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡斜姥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年鸿竖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疾渴。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡千贯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出搞坝,到底是詐尸還是另有隱情搔谴,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布桩撮,位于F島的核電站敦第,受9級特大地震影響峰弹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芜果,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一鞠呈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧右钾,春花似錦蚁吝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至脆烟,卻和暖如春山林,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邢羔。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工驼抹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拜鹤。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓框冀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親署惯。 傳聞我的和親對象是個殘疾皇子左驾,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容