雖然現今大部分的網路設備都提供了親切的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 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; ?> |