ICMP 與 Ping 基礎知識
- ICMP (Internet Control Message Protocol) 屬網路層協議,封裝於 IP 封包中,無連接埠概念。
- Ping 利用 ICMP Echo Request (Type 8) / Echo Reply (Type 0) 檢測主機可達性。
- IP 封包結構:IP Header (20 bytes) + ICMP Header (8 bytes) + Data。
- ICMP 封包結構:Type(1) | Code(1) | Checksum(2) | Identifier(2) | Sequence(2) | Data(可變)。
關鍵實作步驟
- 建立 Raw Socket:
new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp)
- 手工構造 ICMP 封包(含 Checksum 計算)
- 發送:
socket.SendTo(buffer, endPoint)
- 接收:
socket.ReceiveFrom(buffer, ref remoteEP)
- 解析回應,計算往返時間 (RTT)
核心類別:IcmpPacket
public class IcmpPacket
{
public byte Type; // 8 = Echo Request, 0 = Echo Reply
public byte Code; // 通常為 0
public ushort Checksum; // 校驗和
public ushort Identifier; // 識別碼,對應請求/回應
public ushort Sequence; // 序號
public byte[] Data; // 承載資料
public IcmpPacket(byte type, byte code, ushort checksum,
ushort id, ushort seq, int dataSize)
{
Type = type; Code = code; Checksum = checksum;
Identifier = id; Sequence = seq;
Data = new byte[dataSize];
for (int i = 0; i < dataSize; i++) Data[i] = (byte)'#';
}
// 序列化為 byte 陣列
public int ToBytes(byte[] buffer)
{
int offset = 0;
buffer[offset++] = Type;
buffer[offset++] = Code;
BitConverter.GetBytes(Checksum).CopyTo(buffer, offset); offset += 2;
BitConverter.GetBytes(Identifier).CopyTo(buffer, offset); offset += 2;
BitConverter.GetBytes(Sequence).CopyTo(buffer, offset); offset += 2;
Data.CopyTo(buffer, offset); offset += Data.Length;
return offset;
}
// 計算 ICMP Checksum (RFC 1071)
public static ushort ComputeChecksum(ushort[] buffer)
{
uint sum = 0;
foreach (ushort v in buffer) sum += v;
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
return (ushort)(~sum);
}
}
完整 Ping 發送/接收流程
public void PingHost(string hostNameOrIp)
{
// 1. 解析目標 IP
IPHostEntry hostEntry = Dns.GetHostEntry(hostNameOrIp);
IPAddress targetIp = hostEntry.AddressList[0];
IPEndPoint remoteEP = new IPEndPoint(targetIp, 0); // ICMP 無連接埠,設 0
// 2. 建立 Raw Socket (需管理員權限)
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp))
{
socket.ReceiveTimeout = 3000;
// 3. 構造請求封包
var packet = new IcmpPacket(8, 0, 0, 45, 0, 32); // 32 bytes data
byte[] sendBuf = new byte[packet.Data.Length + 8];
int len = packet.ToBytes(sendBuf);
// 計算 checksum
ushort[] ckBuf = new ushort[(len + 1) / 2];
Buffer.BlockCopy(sendBuf, 0, ckBuf, 0, len);
packet.Checksum = IcmpPacket.ComputeChecksum(ckBuf);
// 重新序列化含 checksum 的封包
len = packet.ToBytes(sendBuf);
// 4. 發送並計時
int start = Environment.TickCount;
socket.SendTo(sendBuf, len, SocketFlags.None, remoteEP);
// 5. 接收回應
byte[] recvBuf = new byte[1024];
EndPoint senderEP = new IPEndPoint(IPAddress.Any, 0);
int recvLen = socket.ReceiveFrom(recvBuf, ref senderEP);
int rtt = Environment.TickCount - start;
// 6. 簡易驗證:檢查 Type=0, Identifier 匹配
if (recvLen > 20 && recvBuf[20] == 0) // IP header 20 bytes + ICMP Type
{
Console.WriteLine($"Reply from {targetIp}: time={rtt}ms bytes={recvLen}");
}
else
{
Console.WriteLine("Request timed out or invalid reply.");
}
}
}
執行環境與注意事項
- OS:Windows 2000 Server / XP / 現代 Windows
- VS:.NET Framework 1.0+ (原文為 2002/2003,現代 .NET 6/7/8 同樣適用)
- 需管理員權限 才能建立 Raw Socket
- 防火牆可能封鎖 ICMP,導致 Ping 失敗但 TCP 連線正常
- 首次 Ping 因 ARP 解析/路由表建立通常較慢,後續較穩定
執行畫面


