从0到1用java再造tcpip协议栈:使用jpacap模拟数据链路层

我们上一节成功使用jpcap获得了网卡硬件,我们要重新构造tcp/ip协议栈,那么就需要做两部分工作。一部分由上层协议完成,他们的工作是将要发送的数据进行封装,主要是在数据包上添加包头数据结构,包头里有很多控制字节,用于不同节点间进行数据传送时对传送过程的控制和调整,了解,掌握,实现每层数据协议的包头结构以及数据控制流程是我们系列课程的重点和难点。

有了上层数据封装后,剩下的就需要下层硬件将数据准确的发送到指定目的地。负责将数据包转换为电信号传输到另一端的,就是数据链路层。我们无需了解它的实现原理,只要把它作为一个黑盒子,当上层数据经过各层协议封装好后,传入这个黑盒子,然后确保它能将信息正确的传送出去即可,本节我们看看这个黑盒子如何使用。

我们本节要模拟实现的就是上图所表示的network interface。上一节我们使用jpcap列举了机器当前具备的网卡,其中有很多是虚拟网卡,也就是它们不具备数据的接受和发送功能,因此我们要从中找到可以使用的真正硬件网卡,辨别网卡是否可用的一个标准是,看他是否具备ipv4的地址格式,下面代码就用于从jpcap列举的所有网卡中获取硬件网卡:

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) {
                //网卡网址符合ipv4规范才是可用网卡
                if (!(addr.address instanceof Inet4Address)) {
                    continue;
                }

               findDevice = true;
               break;
            }

            if (findDevice) {
                device = devices[i];
                break;
            }

        }

上面代码通过jpcap遍历当前所有网卡,然后看哪个网卡的ip地址符合ipv4格式,符合的就是可以用于发送和接收数据的硬件网卡。接下来我们看看如何从网卡上截取到来的数据包。

要通过jpcap从网卡获取数据,首先需要继承一个接口叫PacketReceiver,然后实现receivePacket接口。我们在工程下新建一个文件叫DataLinkLayer.java,其实现内容如下:

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

在上面代码中,当监听的网卡有数据包抵达时,jpcap会调用上面类所实现的receviePacket接口,将接收到的数据包传入。在代码中我们注意监控三种网络数据包,他们分别是tcp, icmp, 和udp,我们用三个布尔变量来控制是否打印相应包的信息,上面代码实现中,我们只打印icmp协议数据包。

icmp协议其实就是我们常用的ping命令,用来看看网络通不通。上面代码完成后,我们需要打开要监控的网卡对象,构建上面对象一个实例后,将它传入jpcap框架,以便接口被回调,然后获得数据包,回到项目的主入口,添加如下代码:

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

我们前面的代码已经找到可以收发数据包的网卡对象了,此时我们通过openDevice调用获得网卡硬件的使用权,然后构造DataLinkLayer实例,传入到loopPacket调用里,-1表示持续不停的监听对应网卡上的数据包,于是程序进入一个死循环,一旦网卡有数据包抵达时,DataLinkLayer实例的receivePacket函数就会被调用,同时数据包对象会被传入。我们先运行起代码,得到如下情况:

由于我们此时健康网卡上的ping数据,由于当前没有ping数据包出现在网卡上,所以我们的程序进入等待状况,此时打开控制台,执行一个ping命令,如下:

这是我们在看java程序控制台就会发现ping包的相关数据被打印出来:

后面我们将会使用DataLinkLayer作为数据链路层实现数据包的发送和接收。当它接收到数据包后,会把它提交给我们自己实现的相关协议,在协议里,我们自己安装协议封包的流程解包,并根据协议栈把处理的数据包一层层往上传。同理我们自己实现的协议在把数据进行封包后,也会一层层往下传,最后传到现在实现的DataLinkLayer层,让它把数据发生出去,下一节我们将实现ARP协议层,到时候可以看到我们是如何实现数据封包及发生的

原文发布于微信公众号 - Coding迪斯尼(gh_c9f933e7765d)

原文发表时间:2018-12-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码农阿宇

asp.net core轻松入门之MVC中Options读取配置文件

接上一篇中讲到利用Bind方法读取配置文件 ASP.NET Core轻松入门Bind读取配置文件到C#实例 那么在这篇文章中,我将在上一篇文章的基础上,利...

26240
来自专栏HappenLee的技术杂谈

Python读取大文件的"坑“与内存占用检测

随手搜索python读写文件的教程,很经常看到read()与readlines()这对函数。所以我们会常常看到如下代码:

28520
来自专栏微服务生态

深入理解并发之CompareAndSet(CAS)

java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁,使用这些类在多核CPU的机器上会有比较好的性能.

10620
来自专栏蘑菇先生的技术笔记

探索c#之storm的TimeCacheMap

22770
来自专栏黑泽君的专栏

Java程序的运行原理及JVM的启动是多线程的吗?

A:Java程序的运行原理     Java通过java命令会启动java虚拟机。启动JVM,等于启动了一个应用程序,也就是启动了一个进程。   ...

37820
来自专栏同步博客

浅谈PHP异常处理

  PHP中的异常的独特性,即PHP中的异常不同于主流语言C++、java中的异常。在Java中,异常是唯一的错误报告方式,而在PHP中却不是这样,而是把所有不...

14230
来自专栏决胜机器学习

设计模式专题(十九) ——命令模式

设计模式专题(十九)——命令模式 (原创内容,转载请注明来源,谢谢) 一、概念 命令模式(Command)将一个请求封装为一个对象,从而可用不同的请求对客户进...

363150
来自专栏Android开发指南

Android热修复AndFix

40180
来自专栏张善友的专栏

Contact Manager Web API 示例[2] Web API Routing

联系人管理器web API是一个Asp.net web api示例程序,演示了通过ASP.NET Web API 公开联系信息,并允许您添加和删除联系人,示例地...

20360
来自专栏python3

习题16:读写文件

小技巧就是可以让你的脚本一部分一部分地运行起来,也方便排查错误,以此类推,直到整个脚本运行起来为止

8810

扫码关注云+社区

领取腾讯云代金券