基于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

  • 下载并安装WinPcap
  • 下载Jpcap相关库,安装方法如下:
    拷贝”Jpcap.all” 到 “C:\Windows\System32” 或者 放到 “JAVA_HOME \bin” 下

Unix

  1. 下载并安装libpcap
  2. 下载Jpcap相关库,进入到”src/c” 目录,并且配置Makefile
  3. 在终端运行make命令
  4. 把libjpcap.so 拷贝到[Java directory]/jre/lib/ 中

Jpcap 介绍

Jpcap类库的基本结构如下图:

java TelnetClient用法_网络

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;
                }
            }