基于Java_使用Jpcap进行网络抓包并分析(1万字保姆级教程)二

目录

前言

一、Jpcap类综述

二、逐层分析数据包

1. 数据链路层

1.1 方法分析

1.2 代码实现

2. 网络层

2.1 方法分析

2.2 代码实现

3. 传输层

3.1 方法分析

3.2 代码实现

三、回调抓包并分析

1. 回顾: 回调抓包

2. 代码实现

四、逐个捕获并分析(线程实现)

1. 分析数据包类

2. 抓包线程类

3. 测试类

总结


前言

本学期学习了《计算机网络》专业课程,老师布置了课程设计大作业,作业要求如下:

  • 使用 JPCAP或 wireshark等抓包,可以使用JAVA、PYTHON或C++写代码对数据进行分析最后可视化显示

本文只实现了使用 Java语言的Jpcap接口 在 IDEA环境下抓取数据包并分析的功能,并未实现可视化功能。

  • 之前博主发布了 “利用Jpcap接口 在 IDEA环境下抓取数据包” 的博客,本文是对该博文的续章,如果有些朋友在观看本文时,对其中一些内容不太理解,可以去上一篇博文看看,链接如下:
  • 基于Java--使用jpcap接口抓包(非常详细)

回顾上一篇博客:利用 Jpcap接口解决了如何从指定网卡中捕捉并过滤数据包,但还未能对捕获到的数据包进行分析和统计,现在我们来解决分析和统计数据包的问题。


一、Jpcap类综述

  • 我们在成功抓取到一个数据包 packet后,只是得到了一个很单纯的数据包,packet它的内在(例如它是什么类型的、长度、是否分片,等等)我们还看不见,要使用 Jpcap所提供的一些类和方法,一层一层地剖析 packet,最终得到我们想要的数据。所以首先我们要学习Jpcap 提供的一些重要的分析数据包的类和方法,如下:
Packet这个类是所有被捕获的数据包的基类,继承了 java.lang.Object
DatalinkPaeket这个抽象类描述了数据链路层的包,它继承了 java.lang.Object
EthemetPacket这个类描述了以太帧包,继承了 DatalinkPacket类
IPPacket这个类描述了 IP包,继承了 Packet类,支持 IPv4和 IPv6
IP Address继承了 java. lang. Object,这个类描述了 IPv4和 IPv6地址,其中也包含了将 IP地址转换为域名的方法
ARPPacket这个类描述了 ARP/RARP包,继承了 Packet类
TCPPacket这个类描述了 TCP包,继承了 IPPacket类
UDPPacket这个类描述了 UDP包,继承了 IPPacket类
ICMPPacket这个类描述了 ICMP包,继承了 IPPacket类
  • 由于对上面类中的方法逐个讲解需要篇幅过长,我将Jpcap中文API文档上传到GitHub上了,需要的同学可以自取,该文档来源网络,侵删致歉。
  • Jpcap中文使用文档_GitHub
  • 接下来将会结合代码讲解Jpcap类的使用。

二、逐层分析数据包

  • 在传输过程前,每一个数据包都会被层层封装,最后以比特流的形式进行传输,在此我们将结合代码分析数据包封装最重要的3层结构,分别是数据链路层、网络层和传输层,每一层都对应着Jpcap不同的类和方法。
  • Packet类所有被捕获的数据包的基类,提供了分析数据包一些基本属性的方法,如下图:

​ 

1. 数据链路层

1.1 方法分析

  • 数据包在数据链路层被封装成帧,每一个帧都是由帧头、帧尾和数据载荷部分构成。 DatalinkPacket类和 EthemetPacket类提供了方法分析数据包的以太帧头部,如下图:

​ 

1.2 代码实现

  • 运行以下代码可以打印出每个数据包的帧头部所包含的信息,可以根据帧头部的帧类型判断该帧封装了什么类型的数据报,大家可以试试。
