基于Jpcap 的Traceroute Java 的实现
ICMP 实现Traceroute原理
ICMP 报文类型
ICMP类型 | 编码 | 描述 |
0 | 0 | 回显回答(对ping的回答) |
3 | 0 | 目的网络不可达 |
3 | 1 | 目的主机不可达 |
3 | 2 | 目的协议不可达 |
3 | 3 | 目的端口不可达 |
3 | 6 | 目的网络未知 |
3 | 7 | 目的主机未知 |
4 | 0 | 源抑制(拥塞) |
8 | 0 | 回显请求 |
9 | 0 | 路由器通告 |
10 | 0 | 路由器发现 |
11 | 0 | TTL过期 |
12 | 0 | IP首部损坏 |
ICMP发送过程
首先给目的主机发送一个TTL = 1数据报,经过第一个路由器收到这个数据报以后,自动把TTL减1,而TTL变为0以后,路由器返回一个主机不可达的ICMP数据报给源主机。源主机收到这个数据报后再发送一个TTL=2 数据报,依次类推。当第n个数据报达到第n台路由器时,第n台路由器观察到这个数据报TTL正好过期。根据IP协议规则,路由器丢弃该数据报并发送一个ICMP告警报文给源主机(类型11 编码0)。该告警报文包含了路由器的名字与它的IP地址。当该ICMP报文返回源主机时,源主机从定时器得到往返时延,从ICMP报文中得到第n台路由器名字与IP地址。
源主机为发送的每个报文段的TTL字段+1,因此这些数据报之一将最终沿着这条路到达目的主机。因为该数据报包含了一个具有不可达端口号的UDP报文段,该目的主机将向源发送一个端口不可达的ICMP报文段。当源主机接受到这个特别的ICMP报文时,知道它不需要再发送另外的探测分组。(标准的Tracerote程序实际上用相同的TTL发送三个一组的分组,因此Traceroute输出对每个TTL提高了3个结果)
安装(预安装JAVA环境)
Windows
Unix
- 下载并安装libpcap
- 下载Jpcap相关库,进入到”src/c” 目录,并且配置Makefile
- 在终端运行make命令
- 把libjpcap.so 拷贝到[Java directory]/jre/lib/ 中
Jpcap 介绍
Jpcap类库的基本结构如下图:
Jpcap 类库结构
主要步骤
1、 获取网卡信息
主机可能存在多个网卡,传入deviceNo选择网卡。在这次实验中,我选择以太网网卡deviceNo为0。
private void openDeviceOnInterface(int deviceNo)
{
device = JpcapCaptor.getDeviceList()[deviceNo];
localIP = null;
captor = null;
try{
captor = JpcapCaptor.openDevice(device, 2000, false, 1);
for(NetworkInterfaceAddress addr : device.addresses)
{
if(addr.address instanceof Inet4Address){
localIP = addr.address;
System.out.println("The IP address of the source node: "
+ localIP);
break;
}
}
}catch(Exception e){
device = null;
localIP = null;
captor = null;
}
}
2、 获取默认网关物理地址
private byte[] obtainDefaultGatewayMac(String httpHostToCheck)
{
byte[] gatewayMac = null;
if(captor != null){
try{
InetAddress hostAddr = InetAddress.getByName(httpHostToCheck);
captor.setFilter("tcp and dst host " + hostAddr.getHostAddress(), true);
int timeoutTimer = 0;
new URL("http://" + httpHostToCheck).openStream().close();
while(running)
{
Packet ping = captor.getPacket();
if(ping == null)
{
if(timeoutTimer < 20){
interruptibleSleep(100);
timeoutTimer ++;
continue;
}
System.out.println("ERROR: Cannot obtain MAC address for default gateway.");
return gatewayMac;
}
byte[] destinationMac =((EthernetPacket)ping.datalink).dst_mac;
if( !Arrays.equals(destinationMac, device.mac_address)){
gatewayMac = destinationMac;
break;
}
timeoutTimer = 0;
new URL("http://" + httpHostToCheck).openStream().close();
}
}catch(Exception e)
{
e.printStackTrace();
}
}
return gatewayMac;
}
3、创建ICMP数据包
ICMPPacket icmp = new ICMPPacket();
icmp.type = ICMPPacket.ICMP_ECHO;
icmp.seq = 100;
icmp.id = 0;
icmp.data = "data".getBytes();
icmp.setIPv4Parameter(
0,
false,
false,
false,
0,
false,
false,
false,
0,
0,
0,
IPPacket.IPPROTO_ICMP,
localIP,
remoteIP);
4、设置以太网数据包
EthernetPacket ether = new EthernetPacket();
ether.frametype = EthernetPacket.ETHERTYPE_IP;
ether.src_mac = device.mac_address;
ether.dst_mac = defaultGatewayMac;
5、发送ICMP包
JpcapSender sender = captor.getJpcapSenderInstance();
captor.setFilter("icmp and dst host " + localIP.getHostAddress(), true);
icmp.hop_limit = (short) initailTTL;
int timeoutTimer = 0;
int timeoutCounter = 0;
long tStart = System.currentTimeMillis();
sender.sendPacket(icmp);
int counter = 0;
while(running)
{
ICMPPacket p = (ICMPPacket)captor.getPacket();
int tDelay = (int)( ( System.currentTimeMillis() - tStart ));
if(p == null) // timeout
{
if( timeoutTimer < 30)
{
interruptibleSleep(timeoutTimer < 10 ? 1 : 10);
timeoutTimer ++;
continue;
}
timeoutCounter ++;
interruptibleSleep(100);
if(timeoutCounter < 3)
{
tStart = System.currentTimeMillis();
timeoutTimer = 0;
sender.sendPacket(icmp);
}else{
icmp.hop_limit++;
timeoutTimer = 0;
timeoutCounter = 0;
tStart = System.currentTimeMillis();
sender.sendPacket(icmp);
}
continue;
}
String hopID = p.src_ip.getHostAddress();
if(p.type == ICMPPacket.ICMP_TIMXCEED)
{
interruptibleSleep(10);
if(counter < 3)
{
if(icmp.hop_limit != 0)
times.add(tDelay);
tStart = System.currentTimeMillis();
timeoutTimer = 0;
timeoutCounter = 0;
counter++;
sender.sendPacket(icmp);
continue;
}
counter = 1;
if(icmp.hop_limit != 0){
times.add(tDelay);
datas.put(hopID, new ArrayList<>());
datas.get(hopID).addAll(times);
IPList.add(hopID);
times.clear();
}
icmp.hop_limit ++;
timeoutTimer = 0;
timeoutCounter = 0;
tStart = System.currentTimeMillis();
sender.sendPacket(icmp);
}else if (p.type == ICMPPacket.ICMP_UNREACH)
{
System.out.println(hopID + " unreachable");
running = false;
}else if(p.type == ICMPPacket.ICMP_ECHOREPLY)
{
if(counter < 3)
{
interruptibleSleep(10);
times.add(tDelay);
tStart = System.currentTimeMillis();
timeoutTimer = 0;
timeoutCounter = 0;
counter++;
sender.sendPacket(icmp);
continue;
}
counter = 1;
times.add(tDelay);
datas.put(hopID, new ArrayList<>());
datas.get(hopID).addAll(times);
IPList.add(hopID);
times.clear();
if(initailTTL != 0){
System.out.println(hopID + "is alive.");
}
running = false;
}
}