IP扫描——有哪些服务器开着远程登陆(ssh)?
本文看点:
如何用python协程实现10k并发?python新推出的asyncio库是干嘛用的?python的await和async关键词,你都用过了吗?
这个例子真是一个对于异步IO和协程编程的良好实践。
原理
当我们和ssh服务的监听端口建立连接,ssh服务会主动发送一行字符,写明自己的协议,协议版本,软件等信息。就像下面这样:
SSH-1.99-Comware-5.20
于是,我们可以以此为依据,判断对方电脑是否运行着SSH协议。
分析
我们知道,ipv4地址是非常稀缺的,只有大约30亿个。如果我们能每秒钟和一万个ip地址的22号端口尝试连接,那么,诶,我们只需要88个小时就能遍历所有ip地址。
但是事实上呢,我不想花88个小时。我也不需要找到世界上所有的服务器。而且我们对于一些”智能设备“不感兴趣。于是呢,一个省事的办法,我们可以只集中扫描阿里云申请的网段,这些网段是可以在网上查到的。
接下来需要思考的问题是,我们尝试一个ip地址需要大量的时间。对于每个ip地址,我的策略是等待两秒钟,如果两秒内不能建立连接,那么这个ip地址很有可能不可达,或者是受到了防火墙的阻拦。建立连接之后,我们等待三秒钟,如果运行着ssh服务的话,对方服务器就会发送一行”SSH“开头的文字。收到这一行文字之后,就可以肯定,这是一个运行着SSH服务的计算机了。
可以预料的,大部分ip会连接超时,部分ip会主动拒绝连接,还有部分连接上之后会马上断开或者迟迟不发送任何内容。平均每个IP大约会消耗一秒钟的时间。也就是说,只要我们的程序能够支持上万并发,那么即使一秒钟即使不能尝试一万个,也能尝试几千个吧。
所以,我们只需要编写一个能同时和上万个ip地址建立连接的程序就可以啦,这说难不难,说简单却也不简单,这也是我写这篇文章的原因。
实现
我们用python来实现这个功能。文末有源码链接,欢迎白嫖。
各位,不知道大家是否和我一样,只要在文章中看到python两个字就头大。像什么用python解决日常工作中的重复问题,用python批量整理文件。妈耶,工作中这么多重复问题建议辞职好吧。人人都要学python吗?问出这种话来的还是赶快找个厂上班吧~
好了,吐槽了这么多,总之,接下来的内容可不是python营销号会讲的。虽然简陋了一点,但以下内容四舍五入也算是”10k高并发“了(滑稽)。
首先呢。我们知道,线程是昂贵的。没有40个G,还是别去考虑建立上万个线程了。那么我们该怎么办呢?我们还有异步IO和协程可以选择。最近python出了asyncio这个标准库,以及await,async等等关键字。就是为了这种任务准备的。
相信很多用python的人都关注到了这些更新。同时相信实际用过的人很少。那么我们接下来就来看看该怎么写吧。

看上图,首先我们来实现核心功能,检测ssh协议是否开启。
12行,在事件循环中建立TCP连接,注意,协程函数中不应该有任何会让线程挂起的操作,比如你此时不可以用socket.connect。
17行,尝试从TCP流中完整读出一行(预期是“SSH-2.0-xxx\r\n”)
24行,关掉连接
这个函数接受一个ip地址作为参数,如果这个ip地址开着SSH协议,则返回ip地址和ssh版本信息字符串的元组,否则返回False。要看懂这些代码,你得对python协程有个基础的了解。我将补充在下面。
线程切换的本质,是保存和恢复“现场”。当一个线程需要IO的时候。系统将中断这个线程的执行,保存线程的上下文,然后调度另一个线程获得CPU时间。
而协程的本质在于,协程进行IO会告诉系统,“我不需要等待这个IO的完成,所以不要将我挂起,我有其他事情要做”。
协程进行IO的时候,将由程序内的事件循环调度其他协程继续工作。IO就绪之后,再让事件循环重新调度这个协程。
事实上,可以看出,协程是将系统的活在程序内做了。区别在于协程的切换不需要系统调用,开销很小,python协程是无栈协程,甚至连调用栈都没有。相比于系统线程动则几兆的栈区,简直太节约了。
python的yield关键字天然允许函数运行一半停住,退出来,然后运行别的函数。那么很容易想到,我要不用线程并发的运行几个函数,我可以轮流迭代几个包含yield的函数即可,这就是python协程的本质,await关键字某种程度上可以看成yield from。
就像你想的那样,协程不会在await以外的地方被打断(而线程则会在意想不到的地方被打断)。这意味着你可以省掉大量的脑细胞,你会发现在协程模型中,并发的线程安全问题不再是问题。但是坏处是,写协程满脑子都是await,task,future。总之就是原来复杂的问题变简单了,而一些简单的问题却变得复杂了一点点。
更多的内容请阅读
https://docs.python.org/zh-cn/3/library/asyncio.html
写得清楚得一逼。
并发的控制和输出结果

我们用一个信号量来确保事件循环中的任务不至于多到过分。注意,这里的信号量并不是线程编程用的那个信号量,而是协程专用的信号量。
添加任务完成的回调函数,来处理检查好的ip

如果有异常的话,打印异常栈,没有异常就看看结果,如果这个ip确实开着ssh就记录下来。同时通过print来打印进度信息。
完善的错误日志永远是好的。
进度条永远是好的。
这些ip地址有什么用
理论上,这么多开着ssh协议的服务器,总会有几个是弱口令不是吗
提醒大家要遵纪守法。
最后,给出github源码连接:https://github.com/newtoncy/ssh_scaner
不想跑代码,想直接拿结果的人,评论区回复,然后我会私信你。