/* 数链层,显示数据包的帧头部,每一个数据包都有,都要解析 */
   // EthernetPacket类继承 DataPacket类;将 DataPacket类向下转型成EthernetPacket类;

   EthernetPacket dataLink = (EthernetPacket) packet.datalink;// DatalinkPacket datalink: 数据链路层报头/以太帧报头;
   System.out.println("以太帧首部描述 : " + dataLink.toString());// 描述以太帧头部的字符串
   System.out.println("源mac地址 : " + dataLink.getSourceAddress());// 源mac地址
   System.out.println("目的mac地址 : " + dataLink.getDestinationAddress());// 目的mac地址
   System.out.println("帧类型 : " + dataLink.frametype);// 帧类型

2. 网络层

  • 数据包在网络层有两种类型,分别是IP数据报文和ARP数据报文,IPPacket类和 ARPPacket类提供了分析网络层数据包首部的方法,它们都继承了 Packet类。

2.1 方法分析

2.1.1 IPPacket类

 2.1.2 ARPPacket类

2.2 代码实现

  • 在 IPPacket类和 ARPPacket类帮助下,我们很容易就可以分析网络层报文的首部内容,但是需要注意的是,数据包是被层层封装的,那我们如何知道一个数据包是 IP数据包还是 ARP数据包呢?
  • 这时需要使用 getClass()方法,返回抓取到的数据包的类对象,然后使用 equals()方法进行比较,代码如下:
if (packet.getClass().equals(IPPacket.class)) {

       // IP数据包, IPPacket类继承 Packet类,包括 IPV4和 IPV6;
       IPPacket ipPacket = (IPPacket) packet;// 将 packet类转成 IPPacket类;
       System.out.println("IP报文首部 : " + ipPacket.toString());
       System.out.println("版本version :" + ipPacket.version);
       System.out.println("时间戳sec(秒) : " + ipPacket.sec);
       System.out.println("时间戳usec(毫秒) : " + ipPacket.usec);
       System.out.println("源IP : " + ipPacket.src_ip.getHostAddress());
       System.out.println("目的IP : " + ipPacket.dst_ip.getHostAddress());
       System.out.println("协议protocol : " + ipPacket.protocol);
       System.out.println("优先权priority:" + ipPacket.priority);
       System.out.println("生存时间hop:" + ipPacket.hop_limit);
       System.out.println("标志位RF:保留位必须为false: " + ipPacket.rsv_frag);
       System.out.println("标志位DF:是否允许分片: " + ipPacket.dont_frag);
       System.out.println("标志位MF:后面是否还有分片: " + ipPacket.more_frag);
       System.out.println("片偏移offset:" + ipPacket.offset);
       // 抓到的flowable 流标签的包,是ipv6数据包;
       System.out.println("标识ident:" + ipPacket.ident);
} else if (packet.getClass().equals(ARPPacket.class)) {

          // ARP数据包,ARPPacket类继承 Packet类;
          ARPPacket arpPacket = (ARPPacket) packet;// 将 packet类转成 ARPPacket类;
          System.out.println("硬件类型hardtop : " + arpPacket.hardtype);
          System.out.println("协议类型prototype : " + arpPacket.prototype);
          System.out.println("操作字段operation : " + arpPacket.operation);
          System.out.println("IP首部 : " + arpPacket.toString());// String toString: 返回描述此数据包的字符串;
          System.out.println("发送方硬件地址 : " + arpPacket.getSenderHardwareAddress());
          System.out.println("接收方硬件地址 : " + arpPacket.getTargetHardwareAddress());
          System.out.println("发送方IP地址 : " + arpPacket.getSenderProtocolAddress());
          System.out.println("接收方IP地址 : " + arpPacket.getTargetProtocolAddress());

}
  •  代码运行后将会打印出数据包网络层的首部内容。

3. 传输层

  • 数据报文在传输层有三种类型,分别是TCP数据报文、UDP数据报文和 ICMP数据报文,TCPPacket类 、UDPPacket类 和 ICMPPacket类提供了分析传输层数据报文首部的方法,它们都继承了 IPPacket类。

