從0到1用java再造tcpip協(xié)議棧:使用jpacap模擬數(shù)據(jù)鏈路層

我們上一節(jié)成功使用jpcap獲得了網(wǎng)卡硬件,我們要重新構(gòu)造tcp/ip協(xié)議棧,那么就需要做兩部分工作。一部分由上層協(xié)議完成,他們的工作是將要發(fā)送的數(shù)據(jù)進(jìn)行封裝,主要是在數(shù)據(jù)包上添加包頭數(shù)據(jù)結(jié)構(gòu),包頭里有很多控制字節(jié),用于不同節(jié)點(diǎn)間進(jìn)行數(shù)據(jù)傳送時(shí)對(duì)傳送過(guò)程的控制和調(diào)整,了解,掌握,實(shí)現(xiàn)每層數(shù)據(jù)協(xié)議的包頭結(jié)構(gòu)以及數(shù)據(jù)控制流程是我們系列課程的重點(diǎn)和難點(diǎn)。

有了上層數(shù)據(jù)封裝后,剩下的就需要下層硬件將數(shù)據(jù)準(zhǔn)確的發(fā)送到指定目的地。負(fù)責(zé)將數(shù)據(jù)包轉(zhuǎn)換為電信號(hào)傳輸?shù)搅硪欢说模褪菙?shù)據(jù)鏈路層。我們無(wú)需了解它的實(shí)現(xiàn)原理,只要把它作為一個(gè)黑盒子,當(dāng)上層數(shù)據(jù)經(jīng)過(guò)各層協(xié)議封裝好后,傳入這個(gè)黑盒子,然后確保它能將信息正確的傳送出去即可,本節(jié)我們看看這個(gè)黑盒子如何使用。

屏幕快照 2018-11-27 下午5.38.41.png

我們本節(jié)要模擬實(shí)現(xiàn)的就是上圖所表示的network interface。上一節(jié)我們使用jpcap列舉了機(jī)器當(dāng)前具備的網(wǎng)卡,其中有很多是虛擬網(wǎng)卡,也就是它們不具備數(shù)據(jù)的接受和發(fā)送功能,因此我們要從中找到可以使用的真正硬件網(wǎng)卡,辨別網(wǎng)卡是否可用的一個(gè)標(biāo)準(zhǔn)是,看他是否具備ipv4的地址格式,下面代碼就用于從jpcap列舉的所有網(wǎng)卡中獲取硬件網(wǎng)卡:

NetworkInterface[] devices = JpcapCaptor.getDeviceList();
        NetworkInterface device = null;
        
        System.out.println("there are " + devices.length +  " devices");

        for (int i = 0; i < devices.length; i++) {
            boolean findDevice = false;
            
            for (NetworkInterfaceAddress addr  : devices[i].addresses) {
                //網(wǎng)卡網(wǎng)址符合ipv4規(guī)范才是可用網(wǎng)卡
                if (!(addr.address instanceof Inet4Address)) {
                    continue;
                }
                
               findDevice = true;
               break;
            }
            
            if (findDevice) {
                device = devices[i];
                break;
            }
            
        }

上面代碼通過(guò)jpcap遍歷當(dāng)前所有網(wǎng)卡,然后看哪個(gè)網(wǎng)卡的ip地址符合ipv4格式,符合的就是可以用于發(fā)送和接收數(shù)據(jù)的硬件網(wǎng)卡。接下來(lái)我們看看如何從網(wǎng)卡上截取到來(lái)的數(shù)據(jù)包。

要通過(guò)jpcap從網(wǎng)卡獲取數(shù)據(jù),首先需要繼承一個(gè)接口叫PacketReceiver,然后實(shí)現(xiàn)receivePacket接口。我們?cè)诠こ滔滦陆ㄒ粋€(gè)文件叫DataLinkLayer.java,其實(shí)現(xiàn)內(nèi)容如下:

import jpcap.packet.DatalinkPacket;
import jpcap.packet.EthernetPacket;
import jpcap.packet.ICMPPacket;
import jpcap.packet.IPPacket;
import jpcap.packet.Packet;
import jpcap.packet.TCPPacket;
import jpcap.packet.UDPPacket;

