Ethernet帧的验证
计算Ethernet是网络编程的基础,本文的内容来源于《计算机网络高级软件编程技术》
帧的格式:
| 字段 | 字段长度(字节) | 目的 |
| 前导码(Preamble) | 7 | 同步 |
| 帧开始符(SFD) | 1 | 标明下一个字节为目的MAC字段 |
| 目的MAC地址 | 6 | 指明帧的接受者 |
| 源MAC地址 | 6 | 指明帧的发送者 |
| 长度(Length) | 2 | 帧的数据字段的长度(长度或类型) |
| 类型(Type) | 2 | 帧中数据的协议类型(长度或类型) |
| 类型和填充(Data and Pad)注 | 46~1500 | 高层的数据,通常为3层协议数据单元。对于TCP/IP是IP数据包 |
| 帧校验序列(FCS) | 4 | 对接收网卡提供判断是否传输错误的一种方法,如果发现错误,丢弃此帧 |
注:如果数据包小于46字节,则要求“填充”,以使这个字段达到46字节。填充是必须的,因为数据字段要求至少46字节长。
前导码由10...10的比特序列组成,共7B,56位。帧前定界符有8位,1B。
目的地址和源地址皆为6B,48位。
类型字段表示网络层使用的协议类型,2B,16为,当字段为0x0800时为网络层使用IP协议。
1 #include <stdlib.h> // 用于程序流程控制
2 #include <iostream>
3 #include <fstream> // 用于文件操作
4
5 using namespace std;
6
7 ////////////////////////////////////////////////////////////////////////////////
8 // CRC校验,在上一轮校验的基础上继续作8位CRC校验
9 //
10 // 输入参数:
11 // chCurrByte 低8位数据有效,记录了上一次CRC校验的余数
12 // chNextByte 低8位数据有效,记录了本次要继续校验的一个字节
13 //
14 // 传出参数:
15 // chCurrByte 低8位数据有效,记录了本次CRC校验的余数
16 ////////////////////////////////////////////////////////////////////////////////
17
18 void checkCRC(int &chCurrByte, int chNextByte)
19 {
20 // CRC循环:每次调用进行8次循环,处理一个字节的数据。
21 for (int nMask = 0x80; nMask > 0; nMask >>= 1)
22 {
23 if ((chCurrByte & 0x80) != 0) // 首位为1:移位,并进行异或运算
24 {
25 chCurrByte <<= 1; // 移一位
26 if ( (chNextByte & nMask) != 0) // 补一位
27 {
28 chCurrByte |= 1;
29 }
30 chCurrByte ^= 7; // 首位已经移出,仅对低8位进行异或运算,7的二进制为0000,0111
31 }
32 else // 首位为0,只移位,不进行异或运算
33 {
34 chCurrByte <<= 1; // 移一位
35 if ( (chNextByte & nMask) != 0) // 补一位
36 {
37 chCurrByte |= 1;
38 }
39 }
40 }
41 }
42
43
44 void main(int argc, char* argv[])
45 {
46
47 // 检测输入文件是否存在,并可以按所需的权限和方式打开
48
49 char* File="input";
50 ifstream file(File, ios::in|ios::binary);
51 if (!file.is_open())
52 {
53 cout << "无法打开帧封装包文件,请检查文件是否存在并且未损坏" << endl;
54 exit(0);
55 }
56
57
58 // 变量声明及初始化
59 int nSN = 1; // 帧序号
60 int nCheck = 0; // 校验码
61 int nCurrDataOffset = 22; // 帧头偏移量
62 int nCurrDataLength = 0; // 数据字段长度
63 bool bParseCont = true; // 是否继续对输入文件进行解析
64 int nFileEnd = 0; // 输入文件的长度
65
66 // 计算输入文件的长度
67 file.seekg(0, ios::end); // 把文件指针移到文件的末尾
68 nFileEnd = file.tellg(); // 取得输入文件的长度
69 file.seekg(0, ios::beg); // 文件指针位置初始化
70
71 cout.fill('0'); // 显示初始化
72 cout.setf(ios::uppercase); // 以大写字母输出
73
74 // 定位到输入文件中的第一个有效帧
75 // 从文件头开始,找到第一个连续的“AA-AA-AA-AA-AA-AA-AA-AB”
76 while ( true )
77 {
78 for (int j = 0; j < 7; j++) // 找7个连续的0xaa
79 {
80 if (file.tellg() >= nFileEnd) // 安全性检测
81 {
82 cout<<"没有找到合法的帧"<<endl;
83 file.close();
84 exit(0);
85 }
86 // 看当前字符是不是0xaa,如果不是,则重新寻找7个连续的0xaa
87 if (file.get() != 0xaa)
88 {
89 j = -1;
90 }
91 }
92
93 if (file.tellg() >= nFileEnd) // 安全性检测
94 {
95 cout<<"没有找到合法的帧"<<endl;
96 file.close();
97 exit(0);
98 }
99
100 if (file.get() == 0xab) // 判断7个连续的0xaa之后是否为0xab
101 {
102 break;
103 }
104 }
105
106 // 将数据字段偏移量定位在上述二进制串之后14字节处,并准备进入解析阶段
107 nCurrDataOffset =(int) file.tellg() + 14;
108 file.seekg(-8,ios::cur);
109
110
111 // 主控循环
112 while ( bParseCont ) // 当仍然可以继续解析输入文件时,继续解析
113 {
114
115 // 检测剩余文件是否可能包含完整帧头
116 if ((int)file.tellg() + 14 > nFileEnd)
117 {
118 cout<<endl<<"没有找到完整帧头,解析终止"<<endl;
119 file.close();
120 exit(0);
121 }
122
123 int c; // 读入字节
124 int i = 0; // 循环控制变量
125 int EtherType = 0; // 由帧中读出的类型字段
126 bool bAccept = true; // 是否接受该帧
127
128
129 // 输出帧的序号
130 cout << endl << "序号:\t\t" << nSN;
131
132 // 输出前导码,只输出,不校验
133 cout << endl << "前导码:\t";
134 for (i = 0; i < 7; i++) // 输出格式为:AA AA AA AA AA AA AA
135 {
136 cout.width(2);
137 cout << hex << file.get() << dec << " ";
138 }
139 // 输出帧前定界符,只输出,不校验
140 cout << endl << "帧前定界符:\t";
141 cout.width(2); // 输出格式为:AB
142 cout << hex << file.get();
143
144 // 输出目的地址,并校验
145 cout << endl << "目的地址:\t";
146 for (i = 0; i < 6; i++) // 输出格式为:xx-xx-xx-xx-xx-xx
147 {
148 c = file.get();
149 cout.width(2);
150 cout<< hex << c << dec << (i==5 ? "" : "-");
151 if (i == 0) // 第一个字节,作为“余数”等待下一个bit
152 {
153 nCheck = c;
154 }
155 else // 开始校验
156 {
157 checkCRC(nCheck, c);
158 }
159 }
160
161 // 输出源地址,并校验
162 cout << endl << "源地址:\t";
163 for (i = 0; i < 6; i++) // 输出格式为:xx-xx-xx-xx-xx-xx
164 {
165 c = file.get();
166 cout.width(2);
167 cout<< hex << c << dec << (i==5 ? "" : "-");
168 checkCRC(nCheck, c); // 继续校验
169 }
170
171 // 输出类型字段,并校验
172 cout<<endl<<"类型字段:\t";
173 cout.width(2);
174 // 输出类型字段的高8位
175 c = file.get();
176 cout<< hex << c << dec << " ";
177 checkCRC(nCheck, c); // CRC校验
178 EtherType = c;
179 // 输出类型字段的低8位
180 c = file.get();
181 cout.width(2);
182 cout<< hex << c;
183 checkCRC(nCheck,c); // CRC校验
184 EtherType <<= 8; // 转换成主机格式
185 EtherType |= c;
186
187 // 定位下一个帧,以确定当前帧的结束位置
188 while ( bParseCont )
189 {
190
191 for (int i = 0; i < 7; i++) //找下一个连续的7个0xaa
192 {
193 if (file.tellg() >= nFileEnd) //到文件末尾,退出循环
194 {
195 bParseCont = false;
196 break;
197 }
198 // 看当前字符是不是0xaa,如果不是,则重新寻找7个连续的0xaa
199 if (file.get() != 0xaa)
200 {
201 i = -1;
202 }
203 }
204
205 // 如果直到文件结束仍没找到上述比特串,将终止主控循环的标记bParseCont置为true
206 bParseCont = bParseCont && (file.tellg() < nFileEnd);
207
208 // 判断7个连续的0xaa之后是否为0xab
209 if (bParseCont && file.get() == 0xab)
210 {
211 break;
212 }
213 }
214
215 // 计算数据字段的长度
216 nCurrDataLength =
217 bParseCont ? // 是否到达文件末尾
218 ((int)file.tellg() - 8 - 1 - nCurrDataOffset) :// 没到文件末尾:下一帧头位置 - 前导码和定界符长度 - CRC校验码长度 - 数据字段起始位置
219 ((int)file.tellg() - 1 - nCurrDataOffset); // 已到达文件末尾:文件末尾位置 - CRC校验码长度 - 数据字段起始位置
220
221
222 // 以文本格式数据字段,并校验
223 cout << endl << "数据字段:\t";
224 char* pData = new char[nCurrDataLength]; // 创建缓冲区
225 file.seekg(bParseCont ? (-8 - 1 -nCurrDataLength) : ( -1 - nCurrDataLength), ios::cur);
226 file.read(pData, nCurrDataLength); // 读入数据字段
227
228 int nCount = 50; // 每行的基本字符数量
229 for (i = 0; i < nCurrDataLength; i++) // 输出数据字段文本
230 {
231 nCount--;
232 cout << pData[i]; // 字符输出
233 checkCRC(nCheck, (int)pData[i]); // CRC校验
234
235 if ( nCount < 0) // 换行处理
236 {
237 // 将行尾的单词写完整
238 if ( pData[i] == ' ' )
239 {
240 cout << endl << "\t\t";
241 nCount = 50;
242 }
243 // 处理过长的行尾单词:换行并使用连字符
244 if ( nCount < -10)
245 {
246 cout<< "-" << endl << "\t\t";
247 nCount = 50;
248 }
249 }
250 }
251 delete[] pData; //释放缓冲区空间
252
253
254 // 输出CRC校验码,如果CRC校验有误,则输出正确的CRC校验码
255 cout << endl <<"CRC校验";
256 c = file.get(); // 读入CRC校验码
257 int nTmpCRC = nCheck;
258 checkCRC(nCheck, c); // 最后一步校验
259
260 if ((nCheck & 0xff) == 0) // CRC校验无误
261 {
262 cout.width(2);
263 cout<<"(正确):\t"<< hex << c;
264 }
265 else // CRC校验有误
266 {
267 cout.width(2);
268 cout<< "(错误):\t" << hex << c;
269 checkCRC(nTmpCRC, 0); // 计算正确的CRC校验码
270 cout<< "\t应为:" << hex << (nTmpCRC & 0xff);
271 bAccept = false; // 将帧的接收标记置为false
272 }
273
274 // 如果数据字段长度不足46字节或数据字段长度超过1500字节,则将帧的接收标记置为false
275 if (nCurrDataLength < 46 || nCurrDataLength > 1500 )
276 {
277 bAccept = false;
278 }
279
280 // 输出帧的接收状态
281 cout<< endl << "状态:\t\t" << (bAccept ? "Accept" : "Discard") << endl <<endl;
282
283 nSN++; // 帧序号加1
284 nCurrDataOffset = (int)file.tellg() + 22; // 将数据字段偏移量更新为下一帧的帧头结束位置
285
286 }
287
288 // 关闭输入文件
289 file.close();
290
291 }
帧的检验文件:
浙公网安备 33010602011771号