雖然現今大部分的網路設備都提供了親切的WEB介面讓管理人員能夠快速的完成設定,但是如果遇到了需要批次且大量的設定狀況,對於程式來說,WEB介面就顯得相當的難以整合,因此,若要批次完成某些設定,文字介面(Command Line Interface, CLI),依然是兼顧效能與開發方便的首選,不過,若使用傳統Telnet方式進行設定,難免存在著網路竊聽的風險,加密的連線設定方式當然因應而生,而SSH則是目前相當普遍的網路設備遠端設定方式。

問題來了,該如何開發一個WEB Base的整合性網管介面能讓此系統管理大量的網路設備呢?透過SNMP當然是種選擇,對於一般網路管理者來說,CLI應是再熟悉不過的工具,所以若能讓網頁程式利用加密連線來集中控管,也是不錯的選擇。

最近公司剛好遇到這樣的問題,雖然開發程式的不是我,不過對於網頁程式該如何用SSH連接網路設備我倒是滿有興趣的,總不會笨到還要透過網頁程式去call另外模組(maybe是C、C++或者perl)處理吧?上網找看看PHP有沒有支援SSH,果然,PHP有支援SSH2ㄟ....不過這個模組不是預設的,如果要使用要另外做些設定,因為在公司不像在學校,要幾台機器有幾台,所以只有Win32的環境可以測試,下面是我的測試心得。

關於PHP對SSH的支援請見官方網站

安裝:
在PHP SSH2 manual的安裝教學裡面,告訴我們如果在Win32的環境下要使用PHP的SSH2模組必須要到http://snaps.php.net去下載php_ssh2.dll這個dll檔案,但是奇妙的是,snaps.php.net沒有這個下載點阿?原來,這個外掛的dll是放在pecl-版本-dev.zip中。

你可以根據你的php版本下載相對應的檔案回來,解壓縮後,照著installation manual的方法把他擺在extension diretory裡面,像我把PHP裝在C:\php\下面,而把extension放在php預設的extension directory "C:\php\ext\"裡面,接著修改php.ini,這邊要注意php.ini裡面extension_dir這個變數所指的是哪裡,我的php.ini裡面預設是"./",如果不修改這個設定的話,php_ssh2.dll就必須要擺在你的php根目錄下(像是我的環境就是C:\php\)。

在官方的Installation manaul中的範例是將檔案放在C:\php5\exts中,並修改php.ini裡面extension_dir這個參數,這邊請根據個人喜好安裝,接著就是在php.ini裡面加上一行啟用這個extension的設定:extension=php_ssh2.dll,修改完php.ini後,電腦裡面PHP對SSH2的支援設定就算完成了。

程式:
在官方網站中有網友分享了他寫的class,經過我的修改後如下:

PHP SSH Connect
<?php

// ssh protocols
// note: once openShell method is used, cmdExec does not work

class ssh2 {

  private $host = 'host';
  private $user = 'user';
  private $port = '22';
  private $password = 'password';
  private $con = null;
  private $shell_type = 'xterm';
  private $shell = null;
  private $log = '';

  function __construct($host='', $port=''  ) {

     if( $host!='' ) $this->host  = $host;
     if( $port!='' ) $this->port  = $port;

     $this->con  = ssh2_connect($this->host, $this->port);
     if( !$this->con ) {
       $this->log .= "Connection failed !";
     }

  }

  function authPassword( $user = '', $password = '' ) {

     if( $user!='' ) $this->user  = $user;
     if( $password!='' ) $this->password  = $password;

     if( !ssh2_auth_password( $this->con, $this->user, $this->password ) ) {
       $this->log .= "Authorization failed !";
     }

  }

  function openShell( $shell_type = '' ) {
     
    if ( $shell_type != '' ) $this->shell_type = $shell_type;
    $this->shell = ssh2_shell( $this->con,  $this->shell_type );
    if( !$this->shell ) $this->log .= " Shell connection failed !";
    stream_set_blocking( $this->shell, true );
   
  }

  function writeShell( $command = '' ) {

    fwrite($this->shell, $command."\n");
       
  }

