我們上一節(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è)黑盒子如何使用。

我們本節(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)行起代碼,得到如下情況:

由于我們此時(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命令,如下:

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

后面我們將會(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ā)生的