Python⽹络管理
⽹络
⽹络可以将多台主机进⾏连接,使得⽹络中的主机可以相互通信。在⽹络通信中,使⽤最⼴泛的通信协议是TCP/IP协议簇,因此,Python 也提供了相应的应⽤程序接⼝(API), 使得⼯程师可以在Python程序中创建⽹络连接、进⾏⽹络通信。金惠秀 三级
计算机之间可以相互通信以后,就开始涉及⽹络安全问题。现如今⽹络情况复杂安全环境恶劣。
2017年5⽉12⽇起,全球范围内爆发基于Windows⽹络共享协议进⾏攻击传播的蠕⾍恶意代码,这是不法分⼦通过改造之前泄露的NSA⿊客武器库中“永恒之蓝”攻击程序发起的⽹络攻击事件。五个⼩时内,包括英国、俄罗斯、整个欧洲以及中国国内多个⾼校校内⽹、⼤型企业内⽹和政府机构专⽹中招,被⽀付⾼额赎⾦才能解密恢复⽂件,对重要数据造成严重损失。
被袭击的设备被锁定,并索要300美元⽐特币赎⾦。要求尽快⽀付赎⾦,否则将删除⽂件,甚⾄提出半年后如果还没⽀付的穷⼈可以参加免费解锁的活动。原来以为这只是个⼩范围的恶作剧式的软件,没想到该软件⼤⾯积爆发,许多⾼校学⽣中招,愈演愈烈。
Python是⼀门应⽤领域⾮常⼴泛的语⾔,除了在科学计算、⼤数据处理、⾃动化运维等领域⼴泛应⽤以
外,在计算机⽹络领域中使⽤也⾮常⼴泛。这主要得益于Python语⾔的开发效率⾼、⼊门门槛低、功能强⼤等优点。⼯程师可以使⽤Python语⾔管理⽹络,计算机⿊客可以使⽤Python语⾔或者Python语⾔编写的安全⼯具进⾏渗透测试、⽹络分析、安全防范等。
在这章中,我们将介绍Python在⽹络⽅⾯的应⽤,包括⽹络通信、⽹络管理和⽹络安全。我们⾸先介绍如何使⽤Python语⾔列出⽹络上所有活跃的主机;然后介绍⼀个 端⼝扫描⼯具;接着介绍如何使⽤IPy⽅便地进⾏IP地址管理;随后,介绍了⼀个DNS⼯具包;最后,我们介绍了⼀个⾮常强⼤的⽹络嗅探⼯具。
⼀、列出⽹络上所有活跃的主机
在这⼀⼩节中,我们将会学习如何在shell脚本中调⽤ping命令得到⽹络上活跃的主机列表,随后,我们使⽤Python语⾔改造这个程序,以此⽀持并发的判断。
1、使⽤ping命令判断主机是否活跃
ping命令是所有⽤户都应该了解的最基础的⽹络命令,ping命令可以探测主机到主机之间是否能够通信,如果不能ping到某台主机,则表明不能和这台主机进⾏通信。ping命令最常使⽤的场景是验证⽹络上两台主机的连通性以及出⽹络上活跃的主机。
为了检査⽹络上两台主机之间的连通性,ping命令使⽤互联⽹控制协议(ICMP)中的 ECHO_REQUEST数据报,⽹络设备收到该数据报后会做出回应。ping命令可以通过⽹络设备的回复得知两台主机的连通性以及主机之间的⽹络延迟。
ping命令的使⽤⾮常简单,直接使⽤主机名、域名或IP地址作为参数调⽤ping命令即可。如下所⽰:
[root@bogon ~]# ping 192.168.1.10
PING 192.168.1.10 (192.168.1.10) 56(84) bytes of data.
64 bytes from 192.168.1.10: icmp_seq=1 ttl=64 time=0.023 ms
64 bytes from 192.168.1.10: icmp_seq=2 ttl=64 time=0.034 ms
64 bytes from 192.168.1.10: icmp_seq=3 ttl=64 time=0.033 ms
64 bytes from 192.168.1.10: icmp_seq=4 ttl=64 time=0.039 ms
ping命令会连续发送包,并将结果打印到屏幕终端上。如果主机不可达,ping将会显⽰“Destination Host Unreachable”的错误信息。如下所⽰:
[root@bogon ~]# ping www.baidu
PING www.a.shifen (220.181.38.150) 56(84) bytes of data.
From bogon (192.168.1.10) icmp_seq=1 Destination Host Unreachable
From bogon (192.168.1.10) icmp_seq=2 Destination Host Unreachable
From bogon (192.168.1.10) icmp_seq=3 Destination Host Unreachable
From bogon (192.168.1.10) icmp_seq=4 Destination Host Unreachable
销售面试技巧除了检查⽹络上两台主机之间的连通性外,ping命令还可以粗略地估计主机之间的⽹络延迟情况。在ping命令的输出结果中,time字段的取值表⽰⽹络上两台主机之间的往返时间,它是分组从源主机到B的主机⼀个来回的时间,单位是毫秒。我们可以通过这个时间粗略估计⽹络的速度以及监控⽹络状态。例如,有这样⼀个使⽤ping命令解决线上问题的案例。当时的情况是应⽤程序使⽤我们提供的数据库服务,在每个整点时都会出现应⽤程序建⽴数据库连接失败的情况。通过前期排査,可以确定的是应⽤的请求已成功发出,数据库的压⼒并不是特别⼤,数据库连接也没有满。因此,问题很有可能出在⽹络上⾯。为此,我们增加了⼀个ping延迟监控。通过监控发现,在每个整点时ping的⽹络延迟变⼤,甚⾄⼤到了不可接受的程度。有了这个线索以后,接着排查⽹络问题。通过分析定位,发现是
因为宿主机上有定时任务,导致每个整点宿主机的cpu压⼒增加,从⽽引发了前⾯所说的建⽴数据库连接失败的错误。
默认情况下,ping命令会不停地发送ECHO_REQUEST数据报并等待回复,到按下Ctrl+C为⽌。我们可以⽤选项-c限制所发送的
ECHO_REQUEST数据报数量。⽤法如下:
[root@bogon ~]# ping -c 2 192.168.1.10
PING 192.168.1.10 (192.168.1.10) 56(84) bytes of data.
64 bytes from 192.168.1.10: icmp_seq=1 ttl=64 time=0.030 ms
64 bytes from 192.168.1.10: icmp_seq=2 ttl=64 time=0.047 ms
--- 192.168.1.10 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.030/0.038/0.047/0.010 ms
在这个例⼦中,ping命令发送了 2个ECHO_REQUEST数据报就停⽌发送,这个功能对于脚本中检查⽹络的连通性⾮常有⽤。
ping命令将结果打印到屏幕终端,我们通过ping命令的输出结果判断主机是否可达, 这种⽅式虽然直观,但是不便于程序进⾏处理。在程序中可以通过ping命令的返回码判断主机⾜否可达,当主机活跃时,ping命令的返回码为0,当主机不可达时,ping命令的返回码⾮0。
有了上⾯的基础以后,要判断⽹络上活跃的主机就⾮常容易了。我们只需要ping每⼀台主机,然后通过ping命令的返回值判断主机是否活跃。下⾯这段Shell程序就是⽤来判断⽹络中的主机是否可达:
for ip in''
do
if ping$ip -c 2 &> /dev/null
then
echo"$ip is alive"
else
echo"$ip is unreachable"
fi
done
在这个例⼦中,我们⾸先将主机地址以每⾏⼀个地址的形式保存到⽂件中,然后通过cat命令读取⽂件中的内容,使⽤for循环迭代中保存的主机。
为了减少视觉杂讯,使⽤输出重定向的⽅式将ping命令的结果输出到/dev/null中,以此避免信息在终端上打印。为了简化起见,我们直接在if语句中调⽤ping命令,Shell脚本能够根据ping命令的返回码判断命令执⾏成功还是失败。
2、使⽤Python判断主机是否活跃
前⾯的Shell脚本中,虽然所有的IP地址都是彼此独⽴,但是,我们的程序依然是顺序调⽤ping命令进⾏主机探活。由于每执⾏⼀次ping命令都要经历⼀段时间延迟(或者接收回应,或者等待回应超时),所以,当我们要检査⼤量主机是否处于活跃状态时需要很长的时间。对于这种情况可以考虑并发地判断主机是否活跃。
Shell脚本可以⾮常快速地解决简单的任务,但是,对于⽐较复杂的任务,Shell脚本就⽆能为⼒。如这⾥的并发判断主机是否活跃的需求。对于这种情况,可以使⽤Python语⾔编写并发的程序,以此加快程序的执⾏。如下所⽰:
import threading
def is_reacheable(ip):
if(subprocess.call(['ping','-c','1', ip])):
print('{0} is alive'.format(ip))
else:
print('{0} is unreacheable'.format(ip))
def main():
with open('')as f:
lines = f.readlines();
threads =[]
for line in lines:
thr = threading.Thread(target=is_reacheable, args=(line,))房屋遗产税
thr.start()
诡谲怎么读threads.append(thr)
for thr in threads:
thr.join()
if __name__ =="__main__":
main()
在这个例⼦中,我们⾸先打开⽂件,并通过File对象的readlines函数将所有IP 地址读⼊内存。读⼊内存以后,IP地址保存在⼀个列表中,列表的每⼀项正好是⼀个地址。 对于每⼀个IP地址都创建⼀个线程。由于线程之间不共享任何数据,因此,不需要进⾏并发控制,这也使得整个程序变得简单。
在Python 中要判断两台主机是否可达有两种不同的⽅法,⼀种是在Python程序中调⽤ping命令实 现,另⼀种是使⽤socket编程发送ICMP数据报。为了简单起见,在我们这⾥的例⼦中使⽤前⼀种⽅法。为了调⽤系统中的ping命令,我们使⽤了subprocess模块。
我们的Python程序最终也是调⽤ping命令判断主机是否活跃,从思路上来说,和前⾯的Shell脚本是⼀样的。区别就在于Python程序使⽤并发加快了程序的执⾏效率。在我的测试环境中,测试了36个IP地址,其中,有12个IP地址不可达,需要等待⽹络超时才能返回,另外24个IP地址可达。使⽤Linux⾃带的time命令进⾏粗略计时,Shell脚本的执⾏时间是1分48秒,Python程序的执⾏时间是10秒。两个程序执⾏时间的差异,根据⽹络规模和⽹络环境将会显著不同。这⾥要表达的是,使⽤Python语⾔只需要很少的代码就能够将⼀个程序改造成并发的程序,通过并发来⼤幅提升程序的效率。
3、使⽤⽣产者消费者模型减少线程的数量
在前⾯的例⼦中,我们为每⼀个IP地址创建⼀个线程,这在IP地址较少的时候还算可⾏,但在IP地址较多时就会暴露出各种问题(如频繁的上下⽂切换)。因此,我们需要限制线程的数量。
列出⽹络上所有活跃主机的问题,其实是⼀个简单的⽣产者和消费者的问题。⽣产者 和消费者问题是多线程并发中⼀个⾮常经典的问题,该问题描述如下:
有⼀个或多个⽣产者在⽣产商品,这些商品将提供给若⼲个消费者去消费。为了使⽣产者和消费者能并发执⾏,在两者之间设置⼀个缓冲期,⽣产者将它⽣产的商品放⼊缓冲中,消费者可以从缓冲区中取⾛商品进⾏消费。⽣产者只需要关⼼这个缓冲区是否已满, 如果未满则向缓冲区中放⼊商品,如果已满,则需要等待。同理,消费者只需要关⼼緩冲区中是否存在商品,如果存在商品则进⾏消费,如果缓冲区为空,则需要等待。
⽣产者和消费者模型的好处是,⽣产者不需要关⼼有多少消费者、消费者何时消费、 以怎样的速度进⾏消费。消费者也不需要关⼼⽣产者,这就实现了程序模块的解耦。
我们这⾥的问题⽐⽣产者和消费者模型还要简单,只需要⼀次性将所有的IP地址读⼊到内存中,然后交由多个线程去处理。也就是说,我们⼀开始就⽣产好了所有的商品,只需要消费者消费完这些商品即可。如下所⽰:
import threading
from sqlalchemy.util.queue import Empty, Queue
def call_ping(ip):
if(subprocess.call(['ping','-c','1', ip])):
print('{0} is alive'.format(ip))
else:
print('{0} is unreacheable'.format(ip))
def is_reacheable(q):
try:
while True:
ip = q.get_nowait()
call_ping(ip)
except Empty:
pass
def main():
q = Queue()
with open('')as f:
for line in f:
q.put(line)
threads =[]
for i in range(10):
thr = threading.Thread(target=is_reacheable, args=(q,))
thr.start()
threads.append(thr)
for thr in threads:
thr.join()
晚餐吃什么最减肥if __name__ =="__main__":
main()
在这个例⼦中创建了10个线程作为消费者线程,我们可以修改range函数的参数控制线程的个数。此外,我们还⽤到了⼀个新的数据结构,即Queue。引⼊Queue⾜因为多个消费者之间存在并发访问的问题,即多个消费者可能同时从缓冲区中获取商品。为了解决并发问题,我们使⽤了 Python标准库的Queue。Queue是标准库中线程安全的队列(FIFO) 实现,提供了⼀个适⽤于多线程编程的先进先出的数据结构,⾮常适合⽤于⽣产者和消费者线程之间的数据传递。
在这段程序中,我们⾸先将所有1P地址读⼊内存并放⼊Queue中,消费者不断从 Queue中获取商品。需要注意的是,如果我们使⽤Queue 的get⽅法,当Queue中没有商品时,线程将会阻塞等待直到有新的商品为⽌。⽽在这个例⼦中不需要消费者阻塞等待, 因此,使⽤了Queue的get_nowait⽅法。该⽅法在冇商品时直接返回商品,没有商品时抛出Empty异常。消费者线程不断从Queue中获取IP地址,获取到IP地址以后调⽤call_ ping函数判断主机是否可达,直到没有商品以后退出线程。
⼆、端⼝扫描
仅仅知道⽹络上的主机是否可达还不够,很多情况下,我们需要的是⼀个端⼝扫描器。使⽤端⼝扫描
器吋以进⾏安全检测与攻击防范。例如,在2017年5⽉12⽇,全球范围内爆发了基于Windows⽹络共享协议的永恒之蓝(Wannacry)蠕⾍。仅仅五个⼩时,包 括美国、中国、俄罗斯以及整个欧洲在内的100多个国家都不问程度地遭受永恒之蓝病毒攻击,尤其是⾼校、⼤型企业内⽹和政府机构专⽹,被攻击的电脑被⽀付⾼额赎⾦才能解密恢复⽂件,对重要数据造成严重损失。永恒之蓝利⽤Windows系统的445端⼝进⾏蠕⾍攻击,部分运营商已经在主⼲⽹络上封禁了 445端⼝,但是教育⽹以及⼤量企业内⽹并没有此限制,从⽽导致了永恒之蓝蠕⾍的泛滥。
所以作为⼯程师,⼀⽅⾯需要在⽇常维护养成良好的习惯,如配置防⽕墙、进⾏⽹络隔离、关闭不必要的服务、及时更新补丁;另⼀⽅⾯可以掌握⼀些安全相关的⼯具,在⽇常中进⾏安全防范,在紧急悄况下进⾏安全检测。在这⼀⼩节,我们将介绍如何使⽤Python进⾏端⼝扫描。有了端⼝扫描器,我们可以快速了解主机打开了哪些不必要的端⼝,以便及时消灭安全隐患。
在这⼀⼩节中,我们将使⽤Python语⾔编写⼀个端⼝扫描器,然后介绍⼤名⿍⿍的端 ⼝扫描⼯具nmap,最后,通过python-nmap在Python代码中调⽤nmap进⾏端⼝扫描。
1、使⽤Python编写端⼝扫描⼯具
在Linux下,可以使⽤ping命令要判断⼀台主机是否可达,⽽判断⼀个端⼝是否打开可以使⽤telnet命令。我们可以模仿前⾯⼩节中并⾏ping的例⼦,在Python代码中调⽤ telnet命令判断⼀个端⼝是否打
开。但是telnet命令存在⼀个问题,当我们telnet—个不可达的端⼝时,telnet需要很久才能够超时返回,并且telnet命令没有参数控制超时时间。 此外,如果Python标准库中有相应的模块,应该尽可能地使⽤Python的标准库,⽽不是在 Python代码中执⾏Linux命令。这⼀⽅⾯能够增加代码的可读性、可维护性,另⼀⽅⾯也能够保证程序跨平台运⾏。
为了使⽤Python编写端⼝扫描器,我们需要简单了解socket模块。socket模块为操作系统的socket连接提供了⼀个Python接⼝。有了socket模块,我们可以完成任何使⽤ socket的任务。
socket模块提供了⼀个⼯⼚函数socket,socket函数会返冋⼀个socket对象。我们可以给socket函数传递参数,以此创建不同⽹络协议和⽹络类塑的socket对象。默认情况下,socket函数会返回⼀个使⽤TCP协议的socket对象。如下所⽰:
In [1]:import socket
In [2]: s = socket.socket()
In [3]: s.connect(('47.100.98.242',80))
In [4]: s.send("GET/HTTP/1.0".encode())
Out[4]:12
孙艺洲和李金铭
In [5]:v(200))
b'HTTP/1.1400 Bad Request\r\nServer: nginx\r\nDate: Sat,29 Feb 202015:44:51
GMT\r\nContent-Type: text/html\r\nContent-Length:150\r\nConnection: close\r\
n\r\n<html>\r\n<head><title>400 Bad Request</title></head>\r\n<b'
In [6]: s.close()
在这个例⼦中,socket⼯⼚函数以默认参数AF_INET和SOCK_STREAM创建了⼀个 名为s的socket对象,该对象可以在进程间进⾏TCP 通信。创建完对象以后,我们使⽤connect函数连接到远程服务器的80端⼝,并发送⼀个HTTP请求到远程服务器,发送完 毕之后,接收服务器响应的前200个宇节。最后,调⽤socket对象的close⽅法关闭连接。
在这个例⼦中,我们⽤到了 socket⼯⼚函数、socket的connect⽅法、send⽅法、recv ⽅法和close⽅法,这也是socket中最常使⽤的⼀些⽅法。
接下来,我们就看⼀下如何使⽤简单的socket接⼝编写⼀个端⼝扫描器。如下所⽰: