Python网络编程:从基础套接字到Ping扫描应用
1. 基础网络编程:使用套接字实现简单的客户端 - 服务器连接
在网络编程中,理解如何使用套接字(sockets)来实现客户端和服务器之间的通信是非常重要的。为了介绍Python提供的套接字API,我们将创建一个简单的网络服务器和客户端。
1.1 选择IP地址和端口
我们将使用本地回环IP地址 127.0.0.1 ,这个地址在几乎所有系统中都是相同的,并且发送到该地址的任何消息都不会到达外部网络,而是自动返回到本地主机。端口号范围在0到65,535之间,我们应避免使用低于1024的端口,因为这些端口通常分配给标准网络服务。在这个例子中,我们选择使用端口5555,因为它容易记忆。
可以将IP地址想象成邮局的街道地址,而端口则是邮局内的特定邮箱。
1.2 服务器端代码(server.py)
服务器的主要目标是:
1. 设置一个简单的监听套接字。
2. 等待连接请求。
3. 在端口5555上接受连接。
4. 成功连接后向客户端发送消息。
import socket
# Standard Library Socket Module
# Create Socket
myServerSocket = socket.socket()
# Get my local host address
localHost = socket.gethostname()
# Specify a local Port to accept connections on
localPort = 5555
# Bind myServerSocket to localHost and the specified Port
# Note the bind call requires one parameter, but that
# parameter is a tuple (notice the parenthesis usage)
myServerSocket.bind((localHost, localPort))
# Begin Listening for connections
myServerSocket.listen(1)
# Wait for a connection request
# Note this is a synchronous Call
# meaning the program will halt until
# a connection is received.
# Once a connection is received
# we will accept the connection and obtain the
# ipAddress of the connector
print('Python-Forensics .... Waiting for Connection Request')
conn, clientInfo = myServerSocket.accept()
# Print a message to indicate we have received a connection
print('Connection Received From:', clientInfo)
# Send a message to connector using the connection object 'conn'
# that was returned from the myServerSocket.accept() call
# Include the client IP Address and Port used in the response
conn.send('Connection Confirmed:' + 'IP:' + clientInfo[0] + 'Port:' + str(clientInfo[1]))
1.3 客户端代码(client.py)
客户端的主要目标是:
1. 设置一个客户端套接字。
2. 尝试连接到端口5555上的服务器。
3. 等待回复。
4. 打印从服务器收到的消息。
import socket
# Standard Library Socket Module
MAX_BUFFER = 1024
# Set the maximum size to receive
# Create a Socket
myClientSocket = socket.socket()
# Get my local host address
localHost = socket.gethostname()
# Specify a local Port to attempt a connection
localPort = 5555
# Attempt a connection to my localHost and localPort
myClientSocket.connect((localHost, localPort))
# Wait for a reply
# This is a synchronous call, meaning
# that the program will halt until a response is received
# or the program is terminated
msg = myClientSocket.recv(MAX_BUFFER)
print(msg)
# Close the Socket, this will terminate the connection
myClientSocket.close()
1.4 程序执行
我们可以创建两个终端窗口,先运行 server.py ,再运行 client.py 。客户端会从源端口(如59,714,由套接字服务选择)与服务器的目标端口5555进行通信。
下面是这个简单客户端 - 服务器通信的流程图:
graph LR
A[启动服务器] --> B[等待连接请求]
C[启动客户端] --> D[尝试连接服务器]
D --> B
B --> E[接受连接]
E --> F[服务器发送确认消息]
F --> G[客户端接收消息]
G --> H[客户端关闭连接]
2. 网络调查基础:使用Ping扫描发现网络主机
在网络调查中,发现网络上的所有主机(端点)是关键要素之一。这可以通过向网络上的每个可能的IP地址发送Ping(使用Internet控制消息协议,即ICMP)来实现。
2.1 Ping扫描的原理
当我们向一个IP地址发送Ping请求时,如果该地址有响应,我们可以得到两个重要信息:一是该主机存在且可响应;二是响应返回所需的时间。但需要注意的是,许多现代防火墙会阻止ICMP消息,因为黑客可能会利用这些消息对网络进行侦察活动,而且现代操作系统默认也不会响应ICMP。不过,在网络内部,Ping扫描仍然是定位和检测端点的有效方法。
2.2 相关模块介绍
为了实现Ping扫描应用,我们将使用两个特殊模块:
- wxPython :用于构建Ping扫描应用的简单图形用户界面(GUI)。它提供了跨平台的GUI功能,支持Windows、Linux和Mac系统。可以访问 http://www.wxPython.org/ 项目页面获取更多信息并安装适合自己配置的版本。
- Ping.py :一个完全用Python编写的第三方模块,用于处理ICMP操作的细节。该模块可以从 http://www.g-loaded.eu/2009/10/30/Python-ping/ 下载。
以下是Ping.py模块的代码:
#!/usr/bin/env Python
"""
A pure Python ping implementation using raw socket.
Note that ICMP messages can only be sent from processes running as root.
Derived from ping.c distributed in Linux's netkit. That code is
copyright (c) 1989 by The Regents of the University of California.
That code is in turn derived from code written by Mike Muuss of the
US Army Ballistic Research Laboratory in December, 1983 and
placed in the public domain. They have my thanks.
Bugs are naturally mine. I'd be glad to hear about them. There are
certainly word - size dependencies here.
Copyright (c) Matthew Dixon Cowles, <http://www.visi.com/mdc/>.
Distributable under the terms of the GNU General Public License
version 2. Provided with no warranties of any sort.
Original Version from Matthew Dixon Cowles:
-> ftp://ftp.visi.com/users/mdc/ping.py
Rewrite by Jens Diemer:
-> http://www.Python-forum.de/post-69122.html#69122
Rewrite by George Notaras:
-> http://www.g-loaded.eu/2009/10/30/Python-ping/
Revision history
November 8, 2009
-------------
Improved compatibility with GNU/Linux systems.
Fixes by:
* George Notaras -- http://www.g-loaded.eu
Reported by:
* Chris Hallman -- http://cdhallman.blogspot.com
Changes in this release:
- Re-use time.time() instead of time.clock(). The 2007 implementation
worked only under Microsoft Windows. Failed on GNU/Linux.
time.clock() behaves differently under the two OSes[1].
[1] http://docs.Python.org/library/time.html#time.clock
May 30, 2007
----------
little rewrite by Jens Diemer:
- change socket asterisk import to a normal import
- replace time.time() with time.clock()
- delete "return None" (or change to "return" only)
- in checksum() rename "str" to "source_string"
November 22, 1997
-------------
Initial hack. Doesn't do much, but rather than try to guess
what features I (or others) will want in the future, I've only
put in what I need now.
December 16, 1997
--------------
For some reason, the checksum bytes are in the wrong order when
this is run under Solaris 2.X for SPARC but it works right under
Linux x86. Since I don't know just what's wrong, I'll swap the
bytes always and then do an htons().
December 4, 2000
-------------
Changed the struct.pack() calls to pack the checksum and ID as
unsigned. My thanks to Jerome Poincheval for the fix.
Last commit info:
$LastChangedDate: $
$Rev: $
$Author: $
"""
import os, sys, socket, struct, select, time
# From /usr/include/linux/icmp.h; your mileage may vary.
ICMP_ECHO_REQUEST = 8 # Seems to be the same on Solaris.
def checksum(source_string):
"""
I'm not too confident that this is right but testing seems
to suggest that it gives the same answers as in_cksum in ping.c
"""
sum = 0
countTo = (len(source_string)/2)*2
count = 0
while count<countTo:
thisVal = ord(source_string[count + 1])*256 + ord(source_string[count])
sum = sum + thisVal
sum = sum & 0xffffffff # Necessary?
count = count + 2
if countTo<len(source_string):
sum = sum + ord(source_string[len(source_string) - 1])
sum = sum & 0xffffffff # Necessary?
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
answer = ~sum
answer = answer & 0xffff
# Swap bytes. Bugger me if I know why.
answer = answer >> 8 | (answer << 8 & 0xff00)
return answer
def receive_one_ping(my_socket, ID, timeout):
"""
receive the ping from the socket.
"""
timeLeft = timeout
while True:
startedSelect = time.time()
whatReady = select.select([my_socket], [], [], timeLeft)
howLongInSelect = (time.time() - startedSelect)
if whatReady[0] == []: # Timeout
return
timeReceived = time.time()
recPacket, addr = my_socket.recvfrom(1024)
icmpHeader = recPacket[20:28]
type, code, checksum, packetID, sequence = struct.unpack(
"bbHHh", icmpHeader
)
if packetID == ID:
bytesInDouble = struct.calcsize("d")
timeSent = struct.unpack("d", recPacket[28:28 + bytesInDouble])[0]
return timeReceived - timeSent
timeLeft = timeLeft - howLongInSelect
if timeLeft <= 0:
return
def send_one_ping(my_socket, dest_addr, ID):
"""
Send one ping to the given >dest_addr<.
"""
dest_addr = socket.gethostbyname(dest_addr)
# Header is type (8), code (8), checksum (16), id (16), sequence (16)
my_checksum = 0
# Make a dummy header with a 0 checksum.
header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, ID, 1)
bytesInDouble = struct.calcsize("d")
data = (192 - bytesInDouble) * "Q"
data = struct.pack("d", time.time()) + data
# Calculate the checksum on the data and the dummy header.
my_checksum = checksum(header + data)
# Now that we have the right checksum, we put that in. It's just easier
# to make up a new header than to stuff it into the dummy.
header = struct.pack(
"bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), ID, 1
)
packet = header + data
my_socket.sendto(packet, (dest_addr, 1)) # Don't know about the 1
def do_one(dest_addr, timeout):
"""
Returns either the delay (in seconds) or none on timeout.
"""
icmp = socket.getprotobyname("icmp")
try:
my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
except socket.error, (errno, msg):
if errno == 1:
# Operation not permitted
msg = msg + (
" - Note that ICMP messages can only be sent from processes"
" running as root."
)
raise socket.error(msg)
raise # raise the original error
my_ID = os.getpid() & 0xFFFF
send_one_ping(my_socket, dest_addr, my_ID)
delay = receive_one_ping(my_socket, my_ID, timeout)
my_socket.close()
return delay
def verbose_ping(dest_addr, timeout = 2, count = 4):
"""
Send >count< ping to >dest_addr< with the given >timeout< and
display the result.
"""
for i in xrange(count):
print "ping %s. . ." % dest_addr,
try:
delay = do_one(dest_addr, timeout)
except socket.gaierror, e:
print "failed. (socket error:'%s')" % e[1]
break
if delay == None:
print "failed. (timeout within %ssec.)" % timeout
else:
delay = delay * 1000
print "get ping in %0.4fms" % delay
print
if __name__ == '__main__':
verbose_ping("heise.de")
verbose_ping("google.com")
verbose_ping("a-test-url-taht-is-not-available.com")
verbose_ping("192.168.1.1")
通过以上内容,我们了解了如何使用Python的套接字实现简单的客户端 - 服务器通信,以及如何利用Ping扫描来发现网络上的主机。在下半部分,我们将详细介绍如何使用wxPython构建Ping扫描应用的GUI界面。
3. 构建Ping扫描应用的GUI界面
为了让Ping扫描应用更加易用,我们将使用wxPython构建一个简单的图形用户界面(GUI)。这个界面将包含两个按钮(Scan和Exit)以及几个旋转控件,用于指定基本IP地址和本地主机范围。
3.1 代码实现(guiPing.py)
#
# Python Ping Sweep GUI Application
#
import wxversion
# Specify the proper version of wxPython
wxversion.select("2.8")
# Import the necessary modules
import wx
# Import the GUI module wx
import sys
# Import the standard library module sys
import ping
# Import the ICMP Ping Module
import socket
# Import the standard library module socket
from time import gmtime, strftime # import time functions
#
# Event Handler for the pingScan Button Press
# This is executed each time the Scan Button is pressed on the GUI
#
def pingScan(event):
# Since the user specifies a range of Hosts to Scan, I need to verify
# that the startHost value is <= endHost value before scanning
# this would indicate a valid range
# If not I need to communicate the error with the user
if hostEnd.GetValue() < hostStart.GetValue():
# This is an improper setting
# Notify the user using a wx.MessageDialog Box
dlg = wx.MessageDialog(mainWin, "Invalid Local Host Selection", "Confirm", wx.OK | wx.ICON_EXCLAMATION)
result = dlg.ShowModal()
dlg.Destroy()
return
# If we have a valid range update the Status Bar
mainWin.StatusBar.SetStatusText('Executing Ping Sweep .... Please Wait')
# Record the Start Time and Update the results window
utcStart = gmtime()
utc = strftime("%a, %d %b %Y %X +0000", utcStart)
results.AppendText("\n\nPing Sweep Started: " + utc + "\n\n")
# Similar to the example script at the beginning of the chapter
# I need to build the base IP Address String
# Extract data from the ip Range and host name user selections
# Build a Python List of IP Addresses to Sweep
baseIP = str(ipaRange.GetValue()) + '.' + str(ipbRange.GetValue()) + '.' + str(ipcRange.GetValue()) + '.'
ipRange = []
for i in range(hostStart.GetValue(), (hostEnd.GetValue() + 1)):
ipRange.append(baseIP + str(i))
# For each of the IP Addresses in the ipRange List, Attempt an PING
for ipAddress in ipRange:
try:
# Report the IP Address to the Window Status Bar
# Prior to the attempt
mainWin.StatusBar.SetStatusText('Pinging IP: ' + ipAddress)
# Perform the Ping
delay = ping.do_one(ipAddress, timeout=2)
# Display the IP Address in the Main Window
results.AppendText(ipAddress + '\t')
if delay != None:
# If Successful (i.e. no timeout) display
# the result and response time
results.AppendText('Response Success')
results.AppendText('Response Time: ' + str(delay) + ' Seconds')
results.AppendText("\n")
else:
# If delay == None, then the request timed out
# Report the Response Timeout
results.AppendText('Response Timeout')
results.AppendText("\n")
except socket.error, e:
# If any socket Errors occur Report the offending IP
# along with any error information provided by the socket
results.AppendText(ipAddress)
results.AppendText('Response Failed:')
results.AppendText(e.message)
results.AppendText("\n")
# Once all ipAddresses are processed
# Record and display the ending time of the sweep
utcEnd = gmtime()
utc = strftime("%a, %d %b %Y %X +0000", utcEnd)
results.AppendText("\nPing Sweep Ended: " + utc + "\n\n")
# Clear the Status Bar
mainWin.StatusBar.SetStatusText('')
return
# End Scan Event Handler =========================
#
# Program Exit Event Handler
# This is executed when the user presses the exit button
# The program is terminated using the sys.exit() method
#
def programExit(event):
sys.exit()
# End Program Exit Event Handler =================
#
# Setup the Application Windows =========================
#
# This section of code sets up the GUI environment
#
# Instantiate a wx.App() object
app = wx.App()
# define the main window including the size and title
mainWin = wx.Frame(None, title="Simple Ping (ICMP) Sweeper 1.0", size=(1000, 600))
# define the action panel, this is the area where the buttons and spinners
# are located
panelAction = wx.Panel(mainWin)
# define action buttons
# I'm creating two buttons, one for Scan and one for Exit
# Notice that each button contains the name of the function that will
# handle the button press event -- pingScan and ProgramExit respectively
scanButton = wx.Button(panelAction, label='Scan')
scanButton.Bind(wx.EVT_BUTTON, pingScan)
exitButton = wx.Button(panelAction, label='Exit')
exitButton.Bind(wx.EVT_BUTTON, programExit)
# define a Text Area where I can display results
results = wx.TextCtrl(panelAction, style=wx.TE_MULTILINE | wx.HSCROLL)
# Base Network for Class C IP Addresses have 3 components
# For class C addresses, the first 3 octets (24 bits) define the network
# e.g., 127.0.0
# the last octet (8 bits) defines the host i.e., 0 - 255
# Thus I setup 3 spin controls one for each of the 3 network octets
# I also set the default value to 127.0.0 for convenience
ipaRange = wx.SpinCtrl(panelAction, -1, '')
ipaRange.SetRange(0, 255)
ipaRange.SetValue(127)
ipbRange = wx.SpinCtrl(panelAction, -1, '')
ipbRange.SetRange(0, 255)
ipbRange.SetValue(0)
ipcRange = wx.SpinCtrl(panelAction, -1, '')
ipcRange.SetRange(0, 255)
ipcRange.SetValue(0)
# Also, I'm adding a label for the user
ipLabel = wx.StaticText(panelAction, label="IP Base: ")
# Next, I want to provide the user with the ability to set the host range
# they wish to scan. Range is 0 - 255
hostStart = wx.SpinCtrl(panelAction, -1, '')
hostStart.SetRange(0, 255)
hostStart.SetValue(1)
hostEnd = wx.SpinCtrl(panelAction, -1, '')
hostEnd.SetRange(0, 255)
hostEnd.SetValue(10)
HostStartLabel = wx.StaticText(panelAction, label="Host Start: ")
HostEndLabel = wx.StaticText(panelAction, label="Host End: ")
# Now I create BoxSizer to automatically align the different components
# neatly within the panel
# First, I create a horizontal Box
# I'm adding the buttons, ip Range and Host Spin Controls
actionBox = wx.BoxSizer()
actionBox.Add(scanButton, proportion=1, flag=wx.LEFT, border=5)
actionBox.Add(exitButton, proportion=0, flag=wx.LEFT, border=5)
actionBox.Add(ipLabel, proportion=0, flag=wx.LEFT, border=5)
actionBox.Add(ipaRange, proportion=0, flag=wx.LEFT, border=5)
actionBox.Add(ipbRange, proportion=0, flag=wx.LEFT, border=5)
actionBox.Add(ipcRange, proportion=0, flag=wx.LEFT, border=5)
actionBox.Add(HostStartLabel, proportion=0, flag=wx.LEFT | wx.CENTER, border=5)
actionBox.Add(hostStart, proportion=0, flag=wx.LEFT, border=5)
actionBox.Add(HostEndLabel, proportion=0, flag=wx.LEFT | wx.CENTER, border=5)
actionBox.Add(hostEnd, proportion=0, flag=wx.LEFT, border=5)
# Next I create a Vertical Box that I place the Horizontal Box Inside
# Along with the results text area
vertBox = wx.BoxSizer(wx.VERTICAL)
vertBox.Add(actionBox, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
vertBox.Add(results, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.BOTTOM | wx.RIGHT, border=5)
# I'm adding a status bar to the main windows to display status messages
mainWin.CreateStatusBar()
# Finally, I use the SetSizer function to automatically size the windows
# based on the definitions above
panelAction.SetSizer(vertBox)
mainWin.Show()
app.MainLoop()
3.2 代码说明
- 事件处理函数 :
-
pingScan(event):当用户点击“Scan”按钮时执行。首先检查用户指定的主机范围是否有效,如果无效则弹出消息框通知用户;如果有效,则记录扫描开始时间,构建要扫描的IP地址列表,并对每个IP地址进行Ping扫描,记录扫描结果和响应时间,最后记录扫描结束时间。 -
programExit(event):当用户点击“Exit”按钮时执行,使用sys.exit()方法终止程序。 -
GUI布局 :
- 使用
wx.App()创建应用程序对象。 - 创建主窗口
mainWin,并在其中添加面板panelAction。 - 在面板中添加“Scan”和“Exit”按钮,并绑定相应的事件处理函数。
- 使用
wx.SpinCtrl创建旋转控件,用于用户指定IP地址的各个部分和主机范围。 - 使用
wx.TextCtrl创建文本区域results,用于显示扫描结果。 - 使用
wx.BoxSizer进行布局管理,使界面元素排列整齐。 - 为主窗口添加状态栏,用于显示扫描状态信息。
以下是Ping扫描GUI应用的操作步骤:
1. 确保已经安装了wxPython和Ping.py模块。
2. 运行 guiPing.py 脚本,需要以管理员权限运行(因为发送ICMP消息需要root权限)。
3. 在界面中设置IP地址的基本部分和要扫描的主机范围。
4. 点击“Scan”按钮开始Ping扫描,扫描结果将显示在文本区域中。
5. 点击“Exit”按钮退出程序。
下面是Ping扫描GUI应用的流程图:
graph LR
A[启动程序] --> B[显示GUI界面]
B --> C{点击Scan按钮}
C -- 是 --> D{主机范围是否有效}
D -- 是 --> E[记录开始时间]
E --> F[构建IP地址列表]
F --> G[对每个IP进行Ping扫描]
G --> H{是否有响应}
H -- 是 --> I[记录响应时间和结果]
H -- 否 --> J[记录超时结果]
I --> K{是否扫描完所有IP}
J --> K
K -- 否 --> G
K -- 是 --> L[记录结束时间]
L --> M[显示扫描结果]
D -- 否 --> N[弹出错误消息框]
C -- 否 --> O{点击Exit按钮}
O -- 是 --> P[退出程序]
O -- 否 --> C
总结
通过本文,我们学习了如何使用Python的套接字API实现简单的客户端 - 服务器通信,以及如何利用Ping扫描发现网络上的主机。同时,我们还使用wxPython构建了一个简单的GUI界面,使Ping扫描应用更加易用。这些知识对于网络编程和网络调查都非常有帮助。在实际应用中,我们可以根据需要对代码进行扩展和优化,以满足不同的需求。例如,可以增加更多的错误处理机制,提高程序的稳定性;或者添加多线程功能,提高Ping扫描的效率。
超级会员免费看
74

被折叠的 条评论
为什么被折叠?



