一、题目和要求
题目:协议分析软件开发及网络协议分析
(1)教学内容
网络协议分层结构,网络协议工作过程,协议数据单元结构,协议分析,协议设计,协议分析软件开发。
(2)教学要求
理解网络协议的层次结构及网络协议的工作过程,掌握网络协议的分析方法及网络协议的设计与应用。
二、了解必须的协议首部格式
IP协议,又译为网际协议或互联网协议,是用在TCP/IP协议簇中的网络层协议。IP协议位于TCP/IP协议的网络层,位于同一层次的协议还有下面的ARP和RARP以及上面的ICMP(Internet控制报文协议)和IGMP(Internet组管理协议)。除了ARP和RARP报文以外的几乎所有的数据都要经过IP协议进行传送。ARP和RARP报文没有封装在IP数据报中,而ICMP和IGMP的数据则要封装在IP数据报中进行传输。由于IP协议在网络层中具有重要的地位,TCP/IP协议的网络层又被称为IP层。
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。
TCP旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。
当应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,TCP则把数据流分割成适当长度的报文段,最大传输段大小(MSS)通常受该计算机连接的网络的数据链路层的最大传送单元(MTU)限制。之后TCP把数据包传给IP层,由它来通过网络将包传送给接收端实体的TCP层。
UDP协议概述:一种无连接协议,提供无连接服务,在传送数据之前不需要先建立连接;传送的数据单位协议是 UDP 报文或用户数据报。对方的运输层在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 是一种最有效的工作方式。UDP 用户数据报是在运输层的端到端抽象的逻辑信道中传送的。
三、实现思路
- JAVA语言虽然在TCP/UDP传输方面给予了良好的定义,但对于网络层以下的控制,却是无能为力的。JPCAP扩展包弥补了这一点,
jPcap
是一个可以让java工作在链路层的类库;当然,它底层还是使用了本机API通过Jini调用,在javaAPI中得到数据。JPCAP实际上并非一个真正去实现对数据链路层的控制,而是一个中间件,JPCAP调用wincap/libpcap,而给JAVA语言提供一个公共的接口,从而实现了平台无关性。
- 通过使用主函数main去调用分析函数analyzePacke()和其他的方法去解决各个功能。通过while循环不断的输入选项去完成选择。通过线程抓获去获取数据包(用数组去存放数据包),然后重写run()方法去获取数据包。
- 最后可以通过break跳出menu菜单(或者选择停止抓数据包)。
四、具体实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
| import jpcap.*; import jpcap.packet.*; import java.io.IOException; import java.util.ArrayList; import java.util.Scanner; import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
public class Test{ //定义默认最大抓包数 private static final int max = 1000; //显示所有网络设备信息 private static void showDeviceList(NetworkInterface[] devices) { //展示当前主机设备信息 System.out.println("本机上所有适配器如下:"); for (int i = 0; i < devices.length; i++) { //网络适配器名称(循环输出所有的适配器) System.out.println("Adapter " + (i + 1) + "(名称)" + devices[i].description); //MAC地址 System.out.print(" MAC address(MAC地址): "); for (byte b : devices[i].mac_address) //用:隔开输出16进制 { System.out.print(Integer.toHexString(b & 0xff) + ":"); } System.out.println(); //IP地址 for (NetworkInterfaceAddress a : devices[i].addresses) //调用方法输IP地址 { System.out.println(" IPv6/IPv4 address(IP地址): " + a.address); } System.out.println(); } }
//网络接口监听 private static JpcapCaptor openDevice(NetworkInterface[] devices, int choice) throws java.io.IOException //抛出异常 { JpcapCaptor captor = null; Try { captor = JpcapCaptor.openDevice(devices[choice], 65535, false, 3000); //方法中的变量为一次性抓65535的长度,超时时间为3000ms } catch (IOException e) { e.printStackTrace();//打印异常信息在程序中出错的位置以及原因 System.out.println("打开网络接口失败!"); } return captor; }
//数据包捕获线程(Thread) private static class AThread implements Runnable { Thread thread; JpcapCaptor captor; Packet[] packet; //线程中断标志 volatile boolean cancel; AThread(JpcapCaptor captor) throws IOException{ this.captor = captor;//用this调用数据 this.packet = new Packet[max];//用this调用数据 this.cancel = false;//用this调用数据 thread = new Thread(this);//用this调用数据 }
@Override //重写线程中的run方法 public void run() { packet = new Packet[max]; for(int i = 0; i < max && cancel == false; i++){ packet[i] = captor.getPacket();//调用getpacket()方法获取数据包 } } //设置中断的方法(主要用于选项2的操作) public void cancel() { cancel = true; }
public Packet[] getPacket() { return packet;//获取数据包 }
}
private static Packet[] readPacket(JpcapCaptor captor, String filename) { Packet[] packet = new Packet[max];//定义一个packet数组去存取数据包 try { captor = JpcapCaptor.openFile(filename); } catch (IOException e) { e.printStackTrace(); } for(int i = 0;;i++){ packet[i] = captor.getPacket(); if(packet[i] == null) break; } return packet; }
//按键5的功能去实现展示当前有多少数据包 private static void analyzePacket(Packet[] packet) { //定义三个泛型去动态存取数据包个数 ArrayList<UDPPacket> udpPacketArray = new ArrayList<UDPPacket>(); ArrayList<ARPPacket> arpPacketArray = new ArrayList<ARPPacket>(); ArrayList<TCPPacket> tcpPacketArray = new ArrayList<TCPPacket>(); int count, count1, count2, count3,; count = count1 = count2 = count3 = 0; for(int i = 0; packet[i] != null && i < max; i++) //循环存各个类型的 { count++; if (packet[i] instanceof UDPPacket)//如果获取的数据包属于UDPPacket包 { UDPPacket udp = (UDPPacket) packet[i]; udpPacketArray.add(udp); count1++; } else if(packet[i] instanceof ARPPacket)//如果获取的数据包属于ARPPacket包 { ARPPacket arp = (ARPPacket) packet[i]; arpPacketArray.add(arp); count2++; } else if(packet[i] instanceof TCPPacket)//如果获取的数据包属于TCPPacket包 { TCPPacket tcp = (TCPPacket) packet[i]; tcpPacketArray.add(tcp); count3++; } } System.out.println();//换行方便查看 System.out.println("所有数据包数:" + count); System.out.println("UDP数据包数:" + count1); System.out.println("ARP数据包数:" + count2); System.out.println("TCP数据包数:" + count3); } //此方法循环输出每个包的信息 private static void showPacket(Packet[] packet) { for(int i = 0; packet[i] != null && i < max-1; i++) { System.out.println("Packet " + (i+1) + " : " + packet[i]); } }
//按键4显示数据包详细情况 private static void showPacketDetail(Packet[] packet) { System.out.print("输入你想查看的包序号"); Scanner input=new Scanner(System.in); int num=input.nextInt();//输入你要选择看的包序号 num=num-1; for(int i = 0; packet[i] != null && i < max-1; i++) { if(i==num) //如果选的那个数字和循环的符合的话去判断是哪个数据包 { if(packet[i] instanceof UDPPacket) // 如果获取的数据包属于UDPPacket包 { UDPPacket udp = (UDPPacket) packet[i]; HexBinaryAdapter head = new HexBinaryAdapter(); String str = head.marshal(udp.header); int tou = (str.charAt(29)-48)*4*2;//主要用于判断推送字节输出检验和 System.out.println("Packet " + (i+1) + " : UDP" ); System.out.println(" 数据 : " + str); System.out.println("**********ip首部*********"); System.out.println(" 版本:IPv"+ udp.version); System.out.println(" 首部长度:" + str.substring(29,30)); System.out.println(" 区分服务: 0"); System.out.println(" 总长度:" + udp.length); System.out.println(" 标识:" + udp.ident); System.out.println(" MF: " + udp.more_frag); System.out.println(" DF: " + udp.dont_frag); System.out.println(" 片偏移:" + udp.offset); System.out.println(" 生存时间:" + udp.hop_limit); System.out.println(" 源 ip地址 : " + udp.src_ip.toString()); System.out.println(" 目的ip地址 : " + udp.dst_ip.toString()); System.out.println("**********数据部分*********"); System.out.println(" 源端口 : " + String.valueOf(udp.src_port)); System.out.println(" 目的端口 : " + String.valueOf(udp.dst_port)); System.out.println(" 长度 : " + String.valueOf(udp.length)); System.out.println(" 校验和 : +str.substring(28+tou+12,28+tou+16)); System.out.println(); } else if(packet[i] instanceof TCPPacket) //如果获取的数据包属于TCPPacket包 { TCPPacket tcp = (TCPPacket) packet[i];//TCP HexBinaryAdapter head = new HexBinaryAdapter();//转成16进制 String str = head.marshal(tcp.header); int tou = (str.charAt(29)-48)*4*2;//主要用于判断推送字节输出检验和 System.out.println("Packet " + (i+1) + " : TCP" ); System.out.println(" 数据 : " + str); System.out.println("**********ip首部*********"); System.out.println(" 版本:IPv"+ tcp.version); System.out.println(" 首部长度:" + str.substring(29,30)); System.out.println(" 区分服务: 0"); System.out.println(" 总长度:" + tcp.length); System.out.println(" 标识:" + tcp.ident); System.out.println(" MF: " + tcp.more_frag); System.out.println(" DF: " + tcp.dont_frag); System.out.println(" 片偏移:" + tcp.offset); System.out.println(" 生存时间:" + tcp.hop_limit); System.out.println(" 源 ip地址 : " + tcp.src_ip.toString()); System.out.println(" 目的ip地址 : " + tcp.dst_ip.toString()); System.out.println("**********数据部分*********"); System.out.println(" 源 ip地址 : " + tcp.src_ip.toString()); System.out.println(" 目的ip地址 : " + tcp.dst_ip.toString()); System.out.println(" 源端口 : " + String.valueOf(tcp.src_port)); System.out.println(" 目的端口 : " + String.valueOf(tcp.dst_port)); System.out.println(" 序号:" + tcp.sequence); System.out.println(" 确认号:" + tcp.ack_num); System.out.println(" 数据偏移:" + tcp.offset); System.out.println(" 保留:0"); System.out.println(" 紧急URG:" + tcp.urg); System.out.println(" 确认ACK:" + tcp.ack); System.out.println(" 推送PSH:" + tcp.psh); System.out.println(" 复位RST:" + tcp.rst); System.out.println(" 同步SYN:" + tcp.syn); System.out.println(" 终止FIN:" + tcp.fin); System.out.println(" 窗口:" + tcp.window); System.out.println(" 检验和:" + str.substring(28+tou+32,28+tou+36)); System.out.println(" 紧急指针:" + tcp.urgent_pointer); System.out.println(" 选项:" + tcp.option); System.out.println(); } else if(packet[i] instanceof ARPPacket)//如果获取的数据包属于ARPPacket包 { ARPPacket arp = (ARPPacket) packet[i];//ARP HexBinaryAdapter head = new HexBinaryAdapter();//转成16进制 String str = head.marshal(arp.header); byte[] b = new byte[4]; String s1 = ""; String s2 = ""; b = arp.target_protoaddr; s1 += String.valueOf((b[0] & 0xff) + "." + ( b[1] & 0xff) + "." + (b[2] & 0xff) + "." + (b[3] & 0xff)); b = arp.sender_protoaddr; s2 += String.valueOf((b[0] & 0xff) + "." + ( b[1] & 0xff) + "." + (b[2] & 0xff) + "." + (b[3] & 0xff)); for (byte d : arp.sender_hardaddr) { System.out.print(Integer.toHexString(d & 0xff) + ":"); } for (byte e : arp.target_hardaddr) { System.out.print(Integer.toHexString(e & 0xff) + ":"); } System.out.println("Packet " + (i+1) + " : ARP" ); System.out.println(" 数据 : " + str); System.out.println(" Hardware type: " + arp.hardtype); System.out.println(" protocol type : " + arp.prototype); System.out.println(" Hardware size : " + arp.hlen); System.out.println(" protocol size : " + arp.plen); System.out.print(" sender MAC address: "); for (byte d : arp.sender_hardaddr) { System.out.print(Integer.toHexString(d & 0xff) + ":"); } System.out.println(); System.out.println(" sender ip address: " + s2); System.out.print(" target MAC address: " ); for (byte e : arp.target_hardaddr) { System.out.print(Integer.toHexString(e & 0xff) + ":"); } System.out.println(); System.out.println(" target ip address: " + s1); System.out.println(); } } } }
public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); //数组存放数据包 Packet[] packet = new Packet[max]; //初始化数据包捕获的线程 AThread t = null; //获取网络设备并显示 NetworkInterface[] devices = JpcapCaptor.getDeviceList(); showDeviceList(devices); //输入选择的监控的网卡 System.out.print("选择需要监听的设备序号:"); int card = scanner.nextInt(); card = card -1; System.out.println();
//打开选择的网络接口 JpcapCaptor captor = openDevice(devices, card);
menu: while(true) { //功能菜单 System.out.println("选择功能编号:"); System.out.println("1. 捕获当前网卡的数据包"); System.out.println("2. 停止捕获网络数据包"); System.out.println("3. 显示当前捕获的数据包"); System.out.println("4. 查看数据包详细信息"); System.out.println("5. 分析数据包的协议分布"); System.out.println("6. 退出!"); System.out.print("你的选择:"); //用户选择 int choice = scanner.nextInt();//输入选择项
//功能执行(通过选各个类型去决定下一步干什么) switch (choice){ case 1: System.out.println("正在捕获数据包...");//选择去抓包抓数据包 t = new AThread(captor); Thread capThread = new Thread(t); capThread.start(); break; case 2: System.out.println("已停止捕获数据包");//停止抓数据包 t.cancel();//调用停止方法cancel将布尔值的中断标志改为false break; case 3: System.out.println("显示当前捕获的数据包如下:");//如果线程为空那就显示失败 if(t == null) { System.out.println("数据包捕获未开启,失败!"); break; } packet = t.getPacket(); //调用获得数据包的方法 showPacket(packet);//调用展示数据包的方法 break; case 4: System.out.println("数据包详细分析:");//调用展示数据包细节的方法 showPacketDetail(packet); break; case 5: System.out.println("数据包的协议分布如下:"); analyzePacket(packet);//调用分析数据包的方法 break; case 6: System.out.println("退出!");//想要结束就break跳出menu break menu; } System.out.println();//分隔一行为了方便查看 } //关闭线程和抓包 captor.close(); } }
|
五设计中的问题以及心得
- 一开始不知道怎么去抓获数据包就去上网搜查和查看数据最终发现可以使用JPCAP扩展,jPcap是一个可以让java工作在链路层的类库调用wincap/libpcap,而给JAVA语言提供一个公共的接口,从而使用插件最终得到抓获数据包的操作。
- 第一步讨论得到后,与组员讨论最终决定使用主函数调用每个功能模块,既使用模块化设计方法得到功能。但是不知道怎么去抓获数据包之后存储,询问其他同学和查看网络API之后发现可以通过数组去存储数据包,从而用循环去不断的操作。
- 可以在抓取和实现功能后,发现还需要界面设计达到分析协议软件开发,或者通过GUI图形界面显示。
- 我和组员分工之后,我主要去实现主函数和TCP协议的分析部分,主要是和组员的代码要能配对起来比较麻烦,最终不断地调试和改bug得到了结果。
- 抓获数据包最终分析的时候,首部其他内容都可以得到调用方法得到结果,但是TCP和IP检验和部分就比较麻烦,我们最终决定使用推算字节数得到答案。
以IP检验和为例:
创建一个 HexBinaryAdapter的对象将二进制数组转换为十六进制字符串:
1
| HexBinaryAdapter head = new HexBinaryAdapter();
|
定义一个字符串存首部长度head的16进制信息:
1
| String str = head.marshal(udp.header);
|
计算IP首部部分的长度:
1 2 3 4 5 6 7 8
| int tou = (str.charAt(29)-48)*4*2; ``` 因为IP之前的MAC帧就有14个字节,而一个字节就是2位十六进制,然后通过减去0的ASCII码转换成相对应的16进制数字。 而为了获得检验和就需要转字节之后加上之前的12个字节,从13到16就是Ip数据包的检验和部分 System.out.println(" 校验和 : " +str.substring(28+tou+12,28+tou+16)); 6. 课设答辩的时候薛老师很负责的提出了我们组的设计存在的问题是:一次性显示了所有数据包的信息但是不能去挑选具体的数据包去分析是哪个协议。->于是我们在判断语句中输入要挑选的数据包,然后去判断是否存在这个数据包,然后输出分析数据包的内容。 具体解决问题的代码如下(附录中也有): 在循环中输入想查看的包的序号
|
System.out.print("输入你想查看的包序号");
Scanner input=new Scanner(System.in);
int num=input.nextInt();//输入你要选择看的包序号
num=num-1;
for(int i = 0; packet[i] != null && i < max-1; i++)
{
if(i==num) //如果选的那个数字和循环的符合的话去判断是哪个数据包
{
…//中间的就是确定存在,然后去判断是哪个协议的数据包从而输出
}
}
```