1.ç¨vbç¼åä¸ä¸ªè°ç¨ping.exeç¨åºã
2.ping命令全链路分析(2)
3.ping命令全链路分析(3)-用户态数据包构造与传递
ç¨vbç¼åä¸ä¸ªè°ç¨ping.exeç¨åºã
'æ¹è¯äºä¸
'åæ¥4ç§å·ä¸æ¬¡ï¼ç°å¨0.3ç§å·ä¸æ¬¡
'ç°å¨åªè¿å毫ç§äº
'ä½ æ¯ä¸æ¯ä¸ºäºç©å±±å£å±±åï¼
'2个TextBox :
Text1 - è¾å ¥ip
Text2 - æ¾ç¤ºç»æ
'1个Timer : Timer1
'1个CommandButton : Command1
Option Explicit
Private Const NORMAL_PRIORITY_CLASS = &H&
Private Const STARTF_USESTDHANDLES = &H&
Private Const STARTF_USESHOWWINDOW = &H1
Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As Long
bInheritHandle As Long
End Type
Private Type STARTUPINFO
cb As Long
lpReserved As Long
lpDesktop As Long
lpTitle As Long
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessID As Long
dwThreadID As Long
End Type
Const SWP_NOMOVE = &H2
Const SWP_NOSIZE = &H1
Const FLAG = SWP_NOMOVE Or SWP_NOSIZE
Const HWND_TOPMOST = -1
Const HWND_NOTOPMOST = -2
Const HWND_TOP = 0
Const HWND_BOTTOM = 1
Dim Proc As PROCESS_INFORMATION 'è¿ç¨ä¿¡æ¯
Dim Start As STARTUPINFO 'å¯å¨ä¿¡æ¯
Dim SecAttr As SECURITY_ATTRIBUTES 'å®å ¨å±æ§
Dim hReadPipe As Long '读å管éå¥æ
Dim hWritePipe As Long 'åå ¥ç®¡éå¥æ
Dim lngBytesRead As Long '读åºæ°æ®çåèæ°
Dim strBuffer As String * '读å管éçå符串buffer
Dim Command As String 'DOSå½ä»¤
Dim ret As Long 'APIå½æ°è¿åå¼
Private Declare Function SetWindowPos Lib "user" _
(ByVal hwnd As Long,实现 ByVal hWndInsertAfter As Long, ByVal x As Long, _
ByVal y As Long, ByVal cx As Long, ByVal cy As Long, _
ByVal wFlags As Long) As Long
Private Declare Function CreatePipe Lib "kernel" (phReadPipe As Long, phWritePipe As Long, lpPipeAttributes As SECURITY_ATTRIBUTES, ByVal nSize As Long) As Long
Private Declare Function CreateProcess Lib "kernel" Alias "CreateProcessA" (ByVal lpApplicationName As String, ByVal lpCommandLine As String, lpProcessAttributes As SECURITY_ATTRIBUTES, lpThreadAttributes As SECURITY_ATTRIBUTES, ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, lpEnvironment As Any, ByVal lpCurrentDriectory As String, lpStartupInfo As STARTUPINFO, lpProcessInformation As PROCESS_INFORMATION) As Long
Private Declare Function ReadFile Lib "kernel" (ByVal hFile As Long, ByVal lpBuffer As String, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, ByVal lpOverlapped As Long) As Long
Private Declare Function CloseHandle Lib "kernel" (ByVal hObject As Long) As Long
Private Sub Command1_Click()
If Not InitPipe Then
Exit Sub
Else
'init
Dim s As String
s = ReadPipe
'Me.Text2.Text = s
Me.Timer1.Enabled = True
End If
End Sub
Private Sub Form_Load()
Call SetWindowPos(Me.hwnd, HWND_TOPMOST, 0, 0, 0, 0, FLAG)
Me.Command1.Caption = "Start"
Me.Timer1.Enabled = False
Me.Timer1.Interval =
Me.Text1.Text = "..."
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
ClosePipe
End Sub
Private Sub Timer1_Timer()
Dim strPipe As String
On Error Resume Next
strPipe = ReadPipe()
If Len(strPipe) > 0 Then
If InStr(1, strPipe, "time") > 0 Then
Dim lPosStart As Long
Dim lPosEnd As Long
Dim sMS As String
lPosStart = InStr(strPipe, "time=")
lPosEnd = InStr(strPipe, "ms")
sMS = Mid(strPipe, lPosStart + 5, lPosEnd - lPosStart - 5)
'Text2.Text = Now & "==============>" & vbCrLf & strPipe & vbCrLf & Text2.Text
Text2.Text = sMS & vbCrLf & Text2.Text
End If
End If
End Sub
Private Function InitPipe() As Boolean
'设置å®å ¨å±æ§
With SecAttr
.nLength = LenB(SecAttr)
.bInheritHandle = True
.lpSecurityDescriptor = 0
End With
'å建管é
ret = CreatePipe(hReadPipe, hWritePipe, SecAttr, 0)
If ret = 0 Then
MsgBox "æ æ³å建管é", vbExclamation, "é误"
GoTo ErrHdr
End If
'设置è¿ç¨å¯å¨åçä¿¡æ¯
With Start
.cb = LenB(Start)
.dwFlags = STARTF_USESHOWWINDOW Or STARTF_USESTDHANDLES
.hStdOutput = hWritePipe '设置è¾åºç®¡é
.hStdError = hWritePipe '设置é误管é
End With
'å¯å¨è¿ç¨
Command = "c:\windows\system\ping.exe -t " & Me.Text1.Text 'DOSè¿ç¨ä»¥ipconfig.exe为ä¾
ret = CreateProcess(vbNullString, Command, SecAttr, SecAttr, True, NORMAL_PRIORITY_CLASS, ByVal 0, vbNullString, Start, Proc)
If ret = 0 Then
MsgBox "æ æ³å¯å¨æ°è¿ç¨", vbExclamation, "é误"
ret = CloseHandle(hWritePipe)
ret = CloseHandle(hReadPipe)
GoTo ErrHdr
End If
If False Then
ErrHdr:
InitPipe = False
Exit Function
End If
InitPipe = True
End Function
Private Function ReadPipe() As String
Dim lpOutputs As String
'å 为æ éåå ¥æ°æ®ï¼æ以å å ³éåå ¥ç®¡éãèä¸è¿éå¿ é¡»å ³éæ¤ç®¡éï¼å¦åå°æ æ³è¯»åæ°æ®
ret = CloseHandle(hWritePipe)
'ä»è¾åºç®¡é读åæ°æ®ï¼æ¯æ¬¡æå¤è¯»ååè
ret = ReadFile(hReadPipe, strBuffer, , lngBytesRead, ByVal 0)
lpOutputs = lpOutputs & Left(strBuffer, lngBytesRead)
ReadPipe = lpOutputs
End Function
Private Sub ClosePipe()
On Error Resume Next
'读åæä½å®æï¼å ³éåå¥æ
ret = CloseHandle(Proc.hProcess)
ret = CloseHandle(Proc.hThread)
ret = CloseHandle(hReadPipe)
End Sub
ping命令全链路分析(2)
本文使用 Zhihu On VSCode 创作并发布
上篇文章对开源网络协议栈实现 tapip 触发进行了分析,探讨了执行 ping 命令时,源码数据包是实现如何到达网络协议栈的。本文将继续探讨 ping 命令与网络协议栈的源码联系。目前广泛使用的实现网络协议栈是五层协议划分:应用层、传输层、源码职业教育平台源码网络层、实现链路层和物理层。源码ping 命令采用的实现 ICMP 协议位于网络层,但特别之处在于 ICMP 报文是源码封装在 IP 报文之内的。下文将从 ICMP 协议开始分析。实现
ICMP 协议
ping 命令的源码执行过程实际上包含了源端向目的端发送 ICMP 请求报文和目的端向源端发送 ICMP 回复报文的过程。ICMP 报文头包含了 ICMP type、实现code、源码id、实现seq 等字段,报文头部为 字节,payload 部分数据长度为可变长度。
ICMP 报文头部包含 8bit 类型码 type、8bit 代码 code 和 bit 校验和 checksum,其余部分内容和类型码 type 相关。任务网源码ICMP 报文中定义 type 字段包含以下几种,type 字段与 code 的详细对应关系见附录 1:
其中,ping 命令使用的报文类型为响应请求和响应应答,其报文格式如图:
ICMP 响应请求
在 tapip 中,ICMP 响应请求报文构造是在 ping.c:send_packet() 函数中完成的。ICMP 报文填充构建代码如下:
根据上一篇文章的分析,tapip 采用一个 tap 设备作为虚拟网卡,ICMP 数据报文最终通过 wirte() 接口写入 tap 设备文件中,最终被 Linux 内核中的网络协议栈处理。这里还是网贷系统源码先从 tapip 出发,研究下网络协议栈中如何处理 ICMP 响应请求报文。在 tapip 源码中,处理 ICMP 响应请求报文在函数 icmp_echo_request() 中,其函数调用栈如下:
在 Linux 系统中,数据包到达网络设备后会触发中断,网卡驱动程序将对应数据包传递到内核网络协议栈处理,处理结果通过系统调用接口返回给应用程序(ping 应用)。
tapip 作为一种用户态实现,网络设备 net device 是通过 tap 设备模拟的,tap 设备文件描述符中被写入数据包就相当于网卡设备接收到网络数据包;
网卡驱动程序的传奇世界手游源码工作对应 tapip 中 netdev_interrupt() 到 veth_rx() 之间的过程:首先在中断处理函数中调用 veth_poll() 函数采用轮询的方式检查 tap 设备的文件描述符是否有写入事件;当发生写入事件时,veth_rx() 函数被调用,从文件描述符中读取数据包,并传递到网络协议栈中处理,此时,网络协议栈处理的入口 net_in() 被调用。
网络协议栈按照网络分层模型进行处理:
ICMP 响应回复
ICMP 响应回复的处理过程与接收侧处理 ICMP 响应请求的流程基本一致,不同点在于最后 icmp 报文响应的处理,其 type 为 0,对应的处理函数为 icmp_echo_reply(),具体函数调用栈如下:
总结
本文主要分析了用户态网络协议栈 tapip 处理 ping 命令对应的scala 源码 ICMP 报文的过程,后续将结合 Linux 内核分析这个过程在内核中是如何处理的,另外还会分析下 ARP 协议的实现。
学海无涯,感觉 tapip 的实现逻辑清晰,读起来非常舒服,非常推荐对网络感兴趣的同学学习参考。
(最近特别水逆,希望能早日走出困境,迎来光明吧。)
附录 1: ICMP 报文类型表
markdown
| 类型 Type | 代码 Code | 描述 |
| :------: | :------: | :--------------------------: |
| 0 | 0 | 回显应答(ping 应答) |
| 3 | 0 | 网络不可达 |
| 3 | 1 | 主机不可达 |
| 3 | 2 | 协议不可达 |
| 3 | 3 | 端口不可达 |
| ... | ... | ... |
TODO:
ping命令全链路分析(3)-用户态数据包构造与传递
在Linux系统中,ping命令等网络工具基于inetutils包中的应用层网络工具。本文将探讨ping命令在Linux内核网络协议栈及驱动层面的实现方式。
应用层ping通过socket与内核层交互,程序首先初始化数据结构,创建socket连接,然后构造icmp数据包发送,并对返回的ICMP响应报文进行处理。初始化过程由ping_init()函数完成,创建socket连接,分配数据结构存储空间。数据包构造、发送和返回报文处理由ping_echo()函数完成,其中设置了协议类型、包长度和目的地址,并注册了接收回调函数。
数据包发送过程在ping_run()函数中的send_echo()函数完成,将icmp报文数据部分复制到buf中,并通过socket_fd发送。当目的端返回ping命令的响应报文被网卡接收后,通过内核网络协议栈处理后返回给应用程序。ping应用程序采用IO复用中的select()方式来处理响应报文,当监控到对应socket连接中有数据包到来时,调用ping_recv()函数处理ICMP响应数据包。
应用层软件ping通过socket接口与内核通信,实现数据包发送和接收。数据包发送sendto()的实现代码在linux源码${ linux_src}/net/socket.c中,先检查数据区域是可读的,然后构造待发送消息,并将数据填充到消息中。数据包接收recvfrom()与发送相反,是从内核协议栈中读取数据包到应用层中,实现代码也在${ linux_src}/net/socket.c中。
本文主要分析了用户态程序ping如何构造ICMP请求报文,并通过socket接口实现数据在内核态与用户态之间的搬移。后续将继续分析内核态网络协议栈对数据包的处理,以及内核驱动与硬件的交互实现。