前言
本学期计算机网络要求写一个抓包程序,我通过网上查阅资料,如何实现抓包,实现了一个较为简单的抓包程序。
项目准备
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)值对应。另外存在的问题就是界面比较简单,不是很美观,然后没有实现点开每一个数据包能够查看具体信息的功能,均还有待完善。