3.1 方法分析

  • 由于这三个类的方法篇幅稍长,我就不贴在此处了,有兴趣的同学可以自己去阅读Jpcap中文API文档,文件我已经放在GitHub仓库了。
  • Jpcap中文使用文档_GitHub

3.2 代码实现

  • TCP数据报文、UDP数据报文和 ICMP数据报文在网络层都被封装成了 IP数据报,因此我们需要使用上述的 getClass() 和 equals() 方法对它们判断,然后才能正确分析捕获到的数据报文。代码如下:
/* 传输层,显示数据包的 ICMP/ TCP/ UDP 首部 */

if (packet.getClass().equals(TCPPacket.class)) {

        // TCP数据报,TCPPacket类继承 IPPacket类;
        TCPPacket tcpPacket = (TCPPacket) packet;// 将 TCPPacket类转成 IPPacket类;

        System.out.println("TCP报文首部:" + tcpPacket.toString());
        System.out.println("标志位DF:是否允许分片: " + tcpPacket.dont_frag);
        System.out.println("标志位MF:后面是否还有分片: " + tcpPacket.more_frag);
        System.out.println("片偏移offset:" + tcpPacket.offset);
        System.out.println("标识ident:" + tcpPacket.ident);
        System.out.println("TCP报文");
        System.out.println("源端口src_port:" + tcpPacket.src_port);
        System.out.println("目的端口dst_port:" + tcpPacket.dst_port);
        System.out.println("seq序号:" + tcpPacket.sequence);
        System.out.println("窗口大小window:" + tcpPacket.window);
        System.out.println("ACK标志:" + tcpPacket.ack);// boolean ack :ACK标志
        System.out.println("ack:" + tcpPacket.ack_num);// long ack_num :确认号

        } else if (packet.getClass().equals(UDPPacket.class)) {

        // UDP数据报,UDPPacket类继承 IPPacket类;
        UDPPacket udpPacket = (UDPPacket) packet;

        System.out.println("UDP报文首部:" + udpPacket.toString());
        System.out.println("标志位DF:是否允许分片: " + udpPacket.dont_frag);
        System.out.println("标志位MF:后面是否还有分片: " + udpPacket.more_frag);
        System.out.println("片偏移offset:" + udpPacket.offset);
        System.out.println("标识ident:" + udpPacket.ident);
        System.out.println("UDP报文");
        System.out.println("源端口src_port:" + udpPacket.src_port);
        System.out.println("目的端口dst_port:" + udpPacket.dst_port);

        } else if (packet.getClass().equals(ICMPPacket.class)) {

        // ICMP数据报,ICMPPacket类继承 IPPacket类;
        ICMPPacket icmpPacket = (ICMPPacket) packet;

        System.out.println("ICMP报文首部:" + icmpPacket.toString());// 只包含报文类型和代码;
        System.out.println("标志位DF:是否允许分片: " + icmpPacket.dont_frag);
        System.out.println("标志位MF:后面是否还有分片: " + icmpPacket.more_frag);
        System.out.println("片偏移offset:" + icmpPacket.offset);
        System.out.println("标识ident:" + icmpPacket.ident);
        System.out.println("ICMP报文类型type:" + icmpPacket.type);
        System.out.println("ICMP报文代码code:" + icmpPacket.code);

}
  • 到这里,我们对数据包逐层的报文首部的分析就结束了。接下来让我们把分析的代码放进程序中,实现数据包的分析。

 三、回调抓包并分析

1. 回顾: 回调抓包

  • 在上一篇博文中讲解了利用 processPacket()方法和 loopPacket()方法可以进行抓包,并将抓取到的数据包传给实现了 PacketReceiver接口的 receivePacket()方法进行分析。我们回忆一下抓包的代码,如下:

import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;
import jpcap.PacketReceiver;
import jpcap.packet.Packet;
import java.io.IOException;
import java.util.Scanner;

// 类 Receiver实现了 PacketReceiver接口的 receivePacket()方法;
class Receiver implements PacketReceiver {
    @Override
    // 重写 PacketReceiver接口中的 receivePacket()方法;
    // 实现类中的处理接收到的 Packet 对象的方法,每个 Packet对象代表从指定网络接口上抓取到的数据包;
    // 抓到的包将调用这个 PacketReceiver对象中的 receivePacket(Packet packet)方法处理;
    public void receivePacket(Packet packet) {
        System.out.println(packet);// 直接将捕获的包输出,不做任何处
        System.out.println("---------------------------------------");
    }
}

public class JpcapProcess {
    public static void main(String[] args) {

        // 第一步,获得网卡设备列表;
        NetworkInterface[] devices = JpcapCaptor.getDeviceList();
        if (devices.length == 0) {
            System.out.println("无网卡信息!");
            return;
        }
        // 将本机上所有网卡名称及其网卡描述全部显示出来;
        // 这些信息可以在 wireshark中的网络接口处看到,是一一对应的,或者在cmd 用ipconfig /all 可以全部显示;
        int k = -1;// 网络设备序号
        for (NetworkInterface n : devices) {
            k++;
            // NetworkInterface 类中的 name和 description方法可以显示设备的名称和描述信息;
            System.out.println("序号" + k + "  " + n.name + "    |    " + n.description);
        }
        System.out.println("-------------------------------------------");

        Scanner sc = new Scanner(System.in);
        System.out.println("请选择您要监听的网卡序号:");
        int index = sc.nextInt();

        //第二步,监听选中的网卡;
        try {
            // 参数一:选择一个网卡,调用 JpcapCaptor.openDevice()连接,返回一个 JpcapCaptor类的对象 jpcap;
            // 参数二:限制每一次收到一个数据包,只提取该数据包中前1512个字节;
            // 参数三:设置为非混杂模式,才可以使用下面的捕获过滤器方法;
            // 参数四:指定超时的时间;

            JpcapCaptor jpcap = JpcapCaptor.openDevice(devices[index], 2000, false, 10000);

        //第三步,捕获数据包;
            // 调用 processPacket()方法, count = -1对该方法无影响,主要受 to_ms控制,改成其他数值则会控制每一次捕获包的数目;
            // 换而言之,影响 processPacket()方法的因素有且只有两个,分别是count 和 to_ms;
            // 抓到的包将调用这个 new Receiver()对象中的 receivePacket(Packet packet)方法处理;
            jpcap.setFilter("arp", true);
            jpcap.processPacket(4, new Receiver());

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("抓取数据包时出现异常!!");
        }
    }
}
  • 回忆结束,在上面的代码中还未实现对抓取到的数据包进行分析。接下来我们将重写 receivePacket()方法,在其代码块中实现对数据包的分析和统计。

2. 代码实现

  • 在分析数据包时,我们定义各种数据包的count 变量,用来统计每种数据包出现的次数;定义TCP和UDP数据报文的 len 变量,用来统计这两种数据包的总长度。
  • 由于篇幅过长,这里只贴上了部分代码,源码上传到了源码获取_GitHub中,需要源码的朋友可以自取。
// 类 Receiver实现了 PacketReceiver接口的 receivePacket()方法;
class Receiver implements PacketReceiver {
    
    private static int packetCount = 0;// count 用来统计各种是数据报出现次数;
    private static int packetPacketCount = 0;
    private static int ipPacketCount = 0;
    private static int tcpPacketCount = 0;
    private static int udpPacketCount = 0;
    private static int icmpPacketCount = 0;
    private static int arpPacketCount = 0;
    private static int elsePacketCount = 0;
    private static int tcpPacketLength = 0;// length 用来统计TCP、UDP报文长度;
    private static int udpPacketLength = 0;

    @Override
    public void receivePacket(Packet packet) {
        packetCount++;
        // System.out.println(packet);// 直接将捕获的包输出,不做任何处理;

        /* 数链层,显示数据包的帧头部,每一个数据包都有,都要解析 */
        // EthernetPacket类继承 DataPacket类;
        EthernetPacket dataLink = (EthernetPacket) packet.datalink;// DatalinkPacket datalink: 数据链路层报头/以太帧报头;
        System.out.println("以太帧首部 : " + dataLink.toString());// 描述以太帧的字符串
        System.out.println("源mac地址 : " + dataLink.getSourceAddress());// 源mac地址
        System.out.println("目的mac地址 : " + dataLink.getDestinationAddress());// 目的mac地址
        System.out.println("帧类型 : " + dataLink.frametype);// 帧类型
        ......
}


四、逐个捕获并分析(线程实现)

