《Python网络编程》Part4 IPv6、Unix域套接字和网络接口

 《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:这个值决定何时终止未完成的查验操作。
        再seng_ping()方法中获取了目标主机的DNS主机名,然后使用struct模块创建了一个ICMP_ECHO_REQUEST数据包。在这个方法中,一定要使用do_checksum()方法检查数据的完整性。do_checksum()方法接收一个源字符串,经过处理之后生成一个特有的校验和。在接受端receive_pong()方法在未到达超时时间之前一直等待响应,或者直接接收响应,然后抓取ICMP响应首部,对比数据包ID,再计算请求-响应循环的延迟时间。

       

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:




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值