我们上一节成功使用jpcap获得了网卡硬件,我们要重新构造tcp/ip协议栈,那么就需要做两部分工作。一部分由上层协议完成,他们的工作是将要发送的数据进行封装,主要是在数据包上添加包头数据结构,包头里有很多控制字节,用于不同节点间进行数据传送时对传送过程的控制和调整,了解,掌握,实现每层数据协议的包头结构以及数据控制流程是我们系列课程的重点和难点。
有了上层数据封装后,剩下的就需要下层硬件将数据准确的发送到指定目的地。负责将数据包转换为电信号传输到另一端的,就是数据链路层。我们无需了解它的实现原理,只要把它作为一个黑盒子,当上层数据经过各层协议封装好后,传入这个黑盒子,然后确保它能将信息正确的传送出去即可,本节我们看看这个黑盒子如何使用。
屏幕快照 2018-11-27 下午5.38.41.png我们本节要模拟实现的就是上图所表示的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函数就会被调用,同时数据包对象会被传入。我们先运行起代码,得到如下情况:
屏幕快照 2018-12-04 下午5.04.18.png由于我们此时健康网卡上的ping数据,由于当前没有ping数据包出现在网卡上,所以我们的程序进入等待状况,此时打开控制台,执行一个ping命令,如下:
屏幕快照 2018-12-04 下午5.06.09.png这是我们在看java程序控制台就会发现ping包的相关数据被打印出来:
屏幕快照 2018-12-04 下午5.07.18.png后面我们将会使用DataLinkLayer作为数据链路层实现数据包的发送和接收。当它接收到数据包后,会把它提交给我们自己实现的相关协议,在协议里,我们自己安装协议封包的流程解包,并根据协议栈把处理的数据包一层层往上传。同理我们自己实现的协议在把数据进行封包后,也会一层层往下传,最后传到现在实现的DataLinkLayer层,让它把数据发生出去,下一节我们将实现ARP协议层,到时候可以看到我们是如何实现数据封包及发生的
网友评论