ICMP 協議基礎
- Internet 控制報文協議,網絡層協議,封裝在 IP 數據包中
- 無端口號概念,用類型/代碼區分功能(Echo Request=8, Echo Reply=0)
- 核心用途:錯誤報告、網絡診斷
IP 數據包結構
[IP Header (20 bytes)] [ICMP Header (8 bytes)] [Data (variable)]
- IP Header:版本、TOS、總長度、標識、標志/片偏移、TTL、協議(1=ICMP)、校驗和、源/目 IP
- ICMP Header:類型(1)、代碼(1)、校驗和(2)、標識符(2)、序列號(2)
C# 關鍵類:Socket (Raw + Icmp)
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
Raw:原始套接字,允許構造自定義協議頭
ProtocolType.Icmp:指定 ICMP 協議
- 需管理員權限運行
核心實現步驟
1. 構造 ICMP Echo Request 封包
public class IcmpPacket
{
public byte Type { get; set; } // 8 = Echo Request
public byte Code { get; set; } // 0
public ushort Checksum { get; set; }
public ushort Identifier { get; set; }
public ushort SequenceNumber { get; set; }
public byte[] Data { get; set; }
public IcmpPacket(int dataSize = 32)
{
Type = 8; Code = 0; Checksum = 0;
Identifier = (ushort)Process.GetCurrentProcess().Id;
SequenceNumber = 0;
Data = new byte[dataSize];
// 填充模式數據便於識別
for (int i = 0; i < dataSize; i++) Data[i] = (byte)'#';
}
// 序列化為字節數組
public byte[] ToBytes()
{
using (var ms = new MemoryStream())
using (var bw = new BinaryWriter(ms))
{
bw.Write(Type);
bw.Write(Code);
bw.Write(Checksum); // 先寫 0,稍後回填
bw.Write(Identifier);
bw.Write(SequenceNumber);
bw.Write(Data);
var packet = ms.ToArray();
// 計算校驗和 (RFC 1071)
ushort checksum = ComputeChecksum(packet);
// 回填校驗和 (大端序)
packet[2] = (byte)(checksum >> 8);
packet[3] = (byte)(checksum & 0xFF);
return packet;
}
}
private static ushort ComputeChecksum(byte[] buffer)
{
uint sum = 0;
for (int i = 0; i < buffer.Length; i += 2)
{
ushort word = (ushort)((buffer[i] << 8) | (i + 1 < buffer.Length ? buffer[i + 1] : 0));
sum += word;
}
// 折疊進位
while ((sum >> 16) > 0)
sum = (sum & 0xFFFF) + (sum >> 16);
return (ushort)~sum;
}
}
2. 發送與接收
var packet = new IcmpPacket(32).ToBytes();
var remoteEP = new IPEndPoint(IPAddress.Parse("1.1.1.1"), 0); // ICMP 無端口
socket.SendTo(packet, remoteEP);
var receiveBuffer = new byte[1024];
EndPoint senderEP = new IPEndPoint(IPAddress.Any, 0);
int received = socket.ReceiveFrom(receiveBuffer, ref senderEP);
// 解析響應:跳過 IP Header (20 bytes) 讀取 ICMP Header
if (received > 20)
{
byte icmpType = receiveBuffer[20];
byte icmpCode = receiveBuffer[21];
// icmpType == 0 表示 Echo Reply
}
3. 計算往返時間 (RTT)
var sw = Stopwatch.StartNew();
socket.SendTo(packet, remoteEP);
int received = socket.ReceiveFrom(receiveBuffer, ref senderEP);
sw.Stop();
Console.WriteLine($"Reply from {senderEP}: time={sw.ElapsedMilliseconds}ms");
完整 Ping 邏輯
- 循環發送 4 次 (可配置)
- 設置接收超時 (
socket.ReceiveTimeout)
- 統計:丟包率、最小/最大/平均 RTT
- 異常處理:權限不足、目標不可達、超時
注意事項
| 問題 | 解決 |
| 需管理員權限 | 清單文件 `` 或以管理員運行 |
| 防火牆攔截 | 允許 ICMP 入站/出站規則 |
| 校驗和計算 | 必須按 RFC 1071 大端序計算,包含整個 ICMP 封包 |
| IP Header 長度可變 | 解析響應時先讀取 IP Header 首部長度字段 (IHL),再定位 ICMP |
生產級替代方案
System.Net.NetworkInformation.Ping 類:.NET 內置封裝,無需 Raw Socket 權限,跨平台
- 示例:
new Ping().Send("1.1.1.1") 返回 PingReply 對象