IP掃描——有哪些服務(wù)器開著遠(yuǎn)程登陸(ssh)?
本文看點:
如何用python協(xié)程實現(xiàn)10k并發(fā)?python新推出的asyncio庫是干嘛用的?python的await和async關(guān)鍵詞,你都用過了嗎?
這個例子真是一個對于異步IO和協(xié)程編程的良好實踐。
原理
當(dāng)我們和ssh服務(wù)的監(jiān)聽端口建立連接,ssh服務(wù)會主動發(fā)送一行字符,寫明自己的協(xié)議,協(xié)議版本,軟件等信息。就像下面這樣:
SSH-1.99-Comware-5.20
于是,我們可以以此為依據(jù),判斷對方電腦是否運(yùn)行著SSH協(xié)議。
分析
我們知道,ipv4地址是非常稀缺的,只有大約30億個。如果我們能每秒鐘和一萬個ip地址的22號端口嘗試連接,那么,誒,我們只需要88個小時就能遍歷所有ip地址。
但是事實上呢,我不想花88個小時。我也不需要找到世界上所有的服務(wù)器。而且我們對于一些”智能設(shè)備“不感興趣。于是呢,一個省事的辦法,我們可以只集中掃描阿里云申請的網(wǎng)段,這些網(wǎng)段是可以在網(wǎng)上查到的。
接下來需要思考的問題是,我們嘗試一個ip地址需要大量的時間。對于每個ip地址,我的策略是等待兩秒鐘,如果兩秒內(nèi)不能建立連接,那么這個ip地址很有可能不可達(dá),或者是受到了防火墻的阻攔。建立連接之后,我們等待三秒鐘,如果運(yùn)行著ssh服務(wù)的話,對方服務(wù)器就會發(fā)送一行”SSH“開頭的文字。收到這一行文字之后,就可以肯定,這是一個運(yùn)行著SSH服務(wù)的計算機(jī)了。
可以預(yù)料的,大部分ip會連接超時,部分ip會主動拒絕連接,還有部分連接上之后會馬上斷開或者遲遲不發(fā)送任何內(nèi)容。平均每個IP大約會消耗一秒鐘的時間。也就是說,只要我們的程序能夠支持上萬并發(fā),那么即使一秒鐘即使不能嘗試一萬個,也能嘗試幾千個吧。
所以,我們只需要編寫一個能同時和上萬個ip地址建立連接的程序就可以啦,這說難不難,說簡單卻也不簡單,這也是我寫這篇文章的原因。
實現(xiàn)
我們用python來實現(xiàn)這個功能。文末有源碼鏈接,歡迎白嫖。
各位,不知道大家是否和我一樣,只要在文章中看到python兩個字就頭大。像什么用python解決日常工作中的重復(fù)問題,用python批量整理文件。媽耶,工作中這么多重復(fù)問題建議辭職好吧。人人都要學(xué)python嗎?問出這種話來的還是趕快找個廠上班吧~
好了,吐槽了這么多,總之,接下來的內(nèi)容可不是python營銷號會講的。雖然簡陋了一點,但以下內(nèi)容四舍五入也算是”10k高并發(fā)“了(滑稽)。
首先呢。我們知道,線程是昂貴的。沒有40個G,還是別去考慮建立上萬個線程了。那么我們該怎么辦呢?我們還有異步IO和協(xié)程可以選擇。最近python出了asyncio這個標(biāo)準(zhǔn)庫,以及await,async等等關(guān)鍵字。就是為了這種任務(wù)準(zhǔn)備的。
相信很多用python的人都關(guān)注到了這些更新。同時相信實際用過的人很少。那么我們接下來就來看看該怎么寫吧。

看上圖,首先我們來實現(xiàn)核心功能,檢測ssh協(xié)議是否開啟。
12行,在事件循環(huán)中建立TCP連接,注意,協(xié)程函數(shù)中不應(yīng)該有任何會讓線程掛起的操作,比如你此時不可以用socket.connect。
17行,嘗試從TCP流中完整讀出一行(預(yù)期是“SSH-2.0-xxx\r\n”)
24行,關(guān)掉連接
這個函數(shù)接受一個ip地址作為參數(shù),如果這個ip地址開著SSH協(xié)議,則返回ip地址和ssh版本信息字符串的元組,否則返回False。要看懂這些代碼,你得對python協(xié)程有個基礎(chǔ)的了解。我將補(bǔ)充在下面。
線程切換的本質(zhì),是保存和恢復(fù)“現(xiàn)場”。當(dāng)一個線程需要IO的時候。系統(tǒng)將中斷這個線程的執(zhí)行,保存線程的上下文,然后調(diào)度另一個線程獲得CPU時間。
而協(xié)程的本質(zhì)在于,協(xié)程進(jìn)行IO會告訴系統(tǒng),“我不需要等待這個IO的完成,所以不要將我掛起,我有其他事情要做”。
協(xié)程進(jìn)行IO的時候,將由程序內(nèi)的事件循環(huán)調(diào)度其他協(xié)程繼續(xù)工作。IO就緒之后,再讓事件循環(huán)重新調(diào)度這個協(xié)程。
事實上,可以看出,協(xié)程是將系統(tǒng)的活在程序內(nèi)做了。區(qū)別在于協(xié)程的切換不需要系統(tǒng)調(diào)用,開銷很小,python協(xié)程是無棧協(xié)程,甚至連調(diào)用棧都沒有。相比于系統(tǒng)線程動則幾兆的棧區(qū),簡直太節(jié)約了。
python的yield關(guān)鍵字天然允許函數(shù)運(yùn)行一半停住,退出來,然后運(yùn)行別的函數(shù)。那么很容易想到,我要不用線程并發(fā)的運(yùn)行幾個函數(shù),我可以輪流迭代幾個包含yield的函數(shù)即可,這就是python協(xié)程的本質(zhì),await關(guān)鍵字某種程度上可以看成yield from。
就像你想的那樣,協(xié)程不會在await以外的地方被打斷(而線程則會在意想不到的地方被打斷)。這意味著你可以省掉大量的腦細(xì)胞,你會發(fā)現(xiàn)在協(xié)程模型中,并發(fā)的線程安全問題不再是問題。但是壞處是,寫協(xié)程滿腦子都是await,task,future。總之就是原來復(fù)雜的問題變簡單了,而一些簡單的問題卻變得復(fù)雜了一點點。
更多的內(nèi)容請閱讀
https://docs.python.org/zh-cn/3/library/asyncio.html
寫得清楚得一逼。
并發(fā)的控制和輸出結(jié)果

我們用一個信號量來確保事件循環(huán)中的任務(wù)不至于多到過分。注意,這里的信號量并不是線程編程用的那個信號量,而是協(xié)程專用的信號量。
添加任務(wù)完成的回調(diào)函數(shù),來處理檢查好的ip

如果有異常的話,打印異常棧,沒有異常就看看結(jié)果,如果這個ip確實開著ssh就記錄下來。同時通過print來打印進(jìn)度信息。
完善的錯誤日志永遠(yuǎn)是好的。
進(jìn)度條永遠(yuǎn)是好的。
這些ip地址有什么用
理論上,這么多開著ssh協(xié)議的服務(wù)器,總會有幾個是弱口令不是嗎
提醒大家要遵紀(jì)守法。
最后,給出github源碼連接:https://github.com/newtoncy/ssh_scaner
不想跑代碼,想直接拿結(jié)果的人,評論區(qū)回復(fù),然后我會私信你。