《Python网络编程》Part4 IPv6、Unix域套接字和网络接口
1、把本地端口转发到远程主机
有时,你需要创建一个本地端口转发器,把本地端口发送的流量全部重定向到特定的远程主机上。利用这个功能,可以让用户只能访问特定的网站,而不能访问其他的网站。
我们来编写一个本地端口转发脚本,把8800端口接收到的所有流量重新定向到谷歌的首页(http://www.google.com)。我们可以把本地主机和远程主机连同端口号仪器传入脚本。简单起见,这里只指定本地端口号,因为我们知道web服务器运行在80端口上。
#coding:utf-8
import argparse
LOCAL_SERVER_HOST = 'localhost'
REMOTE_SERVER_HOST ='www.google.com'#'www.sogou.com' #'www.google.com'
BUFSIZE = 4096
import asyncore
import socket
#定义类
class PortForwarder(asyncore.dispatcher):
def __init__(self,ip,port,remoteip,remoteport,backlog=5):
asyncore.dispatcher.__init__(self)
self.remoteip = remoteip
self.remoteport = remoteport
self.create_socket(socket.AF_INET,socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind((ip,port))
self.listen(backlog)
def handle_accept(self):
conn, addr = self.accept()
print "Connected to:",addr
Sender(Receiver(conn),self.remoteip,self.remoteport)
class Receiver(asyncore.dispatcher):
def __init__(self,conn):
asyncore.dispatcher.__init__(self,conn)
self.from_remote_buffer = ' '
self.to_remote_buffer = ' '
self.sender = None
def handle_connect(self):
pass
def handle_read(self):
read = self.recv(BUFSIZE)
self.from_remote_buffer += read
def writable(self):
return (len(self.to_remote_buffer)>0)
def handle_write(self):
sent = self.send(self.to_remote_buffer)
self.to_remote_buffer = self.to_remote_buffer[sent:]
def handle_close(self):
self.close()
if self.sender:
self.sender.close()
class Sender(asyncore.dispatcher):
def __init__(self,receiver,remoteport):
asyncore.dispatcher.__init__(self)
self.recevier = receiver
receiver.sender = self
self.create_socket(socket.AF_INET,socket.SOCK_STREAM)
self.connect((remoteaddr,remoteport))
def handle_connect(self):
pass
def handle_read(self):
read = self.recv(BUFSIZE)
self.recevier.to_remote_buffer += read
def writable(self):
return (len(self.recevier.from_remote_buffer) > 0)
def handle_write(self):
sent = self.send(self.recevier.from_remote_buffer)
self.recevier.from_remote_buffer = self.recevier.from_remote_buffer[sent:]
def handle_close(self):
self.close()
self.recevier.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Stackless Socket Server Example')
parser.add_argument('--local-host',action="store",dest = "local_host")
parser.add_argument('--local-port',action="store",dest="local_port",type=int,required=True)
parser.add_argument('--remote-host',action="store",dest="remote_host",default=REMOTE_SERVER_HOST)
parser.add_argument('--remote-port',action="store",dest="remote_port",type=int,default=80)
given_args = parser.parse_args()
local_host,remote_host = given_args.local_host,given_args.remote_host
local_port,remote_port = given_args.local_port,given_args.remote_port
print "Starting port forwarding local %s:%s => remote %s:%s " %(local_host,local_port,remote_host,remote_port)
PortForwarder(local_host,local_port,romote_host,remote_port)
asyncore.loop()
运行脚本,port_forwarding.py --local-port=8800
然后打开浏览器,访问http://localhost:8800。浏览器会把我们带到谷歌的首页,在命令行中输出类似下面的信息:
Connect to: ('127.0.0.1',38557)
原理:
我们创建了一个端口转发类PortForwarder,继承自asyncore.dispatcher。asyncore.dispatcher类包装了一个套接字对象,还提供了一些帮助方法用于处理特定的时间,例如链接成功或客户端连接到服务器套接字。你可以选择重定义这些方法,在上面的脚本中我们只重定义了handle_accept()方法。
另外两个类也继承自asyncore.dispatcher。Receiver类处理进入的客户端请求,Sender类接收一个Reciever类实例,把数据发送给客户端。如你所见,这两个类都重新定义了handle_read()、handle_write()三个方法,目的是实现远程主机和本地客户端之间的双向通信。
概括来说,PortForwarder类在一个本地套接字中保存进入的客户端请求,然后把这个套接字传给Sender类实例,再使用Receiver类实例发起与远程主机指定端口之间的双向通信。
2、通过ICMP查验网络中的主机
ICMP查验(ICMP ping,ICMP是Internet Control Message Protocol的简称,意思是“网络控制报文协议”)是你见过的最普通的网络扫描类型。ICMP查验做起来很简单,打开命令行或终端,输入ping www.google.com即可。这里展示了一个简单的Python查验脚本。
运行下面这个脚本,需要在超级用户或者管理员权限才行。
你可以偷个懒,在Python脚本中调用系统中的ping命令,如下所示:
import subprocess
import shlex
command_line = "ping -c 1 www.google.com"
args = shlex.split(command_line)
try:
subprocess.check_call(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
print "Google web server is up!"
except subprocess.CalledProcessError:
print "Failed to get ping."
然而很多情况下,系统中的平可执行文件不可用,或者无法访问。此时我们需要一个纯粹的Python脚本实现查验。 注意,这个脚本要使用超级用户或者管理员的身份运行。
#coding :utf-8
import os
import argparse
import socket
import struct
import select
import time
ICMP_ECHO_REQUEST = 8 #platform specific
DEFAULT_TIMEOUT = 2
DEFAULT_COUNT = 4
class Pinger(object):
"""Pings to a host -- the pythonic way"""
def __init__(self,target_host,count=DEFAULT_COUNT,timeout=DEFAULT_TIMEOUT):
self.target_host = target_host
self.count = count
self.timeout = timeout
def do_checksum(self ,source_string):
"""Verify the packet integritity"""
sum = 0
max_count = (len(source_string)/2)*2
count = 0
while count < max_count:
val = ord(source_string[len(source_string)-1])*256 + ord(source_string[count])
sum = sum +val
sum = sum & 0xffffffff
count = count + 2
if max_count < len(source_string):
sum = sum +ord(source_string[len(source_string)-1])
sum = sum & 0xffffffff
sum = (sum >>16) + (sum&0xffff)
sum = sum +(sum>>16)
answer = ~sum
answer = answer&0xffff
answer = answer>>8 | (answer<<8 & 0xff00)
return answer
def receive_pong(self,sock,ID,timeout):
"""Receive ping from the socket"""
time_remaining = timeout
while True:
start_time = time.time()
readable = select.select([sock],[],[],time_remaining)
time_spent = (time.time()-start_time)
if readable[0] == []: #Timeout
return
time_received = time.time()
recv_packet,addr = sock.recvfrom(1024)
icmp_header = recv_packet[20:28]
type ,code,checksum,packet_ID,sequence = struct.unpack("bbHHh",icmp_header)
if packet_ID ==ID :
bytes_In_double = struct.calcsize("d")
time_sent = struct.unpack("d",recv_packet[28:28]+bytes_In_double)[0]
return time_received - time_sent
time_remaining = time_remaining -time_spent
if time_remaining <= 0:
return
def send_ping(self,sock,ID):
"""Send ping to the target host"""
target_addr = socket.gethostbyname(self.target_host)
my_checksum = 0
#Create a dumy header woth a 0 checksum.
header = struct.pack("bbHHh",ICMP_ECHO_REQUEST,0,my_checksum,ID,1)
bytes_In_double = struct.calcsize("d")
data = (192 - bytes_In_double)* "Q"
data = struct.pack("d",time.time()) + data
#Get the checksum on the data and the dummy header
my_checksum = self.do_checksum(header + data)
header =struct.pack("bbHHh",ICMP_ECHO_REQUEST,0,socket.htons(my_checksum),ID,1)
packet = header + data
sock.sendto(packet,(target_addr,1))
def ping_once(self):
"""Return the delay (in seconds) or none on time_out."""
icmp = socket.getprotobyname("icmp")
try:
sock = socket.socket(socket.AF_INET,socket.SOCK_RAW,icmp)
except socket.error,(errno,msg):
if errno == 1 :
# Not superuser, so operation not permitted
msg += "ICMP messages can only be sent from root user processes"
raise socket.error(msg)
except Exception,e:
print "Exception: %s" %(e)
my_ID = os.getpid() & 0xFFFF
self.send_ping(sock,my_ID)
delay = self.receive_pong(sock,my_ID,self.time_out)
sock.close()
return delay
def ping(self):
"""Run the ping process."""
for i in xrange(self.count):
print "Ping to %s ..." %self.target_host
try:
delay = self.ping_once()
except socket.gaierror,e:
print "Ping failed.(socket error: '%s')"%e[1]
break
if delay == None:
print "Ping failed.(timeout within %ssec .)" % self.timeout
else:
delay = delay * 1000
print "Get pong in %0.4fms " %delay
if __name__ == '__main__':
#--target_host ='http://www.baidu.com'
parser = argparse.ArgumentParser(description = 'Python ping')
parser.add_argument('--target-host',action="store",dest = "target_host",required = True)
given_args = parser.parse_args()
target_host = given_args.target_host
pinger = Pinger(target_host=target_host)
pinger.ping()
注:
1)、我们定义send_ping()方法,把查验请求的数据发送给目标主机。而且,在这个方法中还要调用do_checksum()方法,检查查验数据的完整性。
2)我们定义ping_once()方法,只向目标主句发送一次查验。在这个方法中,ICMP协议传给socket()方法,创建一个原始的ICMP套接字。异常处理代码负责处理未使用超级用户运行脚本的情况,以及其他套接字错误。
3)我们定义ping()方法,这个方法中有一个for循环,在for循环中调用ping_once()方法count次。延迟时间从检查的响应中获取,单位为秒。如果没有返回延迟时间,就意味着查验失败。
原理分析:
Pinger类定义了很多有用的方法,初始化时创建了几个变量,其值由用户指定,或者有默认值,如下所示:
- target_host:要查验的目标主机;
- count:查验次数;
- timeout:这个值决定何时终止未完成的查验操作。
3、等待远程网络服务上线
如果你的设备上运行这个一个web服务器,例如Apache,运行这个脚本后会看到如下输出(省略)。
现在停止Apache进程,在运行这个脚本,然后重启Apache。此时看到的输出会有所不同,此处省略。而且可以看到等待Apache Web服务器上线的过程。
#coding: utf-8
import argparse
import socket
import errno
from time import time as now
DEFAULT_TIMEOUT = 120
DEFAULT_SERVER_HOST = 'localhost'
DEFAULT_SERVER_PORT = 80
class NetServiceChecker(object):
"""Wait for a network service to come online"""
def __init__(self,host,port,timeout=DEFAULT_TIMEOUT):
self.host = host
self.port = port
self.timeout = timeout
self.sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
def end_wait(self):
"""Check the service"""
if self.sock.close():
end_time = now() + self.timeout
while True:
try:
if self.timeout:
next_timeout = end_time - now()
if next_timeout < 0 :
return False
else:
print "Seting socket next timeout %ss " %round(next_timeout)
slef.sock.settimeout(next_timeout)
self.sock.connect((self.host,self.port))
#handle excptions
except socket.timeout,err:
if self.timeout:
return False
except socket.error,err:
print "Exception: %s" %err
else :# if all goes well
self.end_wait()
return True
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Wait for Network Service')
parser.add_argument('--host',action="store",dest="host",default = DEFAULT_SERVER_HOST)
parser.add_argument('--port',action = "store",dest = "port",type = int ,default= DEFAULT_SERVER_PORT)
parser.add_argument('--timeout',action = "store",dest="timeout",type=int,default = DEFAULT_TIMEOUT)
given_parse = parser.parse_args()
host ,port , timeout = given_parse.host,given_parse.port,given_parse.timeout
service_checker = NetServiceChecker(host,port,timeout=timeout)
print "Checking for network service %s : %s ..." %(host,port)
if service_checker.check():
print "Service is avaiable again!"
原理分析:
上述脚本使用argparse模板接收用户的输入,处理主机名、端口和超时时间。超时时间指等待所需网络服务的时间。这个脚本创建了一个NetSerciceChecker类实例,然后调用check()方法。这个方法计算等待的最后结束时间,并使用套接字的settimeout()方法控制每次循环的结束时间,即next_timeout。然后check()方法调用套接字的connect()方法在超时时间到达之前测试所需网络服务是否可用。check()方法还能捕获套接字超时异常,以及比较套接字超时时间和用户指定的超时时间。
4、枚举设备中的接口
在Python中列出设备中的网络接口并不难。有很多第三方库可以使用,秩序几行代码即可。不过我们来看一下如何只使用套接字调用完成这一操作。
这个攻略需要在Linux设备中运行。弱项列出可用的网络接口,可以执行下面的命令:
$ /sbin/ifconfig
list_network_interfaces.py: