Java实现抓包程序(网络协议分析程序)

  • Post author:
  • Post category:java




前言


本学期计算机网络要求写一个抓包程序,我通过网上查阅资料,如何实现抓包,实现了一个较为简单的抓包程序。






项目准备


1.首先得有java编译环境,安装并配置好jdk;

2.需要安装Winpcap,Winpcap是windows平台下的一个免费的,公共的网络访问系统(Linux系统是Libpcap);

3.还需要下载Jpcap,Jpcap就是调用Winpcap给java提供一个公共的接口,从而实现平台无关性,并能够捕获发送数据包。Jpcap包括Jpcap.jar和Jpcap.dll,两者需要版本一致,并且区分32位和64位。将Jpcap.jar导入你的idea或者Eclipse项目,并且把Jpcap.dll复制到java的jdk的bin目录下,就ok了。



注:我的项目是用idea开发的。



一、抓包功能的基本实现



前面的准备工作完成后,我们就可以使用Jpcap编程进行ip数据包的捕获了。

  • JpcapHandler :这个接口用来定义分析被捕获数据包的方法;

  • ARPPacket:这个类描述了ARP/RARP包,继承了Packet类;

  • DatalinkPacket :这个抽象类描述了数据链路层;

  • EthernetPacket :这个类描述了以太帧包,继承DatalinkPacket类;

  • ICMPPacket:这个类描述了ICMP包,继承了IPPacket类;

  • IPAddress:这个类描述了IPv4和IPv6地址,其中也包含了将IP地址转换为域名的方法;

  • IPPacket:这个类描述了IP包,继承了Packet类,支持IPv4和IPv6;

  • IPv6Option :这个类描述了IPv6选项报头;

  • Jpcap:用来捕获数据包;

  • Jpcap.JpcapInfo :Jpcap的内部类,它包含被捕获数据包的信息(在jpcap0.4修改部分BUG之后不再使用这个类);

  • JpcapSender :它用来发送一个数据包;

  • JpcapWriter :它用来将一个被捕获的数据包保存到文件;

  • Packet :这个类是所有被捕获的数据包的基类;

  • TCPPacket:这个类描述TCP包,继承了IPPacket类;

  • UDPPacket :这个类描述了UDP包,继承了IPPacket类;


    以抓取ip数据包为例,JPCAP抓包基本步骤为:绑定网络设备、抓包、分析。



    以下附上基本功能实现的代码(无界面,能够基本实现抓包功能):

import java.io.IOException;
 
import jpcap.*;
import jpcap.packet.IPPacket;
import jpcap.packet.Packet;
 
public class JpcapPacket {
	public static void main(String[] args)
	{
        /*--------------	第一步绑定网络设备       --------------*/ 
		NetworkInterface[] devices = JpcapCaptor.getDeviceList();
		
		for(NetworkInterface n : devices)
		{
			System.out.println(n.name + "     |     " + n.description);
		}
		System.out.println("-------------------------------------------");
		
		JpcapCaptor jpcap = null;
		int caplen = 1512;
		boolean promiscCheck = true;
		
		/*
		devices[ ]中的数字需要注意,这里的数字根据你的网卡而定,
		你选择抓包的网卡正确才能抓到数据包,
		不同设备在使用有线网和无线网时的都不一样,
		具体的需要自己去试验。
		*/
		try{
			jpcap = JpcapCaptor.openDevice(devices[1], caplen, promiscCheck, 50);
		}catch(IOException e)
		{
			e.printStackTrace();
		}
		
		/*----------第二步抓包-----------------*/
		int i = 0;
		while(i < 10)
		{
			Packet packet  = jpcap.getPacket();
			if(packet instanceof IPPacket && ((IPPacket)packet).version == 4)
			{
				i++;
				IPPacket ip = (IPPacket)packet;//强转
				
				System.out.println("版本:IPv4");
				System.out.println("优先权:" + ip.priority);
				System.out.println("区分服务:最大的吞吐量: " + ip.t_flag);
				System.out.println("区分服务:最高的可靠性:" + ip.r_flag);
				System.out.println("长度:" + ip.length);
				System.out.println("标识:" + ip.ident);
				System.out.println("DF:Don't Fragment: " + ip.dont_frag);
				System.out.println("NF:Nore Fragment: " + ip.more_frag);
				System.out.println("片偏移:" + ip.offset);
				System.out.println("生存时间:"+ ip.hop_limit);
				
				String protocol ="";
				switch(new Integer(ip.protocol))
				{
				case 1:protocol = "ICMP";break;
				case 2:protocol = "IGMP";break;
				case 6:protocol = "TCP";break;
				case 8:protocol = "EGP";break;
				case 9:protocol = "IGP";break;
				case 17:protocol = "UDP";break;
				case 41:protocol = "IPv6";break;
				case 89:protocol = "OSPF";break;
				default : break;
				}
				System.out.println("协议:" + protocol);
				System.out.println("源IP " + ip.src_ip.getHostAddress());
				System.out.println("目的IP " + ip.dst_ip.getHostAddress());
				System.out.println("源主机名: " + ip.src_ip);
				System.out.println("目的主机名: " + ip.dst_ip);
				System.out.println("----------------------------------------------");
			}
		}
		
	}
}



二、完整项目实现


具有界面,能够实现基本功能(查看网卡信息,开始抓包,暂停抓包(开始抓包后“开始”按钮变为“暂停”,清空界面内容,退出,以及过滤器功能的简单实现)),界面如下图:

界面如图

注:界面使用swing实现(现在swing基本很少用了,不过做个简单界面还是不错)



1.界面布局


JpCapFrame代码如下:

package packetCapture;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import java.awt.*;

/**
 * 流式布局
 */
public class JpCapFrame extends JFrame {
    private static DefaultTableModel model;
    private static JTextField filterField;
    private JTextArea showArea;
    private JButton startBtn;
    private JButton checkBtn;
    private JButton exitBtn;
    private JButton clearBtn;

    public JpCapFrame() {
        super();
        initGUI();
    }

    public static DefaultTableModel getModel() {
        return model;
    }

    public JTextArea getShowArea() {
        return showArea;
    }

    public JButton getStartBtn() {
        return startBtn;
    }

    public JButton getCheckBtn() {
        return checkBtn;
    }

    public JButton getExitBtn() {
        return exitBtn;
    }

    public JButton getClearBtn() {
        return clearBtn;
    }

    public static JTextField getFilterField() {
        return filterField;
    }

    private void initGUI() {
        Font font1 = new Font("宋体", Font.BOLD, 15);
        Font font4 = new Font("宋体", Font.BOLD, 14);
        Font font2 = new Font("宋体", Font.PLAIN, 16);
        Font font3 = new Font("微软雅黑", Font.PLAIN, 16);

        //界面
        setSize(1550, 1000);
        setVisible(true);
        setTitle("Captor");
        Container container = this.getContentPane();

        //顶部
        JPanel pane = new JPanel();
        pane.setBounds(0, 0, 775, 150);
        pane.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 0));
        pane.setPreferredSize(new Dimension(775, 27));

        checkBtn = new JButton("查看网卡信息");
        checkBtn.setFont(font4);
        checkBtn.setBounds(0, 0, 50, 0);
        pane.add(checkBtn);

        startBtn = new JButton("开始");
        startBtn.setFont(font4);
        startBtn.setBounds(0, 0, 50, 0);
        pane.add(startBtn);

        clearBtn = new JButton("清空");
        clearBtn.setFont(font4);
        clearBtn.setBounds(0, 0, 50, 0);
        pane.add(clearBtn);

        exitBtn = new JButton("退出");
        exitBtn.setFont(font4);
        exitBtn.setBounds(0, 0, 50, 0);
        pane.add(exitBtn);

        JPanel panelTest = new JPanel();
        panelTest.setBounds(775, 0, 775, 150);
        panelTest.setPreferredSize(new Dimension(775, 27));
        panelTest.setLayout(new FlowLayout(FlowLayout.RIGHT, 20, 0));

        JLabel filter = new JLabel("Filter:");
        filter.setFont(font1);
        filter.setBounds(0, 0, 500, 0);
        filterField = new JTextField(50);
        filterField.setBounds(200, 0, 500, 0);
        panelTest.add(filter);
        panelTest.add(filterField);

        //中部主体内容显示区
        String[] name = {"No.", "Time", "Source", "Destination", "Protocol", "Length", "Info"};
        //model = new DefaultTableModel();
        //model.setColumnIdentifiers(name);
        JTable table = new JTable(model);
        JTableHeader tableHeader = table.getTableHeader();
        tableHeader.setFont(font1);
        table.setFont(font2);
        table.setRowHeight(20);
        model = (DefaultTableModel) table.getModel();
        model.setColumnIdentifiers(name);
        table.setEnabled(false);
        JScrollPane jScrollPane = new JScrollPane(table);
        jScrollPane.setBounds(0, 300, 1550, 600);

        //底部
        JPanel pane2 = new JPanel();
        pane2.setLayout(new BorderLayout());
        pane2.setPreferredSize(new Dimension(1550, 300));

        showArea = new JTextArea(5, 5);
        //showArea.setBounds(0,0,1200,300);
        //showArea.setText("Test");
        showArea.setEditable(false);
        showArea.setLineWrap(false);
        showArea.setFont(font3);
        //showArea.setBackground(Color.GRAY);
        //pane2.add(showArea);
        pane2.setSize(10, 10);
        pane2.setBounds(0, 0, 1, 1);
        //给textArea添加滚动条
        JScrollPane scrollPane = new JScrollPane(showArea);
        scrollPane.setBounds(0, 0, 1, 1);
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        pane2.add(scrollPane, BorderLayout.CENTER);
        scrollPane.setViewportView(showArea);

        container.add(jScrollPane, BorderLayout.CENTER);
        container.add(pane, BorderLayout.NORTH);
        container.add(panelTest, BorderLayout.NORTH);
        container.add(pane2, BorderLayout.SOUTH);

        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}