  • 我们上面利用回调方法可以方便地捕获并分析数据包,但是却很难利用线程实现(也可能是受限于博主的水平不够),下面我们将实现利用 getPacket() 方法在线程中实现逐个捕获多个数据包。

1. 分析数据包类

  • 创建一个 AnalyzePacket类用来分析和统计捕获到的数据包。代码内容同上面分析的代码,为了节省篇幅,就不贴出来了。源码上传到了GitHub上,需要自取。

2. 抓包线程类

  • 创建一个实现了 Runnable接口的抓包线程类,用来捕获数据包。
  • 代码如下:
package Jpcap_Test;

import jpcap.JpcapCaptor;
import jpcap.packet.*;

// 线程任务类,用来捕获数据并分析数据包;
class CaptureThread implements Runnable {
    private JpcapCaptor jpcap;

    public CaptureThread(JpcapCaptor jpcap) {
        this.jpcap = jpcap;
    }

    boolean run = true;// 用来控制是否抓包;

    @Override
    public void run() {

        while (run) {
            // 抓包并分析
            try {
                printThreadInfo();
                // 开始抓包
                Packet packet = jpcap.getPacket();
                // 创建分析数据包对象 ap,分析数据包
                AnalyzePacket ap = new AnalyzePacket(packet);
                ap.analyzePacket();
                // 抓包线程休眠1秒;
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static void printThreadInfo() {
        System.out.println("当前运行的线程名为: " + Thread.currentThread().getName());
    }
}

3. 测试类

  • 创建一个测试类,在测试类中我们将启动指定的网卡接口和抓包线程。
  • 代码如下:
package Jpcap_Test;

import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;

import java.io.IOException;

// 测试类
public class JpcapGetPacketThread {
    public static void main(String[] args) {
        NetworkInterface[] devices = JpcapCaptor.getDeviceList();
        int k = -1;
        for (NetworkInterface n : devices) {
            k++;
            System.out.println("序号" + k + "  " + n.name + "    |    " + n.description);
        }
        System.out.println("--------------------------------------------------------");
        // 启动一个网卡;
        JpcapCaptor jpcap = null;
        try {
            // 注意! getPacket()方法不受to_ms 参数的影响;此处网卡的选择因不同电脑而异;
            jpcap = JpcapCaptor.openDevice(devices[7], 4800, true, 200);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("抓取数据包时出现异常!!");
        }
        // 创建抓包任务 c1;
        CaptureThread c1 = new CaptureThread(jpcap);
        // 创建抓抓包线程 t1;
        Thread t1 = new Thread(c1);
        // 启动 t1线程,开始抓包并分析;
        t1.start();
        // 主线程休眠5秒,此处控制抓包时间;
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 停止抓包,将控制抓包任务的变量设为false;
        c1.run = false;
    }
}
  • 好啦!只要一步一步实现上面的代码,便可以实现在线程中抓包并分析了,至于为什么要在这里引出线程,是因为在最后一步的可视化中可能需要使用到多线程了。

总结

  1. 经过了一个星期的不断尝试和摸索,思考和总结,小白博主终于搞定了对捕获的数据包分析并统计的要求了。可是老师的大作业要求远远没有这么简单。。。
  2. 呜呜~~有点后悔当初选择了java实现这个大作业,但是既然选择了,那就要全力以赴地完成呀!
  3. 剩下的内容就是将统计好的数据可视化了,博主暂时还没有什么好的想法,如果有好点子的朋友可以在评论区和博主一起学习讨论呀!热烈欢迎!!
  4. 如果本文有不足之处,请各位大佬批评指正,如果而本文对你有帮助的话,那就点个赞吧哈哈!!

  • 40
    点赞
  • 110
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林二月er

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值