  function cmdExec( ) {

        $argc = func_num_args();
        $argv = func_get_args();

    $cmd = '';
    for( $i=0; $i<$argc ; $i++) {
        if( $i != ($argc-1) ) {
          $cmd .= $argv[$i]." && ";
        }else{
          $cmd .= $argv[$i];
        }
    }
    echo $cmd;

    $stream = ssh2_exec( $this->con, $cmd );
    stream_set_blocking( $stream, true );
       return stream_get_contents($stream);

  }

  function getLog() {
     return $this->log;

  }

    function getResult(){
        $contents='';
    while (!feof($this->shell)) {
        $contents.=fgets($this->shell);
    }
    return $contents;
    }
}

?>
我修改的部份主要是新增了一個getResult的function,這個function是提供writeShell()寫入命令後,Server回傳的response,另外把cmdExec()這個function裡面的fread換成stream_get_contents(),最主要是因為我沒辦法control主機回傳stream的大小,所以利用stream_get_contents(),當然,用fgets()也是可以的,這兩個function哪一個處理的效率比較好在PHP官網上面有人比較過,不過我忘記誰比較快了。

值得注意的是,ssh2_exec()一次只能執行一個指令,也就是說,在執行完ssh2_exec()之後,PHP就會將連線中斷,下一個ssh2_exec()執行時,會重新Login進去執行,也就是說指令之間是不會有關聯性的,這對網路設備來說是非常不合理的,因為有用過網路設備的都知道,要完成一項作業可能會需要很多道指令,每個指令可能都有階層式關係,我目前的解法是在每個指令後用換行符號接起來,eg."config firewall policy \n edit 1\n"。

由於上面的原因,所以在大部分情況下,用ssh2_shell()是比較好的解決方式,不過因為ssh2_shell()不會主動中斷連線,使用時(Class裡面的openShell跟writeShell),記得要把logout的指令一並執行,不然程式會hang住(感謝小王子學長幫我debug)。

範例一:
使用ssh2_exec()連線
<?
//將上面的class檔include進來
include_once("ssh2.php");
//初始化class
$shell = new ssh2("設備IP");
$shell->authPassword("帳號","密碼");
//執行指令
$result=$shell->cmdExec("指令");
//印出指令執行結果
echo $result;
?>
範例二:
使用ssh2_shell()連線
<?
//將上面的class檔include進來
include_once("ssh2.php");
//初始化class
$shell = new ssh2("設備IP");
$shell->authPassword("帳號","密碼");
//設定Terminal Type,預設就是xterm
$shell->openShell("xterm");
//執行指令
$shell->writeShell("指令1");
$shell->writeShell("指令2");
//登出logout指令(譬如在linux就是exit)
$shell->writeShell("logout");
//得到執行結果
$result=$shell->getResult();
//印出指令執行結果
echo $result;
?>

Alvin所問的連續登入,只需要按照一般登入登出動作依序執行即可,如下面範例, 批次大量的執行可以考慮寫成function再加上multi-process來做:
<?
//將上面的class檔include進來
include_once("ssh2.php");

//**************************
*         登入第一台設備
***************************//
//初始化class
$shell = new ssh2("設備IP");
$shell->authPassword("帳號","密碼");
//設定Terminal Type,預設就是xterm
$shell->openShell("xterm");
//執行指令
$shell->writeShell("指令1");
$shell->writeShell("指令2");
//登出logout指令(譬如在linux就是exit)
$shell->writeShell("exit");
//得到執行結果
$result=$shell->getResult();
//印出指令執行結果
echo $result;


//**************************
*         登入第二台設備
***************************//
//初始化class
$shell = new ssh2("設備IP");
$shell->authPassword("帳號","密碼");
//設定Terminal Type,預設就是xterm
$shell->openShell("xterm");
//執行指令
$shell->writeShell("指令1");
$shell->writeShell("指令2");
//登出logout指令(譬如在linux就是exit)
$shell->writeShell("exit");
//得到執行結果
$result=$shell->getResult();
//印出指令執行結果
echo $result;
?>

arrow
arrow
    全站熱搜

    nsysumis94 發表在 痞客邦 留言(9) 人氣()