2.抓包功能管理类


JpCapPacket代码如下:

package packetCapture;

import jpcap.JpcapCaptor;
import jpcap.packet.IPPacket;
import jpcap.packet.Packet;

import java.sql.Timestamp;
import java.util.Vector;

public class JpCapPacket {
    private JpcapCaptor jpcap;

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

    void capture() throws InterruptedException {
        int i = 0;
        while (true) {
            synchronized (JpCapMain.getThread()) {
                if (JpCapMain.isPause()) {
                    JpCapMain.getThread().wait();
                }
            }
            Packet packet = jpcap.getPacket();
            if (packet instanceof IPPacket && ((IPPacket) packet).version == 4) {
                i++;
                IPPacket ip = (IPPacket) packet;//强转

//                System.out.println("版本:IPv4");
//                System.out.println("优先权:" + ip.priority);
//                System.out.println("区分服务:最大的吞吐量: " + ip.t_flag);
//                System.out.println("区分服务:最高的可靠性:" + ip.r_flag);
//                System.out.println("长度:" + ip.length);
//                System.out.println("标识:" + ip.ident);
//                System.out.println("DF:Don't Fragment: " + ip.dont_frag);
//                System.out.println("NF:Nore Fragment: " + ip.more_frag);
//                System.out.println("片偏移:" + ip.offset);
//                System.out.println("生存时间:" + ip.hop_limit);

                String protocol = "";
                switch (new Integer(ip.protocol)) {
                    case 1:
                        protocol = "ICMP";
                        break;
                    case 2:
                        protocol = "IGMP";
                        break;
                    case 6:
                        protocol = "TCP";
                        break;
                    case 8:
                        protocol = "EGP";
                        break;
                    case 9:
                        protocol = "IGP";
                        break;
                    case 17:
                        protocol = "UDP";
                        break;
                    case 41:
                        protocol = "IPv6";
                        break;
                    case 89:
                        protocol = "OSPF";
                        break;
                    default:
                        break;
                }
//                System.out.println("协议:" + protocol);
//                System.out.println("源IP " + ip.src_ip.getHostAddress());
//                System.out.println("目的IP " + ip.dst_ip.getHostAddress());
//                System.out.println("源主机名: " + ip.src_ip);
//                System.out.println("目的主机名: " + ip.dst_ip);
//                System.out.println("----------------------------------------------");
                String filterInput = JpCapFrame.getFilterField().getText();
                if (filterInput.equals(ip.src_ip.getHostAddress()) ||
                        filterInput.equals(ip.dst_ip.getHostAddress()) ||
                        filterInput.equals(protocol) ||
                        filterInput.equals("")) {
                    Vector dataVector = new Vector();
                    Timestamp timestamp = new Timestamp((packet.sec * 1000) + (packet.usec / 1000));

                    dataVector.addElement(i + "");
                    //dataVector.addElement(new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss").format(new Date()));
                    dataVector.addElement(timestamp.toString());//数据包时间
                    dataVector.addElement(ip.src_ip.getHostAddress());
                    dataVector.addElement(ip.dst_ip.getHostAddress());
                    dataVector.addElement(protocol);
                    dataVector.addElement(packet.data.length);

                    String strtmp = "";
                    for (int j = 0; j < packet.data.length; j++) {
                        strtmp += Byte.toString(packet.data[j]);
                    }
                    dataVector.addElement(strtmp); //数据内容

                    JpCapFrame.getModel().addRow(dataVector);
                }
            }
        }
    }
}



