单单看看书就知道UDP和TCP这些校验和的大概的算法,无非就是什么报文段相加取反什么的。但是就看书不操作满足不了好奇心~,本文分以下两个模块
- UDP抓包并且验算checksum
- TCP抓包以及验算checksum
UDP抓包实验
首先我写了一段小程序,往IP地址位11.111.111.111发送了一串字符。
public class Client1 {
private DatagramSocket socket;
public Client1() throws SocketException {
socket = new DatagramSocket();
}
public void run() throws IOException {
InetAddress ip = InetAddress.getByName("11.111.111.111");
String message = "hello UDP";
byte[] buffer = message.getBytes();
//DatagramPacket这个类是Java当中实现了UDP的类
DatagramPacket sendPacket = new DatagramPacket(buffer, buffer.length, ip, 12345);
socket.send(sendPacket);
}
public static void main(String[] args) throws IOException {
new Client1().run();
}
}
打开Wireshark经过抓包以后,在地址栏中输入ip.addr==11.111.111.111来筛选下报文。截图如下:

但是我们还不能马上开始计算校验和,先来看下校验和需要由哪些部分来计算。根据RFC768文档,校验和=(报文伪首部+UDP报文头+数据段)取反
其中报文的伪首部长这样(在TCP当中也是一样的):

至于伪首部的作用,引用下RFC的原文:
The pseudo header conceptually prefixed to the UDP header contains the
source address, the destination address, the protocol, and the UDP
length. This information gives protection against misrouted datagrams.
This checksum procedure is the same as is used in TCP
接下来就可以开始计算校验和了,我将数据整理下更加直观

在计算的时候,IP地址因为是32位的,所以要分成两个16位来计算。我写了一段代码来计算校验和
//UDP,计算出来的正确校验和应该为0xc20a
int[] data = {
0xc0a8,0x0106,//source IP
0x0b6f,0x6f6f,//destination IP
17,//UDP
17,//UDP length
60849,//source port
12345,//destination port
17,//UDP len
0x6865,0x6c6c,0x6f20,0x5544,0x5000 //hello UDP对应的ascii码对应的十六进制数
};
public void compute(int[] data) {
int sum = 0;
for(int value:data) {
sum += value;
if(sum > 0xFFFF) {
int carry = sum>>16; //进位
sum = 0x0000_FFFF∑ //处理回卷
sum +=carry;
}
}
String result = Integer.toHexString(~sum);
System.out.println("result:"+result);
}
//输出结果:
ffffbc1d
上述代码的一点解释:
在校验和的计算当中,如果出现了进位,从而出现了第17位,那么就会发生回卷,回卷的意思如下,我以一个十进制相加来说明问题:

当发生进位以后,把进位和低位的数相加从而得到最终答案。在16位的校验和当中,出现第17位的时候,把第17位和剩下的低16位相加即可,这就是回卷!
因为校验和是16位的,所以前面的ffff是因为int为32位,本来是0000,但是我们对sum取反了,再补位上去的,因此我们得到的校验和是0xbc1d。在和wireshark中的校验和对比下

可以看到,我们计算的是正确的~
TCP抓包实验
TCP抓包实验和UDP抓包有一点不一样,wireshark在选择网卡的时候,选wireshark给的虚拟网卡。选择捕获->选项->虚拟网卡

下面给出所需要的两端代码,
//这是发送方
public static void main(String[] args) {
try {
//默认使用tcp创建一个tcp
Socket clientSocket = new Socket("127.0.0.1",44444);
String data = "hello world!";
OutputStream out = clientSocket.getOutputStream();
out.write(data.getBytes());
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//这是接收方
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(44444);
Socket socket = serverSocket.accept();
System.out.println("accept");
InputStream in = socket.getInputStream();
int i;
while((i = in.read()) > 0 ) {
System.out.println((char)i);
}
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
基本上的过程和UDP的完全一样,我们在报文筛选中输入tcp.port=44444可以筛选到这条报文。另外wireshark的TCP报文段中,序列号(sequence number)

右键sequence number->协议首选项->**relative sequence number **把那个勾给去了。这样得到的才是正确的序列号。
接下来开始计算校验和:

同样的,校验和的计算也是校验和=(伪首部+TCP报文+数据)然后取反,首部和上面的那个首部是一样的。TCP报文中,我们将上面红框的字段就是我们需要参与计算的字段,其中Data offset字段在Wireshark报文中对应的是:

他表示的是在报文中,表示TCP报文头的长度,这里20字节,表示21字节就是数据开始了,也就是说数据的偏移量是20字节。
public void compute(int[] data) {
int sum = 0;
for(int value:data) {
sum += value;
if(sum > 0xFFFF) {
int carry = sum>>16;
sum = 0x0000_FFFF∑
sum +=carry;
}
}
String result = Integer.toHexString(~sum);
System.out.println("result:"+result);
用上述这个函数,即可计算到TCP报文的校验和。
网友评论