public class DataLinkLayer implements jpcap.PacketReceiver {
    String protocoll[] = {"HOPOPT", "ICMP", "IGMP", "GGP", "IPV4", "ST", "TCP", "CBT", "EGP", "IGP", "BBN", "NV2", "PUP", "ARGUS", "EMCON", "XNET", "CHAOS", "UDP", "mux"};
    
    @Override
    public void receivePacket(Packet packet) {
       
        boolean show_tcp = false, show_icmp = true, show_udp = false;
        
        IPPacket tpt=(IPPacket)packet;
        if (packet != null) {
            int ppp=tpt.protocol;
            String proto=protocoll[ppp];
          
            if (proto.equals(("TCP")) && show_tcp) {
                   System.out.println("\nthis is TCP packet");
                   TCPPacket tp = (TCPPacket) packet;
                   System.out.println("this is destination port of tcp :" + tp.dst_port);
                   if (tp.ack) {
                         System.out.println("\n" + "this is an acknowledgement");
                   } else {
                         System.out.println("this is not an acknowledgment packet");
                   }

                   if (tp.rst) {
                         System.out.println("reset connection ");
                   }
                   System.out.println(" \n protocol version is :" + tp.version);
                   System.out.println("\n this is destination ip " + tp.dst_ip);
                   System.out.println("this is source ip"+tp.src_ip);
                   if(tp.fin){
                         System.out.println("sender does not have more data to transfer");
                   }
                   if(tp.syn){
                         System.out.println("\n request for connection");
                   }

             }else if(proto.equals("ICMP") && show_icmp){
                   ICMPPacket ipc=(ICMPPacket)packet;
                        
                   System.out.println("\nThis ICMP Packet");
                   System.out.println(" \n this is alive time :"+ipc.alive_time);
                   System.out.println("\n number of advertised address :"+(int)ipc.addr_num);
                   System.out.println("mtu of the packet is :"+(int)ipc.mtu);
                   System.out.println("subnet mask :"+ipc.subnetmask);
                   System.out.println("\n source ip :"+ipc.src_ip);
                   System.out.println("\n destination ip:"+ipc.dst_ip);
                   System.out.println("\n check sum :"+ipc.checksum);
                   System.out.println("\n icmp type :"+ipc.type);
                   System.out.println("");
             }
             else if(proto.equals("UDP")  && show_udp){
                  UDPPacket pac=(UDPPacket)packet;
                  System.out.println("this is udp packet \n");
                  System.out.println("this is source port :"+pac.src_port);
                  System.out.println("this is destination port :"+pac.dst_port);

             }
        }
        else{
            System.out.println("dft bi is not set. packet will  be fragmented \n");
        }
    }
}

在上面代碼中,當(dāng)監(jiān)聽的網(wǎng)卡有數(shù)據(jù)包抵達(dá)時(shí),jpcap會(huì)調(diào)用上面類所實(shí)現(xiàn)的receviePacket接口,將接收到的數(shù)據(jù)包傳入。在代碼中我們注意監(jiān)控三種網(wǎng)絡(luò)數(shù)據(jù)包,他們分別是tcp, icmp, 和udp,我們用三個(gè)布爾變量來(lái)控制是否打印相應(yīng)包的信息,上面代碼實(shí)現(xiàn)中,我們只打印icmp協(xié)議數(shù)據(jù)包。

icmp協(xié)議其實(shí)就是我們常用的ping命令,用來(lái)看看網(wǎng)絡(luò)通不通。上面代碼完成后,我們需要打開要監(jiān)控的網(wǎng)卡對(duì)象,構(gòu)建上面對(duì)象一個(gè)實(shí)例后,將它傳入jpcap框架,以便接口被回調(diào),然后獲得數(shù)據(jù)包,回到項(xiàng)目的主入口,添加如下代碼:

public class ProtocolEntry   {
    public static void main(String[] args) throws IOException {
        ....
            System.out.println("open divice: " + device.name);
        
        JpcapCaptor jpcap = JpcapCaptor.openDevice(device, 2000, true, 20);

       jpcap.loopPacket(-1, (jpcap.PacketReceiver) new DataLinkLayer());
    }
}

我們前面的代碼已經(jīng)找到可以收發(fā)數(shù)據(jù)包的網(wǎng)卡對(duì)象了,此時(shí)我們通過(guò)openDevice調(diào)用獲得網(wǎng)卡硬件的使用權(quán),然后構(gòu)造DataLinkLayer實(shí)例,傳入到loopPacket調(diào)用里,-1表示持續(xù)不停的監(jiān)聽對(duì)應(yīng)網(wǎng)卡上的數(shù)據(jù)包,于是程序進(jìn)入一個(gè)死循環(huán),一旦網(wǎng)卡有數(shù)據(jù)包抵達(dá)時(shí),DataLinkLayer實(shí)例的receivePacket函數(shù)就會(huì)被調(diào)用,同時(shí)數(shù)據(jù)包對(duì)象會(huì)被傳入。我們先運(yùn)行起代碼,得到如下情況:

屏幕快照 2018-12-04 下午5.04.18.png

由于我們此時(shí)健康網(wǎng)卡上的ping數(shù)據(jù),由于當(dāng)前沒(méi)有ping數(shù)據(jù)包出現(xiàn)在網(wǎng)卡上,所以我們的程序進(jìn)入等待狀況,此時(shí)打開控制臺(tái),執(zhí)行一個(gè)ping命令,如下:

屏幕快照 2018-12-04 下午5.06.09.png

這是我們?cè)诳磈ava程序控制臺(tái)就會(huì)發(fā)現(xiàn)ping包的相關(guān)數(shù)據(jù)被打印出來(lái):

屏幕快照 2018-12-04 下午5.07.18.png

后面我們將會(huì)使用DataLinkLayer作為數(shù)據(jù)鏈路層實(shí)現(xiàn)數(shù)據(jù)包的發(fā)送和接收。當(dāng)它接收到數(shù)據(jù)包后,會(huì)把它提交給我們自己實(shí)現(xiàn)的相關(guān)協(xié)議,在協(xié)議里,我們自己安裝協(xié)議封包的流程解包,并根據(jù)協(xié)議棧把處理的數(shù)據(jù)包一層層往上傳。同理我們自己實(shí)現(xiàn)的協(xié)議在把數(shù)據(jù)進(jìn)行封包后,也會(huì)一層層往下傳,最后傳到現(xiàn)在實(shí)現(xiàn)的DataLinkLayer層,讓它把數(shù)據(jù)發(fā)生出去,下一節(jié)我們將實(shí)現(xiàn)ARP協(xié)議層,到時(shí)候可以看到我們是如何實(shí)現(xiàn)數(shù)據(jù)封包及發(fā)生的

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 個(gè)人認(rèn)為,Goodboy1881先生的TCP /IP 協(xié)議詳解學(xué)習(xí)博客系列博客是一部非常精彩的學(xué)習(xí)筆記,這雖然只是...
    貳零壹柒_fc10閱讀 5,194評(píng)論 0 8
  • 1.這篇文章不是本人原創(chuàng)的,只是個(gè)人為了對(duì)這部分知識(shí)做一個(gè)整理和系統(tǒng)的輸出而編輯成的,在此鄭重地向本文所引用文章的...
    SOMCENT閱讀 13,362評(píng)論 6 174
  • 簡(jiǎn)介 用簡(jiǎn)單的話來(lái)定義tcpdump,就是:dump the traffic on a network,根據(jù)使用者...
    保川閱讀 6,078評(píng)論 1 13
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 32,302評(píng)論 2 89
  • 很不巧,今天的昆明一直在下雨,大雨,中雨,小雨,一直不斷。 想到第一年在昆明上學(xué)時(shí),到了雨季,天天毛毛雨,下了一個(gè)...
    我若盛開清風(fēng)自來(lái)閱讀 199評(píng)論 0 1

友情鏈接更多精彩內(nèi)容