3.主界面及功能实现


JpCapMain代码如下:

package packetCapture;

import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;

import java.io.IOException;

public class JpCapMain implements Runnable {
    JpCapFrame frame;
    JpcapCaptor jpcap = null;
    private static Thread thread = null;
    private static boolean pause = true;

    public JpCapMain() {
        //创建界面
        frame = new JpCapFrame();
        frame.setVisible(true);

        //绑定网络设备
        NetworkInterface[] devices = JpcapCaptor.getDeviceList();

        int caplen = 1512;
        boolean promiscCheck = true;

        /*
        WIFI:3
        有线:1
        (不同设备对应的不一样)
        */
        int device = 1;
        try {
            jpcap = JpcapCaptor.openDevice(devices[device], caplen, promiscCheck, 50);
        } catch (IOException e) {
            e.printStackTrace();
        }

        frame.getCheckBtn().addActionListener(e -> {
            frame.getShowArea().append("当前设备全部网络设备信息为: \n");

            for (NetworkInterface n : devices) {
                System.out.println(n.name + "     |     " + n.description);
                frame.getShowArea().append(n.name + "     |     " + n.description + "\n");
            }
            //System.out.println("-------------------------------------------");
            frame.getShowArea().append(printSeparator(110, 0));
            frame.getShowArea().append("\n当前使用网卡信息: " + devices[device].name + "     |     " + devices[device].description + "\n");
            frame.getShowArea().append(printSeparator(110, 1));
        });

        frame.getStartBtn().addActionListener(e -> {
            if (pause) {
                if (thread == null) {
                    frame.getShowArea().append("   开始抓包,抓取范围为:" + JpCapFrame.getFilterField().getText() + " ……\n");
                    thread = new Thread(this);
                    thread.setPriority(Thread.MIN_PRIORITY);
                    //thread.sleep(100);
                    thread.start();
                    pause = false;
                    frame.getStartBtn().setText("暂停");
                } else {
                    frame.getStartBtn().setText("暂停");
                    pause = false;
                    frame.getShowArea().append("   继续抓包,抓取范围为:" + JpCapFrame.getFilterField().getText() + " ……\n");
                    synchronized (thread) {
                        thread.notify();
                    }
                }
            } else {
                pause = true;
                frame.getStartBtn().setText("开始");
                frame.getShowArea().append("    暂停抓包\n");
            }
        });

        frame.getClearBtn().addActionListener(e -> {
            frame.getShowArea().setText("");
            frame.getModel().setRowCount(0);
        });

        frame.getExitBtn().addActionListener(e -> {
            System.exit(0);
        });
    }

    public static void main(String[] args) {
        new JpCapMain();
    }

    @Override
    public void run() {
        try {
            new JpCapPacket(jpcap).capture();
            thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * @param separator "-"的数量
     * @param line      "\n"的数量
     * @return
     */
    public String printSeparator(int separator, int line) {
        String s = "";
        String l = "";

        for (int i = 0; i < separator; i++) {
            s += "-";
        }

        for (int i = 0; i < line; i++) {
            l += "\n";
        }
        return s + l;
    }

    public static Thread getThread() {
        return thread;
    }

    public static boolean isPause() {
        return pause;
    }
}


项目完整代码如上。




总结


本项目基本实现了抓包的功能,但是因为做的比较赶,所以还有很多功能没有完善。比如演示的时候发现有的同学有选择网卡的功能,我这里只做了查看网卡的功能,但是实现这个功能还是不难的,就是在devices[device]中想办法能够通过选择网卡与device(int)值对应。另外存在的问题就是界面比较简单,不是很美观,然后没有实现点开每一个数据包能够查看具体信息的功能,均还有待完善。



版权声明:本文为twj1248445531原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。