經(jīng)過前兩節(jié)的準備,我們完成了數(shù)據(jù)鏈路層,已經(jīng)具備了數(shù)據(jù)包接收和發(fā)送的基礎(chǔ)設(shè)施,本機我們在此基礎(chǔ)上實現(xiàn)上層協(xié)議,我們首先從實現(xiàn)ARP協(xié)議開始。先簡單認識一下ARP協(xié)議,ARP是一種尋址協(xié)議,它要找尋目標的物理地址,連接在互聯(lián)網(wǎng)上的設(shè)備有兩種地址,一種叫IP,也就是我們常見的192.168.2.1這類地址,另一種叫物理地址,例如我們電腦上的mac地址。
為何要使用兩種地址呢?這類似與個人的名字與身份證號的區(qū)別,名字是可以重復(fù)的,例如叫”張三“的肯定不止一個人,當(dāng)一件包裹寄到一棟樓里如果樓內(nèi)有多個人叫張三,那么此時就需要身份證號來辨別哪個張三才是包裹的收件人。在網(wǎng)絡(luò)上傳輸數(shù)據(jù)時,ip對應(yīng)一個區(qū)域網(wǎng),該區(qū)域網(wǎng)內(nèi)有多臺連接設(shè)備,當(dāng)數(shù)據(jù)包根據(jù)ip找到對應(yīng)區(qū)域網(wǎng)后,如何知道區(qū)域網(wǎng)上的哪臺設(shè)備是數(shù)據(jù)包的接收者呢?這時就得看硬件地址,只有與數(shù)據(jù)包附帶的硬件地址相符合的設(shè)備才應(yīng)該接收到來的數(shù)據(jù)包,如此一來數(shù)據(jù)包要正確發(fā)生并接收,那就得知道兩個地址,一個是ip,一個是硬件地址,對應(yīng)互聯(lián)網(wǎng)設(shè)備而言,通常就是mac地址,而ARP協(xié)議就是專門用來獲得接收對象的mac地址的。
網(wǎng)絡(luò)協(xié)議的本質(zhì)其實就是填表單。ARP協(xié)議的實現(xiàn)也是填寫一系列表單,發(fā)給對方,對方根據(jù)表單要求也填寫一張表單發(fā)回來,我們看看這張表單的結(jié)構(gòu):
這張表上頭的0-32單位是比特位而不是字節(jié),要注意。根據(jù)表單所示,前16個比特位也就是前2個字節(jié)對于的是硬件類型,也就是傳送數(shù)據(jù)的網(wǎng)絡(luò),其取值情況如下:
1:10MB以太網(wǎng);6:IEEE802網(wǎng)絡(luò);7:ARCNET;16:ATM...
它還有其他取值,為了簡便我沒有羅列出來,由于我們默認在互聯(lián)網(wǎng)上收發(fā)數(shù)據(jù),因此填表時這兩個字節(jié)寫死為1。
接下兩字節(jié)也就是protocoal type,表示數(shù)據(jù)傳輸使用的網(wǎng)絡(luò)協(xié)議,如果數(shù)據(jù)包使用IP定位接收目標所在的局域網(wǎng),那么該值寫死為0x0800,我們實現(xiàn)的協(xié)議也是把這兩個字節(jié)寫死。
接下來是兩個單字節(jié)用于表示兩種地址的長度,我們默認收發(fā)數(shù)據(jù)包的設(shè)備都是mac地址,因此Hardware Adress Length這個字節(jié)寫死為6,同理我們默認設(shè)備都使用IP地址,因此protocal Address Length這個字節(jié)寫死為4.
接著兩字節(jié)是OpCode,用來表示消息目的。1表示請求,當(dāng)A向B發(fā)出ARP請求希望獲得B的mac地址時,A構(gòu)造這張表單時在該字節(jié)填寫1。2表示回應(yīng),當(dāng)B收到請求,向A返回同樣格式的表單,此時它在該字節(jié)填寫2,同時把自己的硬件地址填寫在表單里。
接下來是Sender Hardware Address,它用來存儲發(fā)送者的硬件地址,其長度與Hardware Address Length中表示的一致,在我們實現(xiàn)中,它用來存儲發(fā)生者的mac地址,因此占據(jù)6個字節(jié)。
接著的Sender Protocol Address表示發(fā)送者的IP地址,因此占據(jù)4字節(jié)。
Target Hardware Address是接收者的mac地址,占據(jù)6字節(jié),最后是接收者的IP地址,占據(jù)4字節(jié)。
當(dāng)表單填好后,數(shù)據(jù)鏈路層在發(fā)送出去前還會再加上一個包頭,包頭有14個字節(jié),前6個字節(jié)表示接收者的mac地址,接著6個字節(jié)表示發(fā)送者的mac地址,然后有2字節(jié)表示包的類型,如果發(fā)送的是ARP包,那么這2字節(jié)的值為0x0806,如果發(fā)送的是IP包,那么值為0x0800,當(dāng)網(wǎng)卡接收到數(shù)據(jù)包后,它會檢測這兩個字節(jié),根據(jù)數(shù)值把前14字節(jié)的數(shù)據(jù)鏈路包頭去除后,將剩下的數(shù)據(jù)提交給對應(yīng)的網(wǎng)絡(luò)協(xié)議層,因此ARP包經(jīng)過鏈路層封裝后發(fā)送時格式如下:
接下來我們看看代碼實現(xiàn),首先我們需要對上節(jié)模擬的數(shù)據(jù)鏈路層做一些修改:
package datalinklayer;
import jpcap.NetworkInterfaceAddress;
import jpcap.packet.EthernetPacket;
import jpcap.packet.Packet;
import utils.IMacReceiver;
import utils.PacketProvider;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import ARPProtocolLayer.ARPProtocolLayer;
import jpcap.JpcapCaptor;
import jpcap.JpcapSender;
import jpcap.NetworkInterface;
public class DataLinkLayer extends PacketProvider implements jpcap.PacketReceiver, IMacReceiver{
//change 1
private static DataLinkLayer instance = null;
private NetworkInterface device = null;
private Inet4Address ipAddress = null;
private byte[] macAddress = null;
JpcapSender sender = null;
private DataLinkLayer() {
}
public static DataLinkLayer getInstance() {
if (instance == null) {
instance = new DataLinkLayer();
}
return instance;
}
// change 2
public void initWithOpenDevice(NetworkInterface device) {
this.device = device;
this.ipAddress = this.getDeviceIpAddress();
this.macAddress = new byte[6];
this.getDeviceMacAddress();
JpcapCaptor captor = null;
try {
captor = JpcapCaptor.openDevice(device,2000,false,3000);
} catch (IOException e) {
e.printStackTrace();
}
this.sender = captor.getJpcapSenderInstance();
//測試arp協(xié)議
this.testARPProtocol();
}
private Inet4Address getDeviceIpAddress() {
for (NetworkInterfaceAddress addr : this.device.addresses) {
//網(wǎng)卡網(wǎng)址符合ipv4規(guī)范才是可用網(wǎng)卡
if (!(addr.address instanceof Inet4Address)) {
continue;
}
return (Inet4Address) addr.address;
}
return null;
}
private void getDeviceMacAddress() {
int count = 0;
for (byte b : this.device.mac_address) {
this.macAddress[count] = (byte) (b & 0xff);
count++;
}
}
// change 3
public byte[] deviceIPAddress() {
return this.ipAddress.getAddress();
}
public byte[] deviceMacAddress() {
return this.macAddress;
}
@Override
public void receivePacket(Packet packet) {
//將受到的數(shù)據(jù)包推送給上層協(xié)議
this.pushPacketToReceivers(packet);
}
public void sendData(byte[] data, byte[] dstMacAddress, short frameType) {
/*
* 給上層協(xié)議要發(fā)送的數(shù)據(jù)添加數(shù)據(jù)鏈路層包頭,然后使用網(wǎng)卡發(fā)送出去
*/
if (data == null) {
return;
}
Packet packet = new Packet();
packet.data = data;
/*
* 數(shù)據(jù)鏈路層會給發(fā)送數(shù)據(jù)添加包頭:
* 0-5字節(jié):接受者的mac地址
* 6-11字節(jié): 發(fā)送者mac地址
* 12-13字節(jié):數(shù)據(jù)包發(fā)送類型,0x0806表示ARP包,0x0800表示ip包,
*/
EthernetPacket ether=new EthernetPacket();
ether.frametype = EthernetPacket.ETHERTYPE_ARP;
ether.src_mac= this.device.mac_address;
ether.dst_mac= dstMacAddress;
packet.datalink = ether;
sender.sendPacket(packet);
}
private void testARPProtocol() {
ARPProtocolLayer arpLayer = new ARPProtocolLayer();
this.registerPacketReceiver(arpLayer);
byte[] ip;
try {
ip = InetAddress.getByName("192.168.2.1").getAddress();
arpLayer.getMacByIP(ip, this);
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void receiveMacAddress(byte[] ip, byte[] mac) {
System.out.println("receive arp reply msg with sender ip: ");
for (byte b: ip) {
System.out.print(Integer.toUnsignedString(b & 0xff) + ".");
}
System.out.println("with sender mac :");
for (byte b : mac)
System.out.print(Integer.toHexString(b&0xff) + ":");
}
}
首先它改成單子模式,一次只生成一個實力。它通過jpcap獲得網(wǎng)卡對象,然后得到本機mac地址和ip地址,同時導(dǎo)出一個接口叫sendData,該接口從上層接收要發(fā)送的數(shù)據(jù),然后封裝一個數(shù)據(jù)鏈路層包頭后,調(diào)用網(wǎng)卡將數(shù)據(jù)發(fā)送出去。它繼承PacketProvider類,后者是一個觀察者模式的實現(xiàn),所有想獲得網(wǎng)絡(luò)數(shù)據(jù)包的對象都通過PacketProvider注冊,一旦網(wǎng)卡收到數(shù)據(jù)后,PacketProvider就會把數(shù)據(jù)包推送給所有觀察者,后者實現(xiàn)如下:
package utils;
public interface IPacketProvider {
public void registerPacketReceiver(jpcap.PacketReceiver receiver);
}
package utils;
import java.util.ArrayList;
import jpcap.PacketReceiver;
import jpcap.packet.Packet;
public class PacketProvider implements IPacketProvider{
private ArrayList<PacketReceiver> receiverList = new ArrayList<PacketReceiver>();
@Override
public void registerPacketReceiver(PacketReceiver receiver) {
if (this.receiverList.contains(receiver) != true) {
this.receiverList.add(receiver);
}
}
@SuppressWarnings("unused")
protected void pushPacketToReceivers(Packet packet) {
for (int i = 0; i < this.receiverList.size(); i++) {
PacketReceiver receiver = (PacketReceiver) this.receiverList.get(i);
receiver.receivePacket(packet);
}
}
}
接著我們實現(xiàn)ARP協(xié)議層。我們在實現(xiàn)ARP協(xié)議時,除了按規(guī)定填表和讀表外,我們還需要做的工作是提供緩存機制。由于發(fā)送數(shù)據(jù)包再等待回應(yīng)是一種非常耗時的工作,因此完成后要把結(jié)果緩存起來,下次需要時不用再進行耗時的數(shù)據(jù)收發(fā)工作,因此我們在實現(xiàn)時會準備一個映射表,將ip和mac地址緩存起來,當(dāng)查找指定ip設(shè)備的mac地址時,現(xiàn)在表中查找,如果找不到在進行數(shù)據(jù)包的發(fā)送接收,相關(guān)的代碼實現(xiàn)如下:
package ARPProtocolLayer;
import java.net.Inet4Address;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import datalinklayer.DataLinkLayer;
import jpcap.PacketReceiver;
import jpcap.packet.ARPPacket;
import jpcap.packet.EthernetPacket;
import jpcap.packet.Packet;
import utils.IMacReceiver;
public class ARPProtocolLayer implements PacketReceiver {
/*
*/
private HashMap<byte[], byte[]> ipToMacTable = new HashMap<byte[], byte[]>();
private HashMap<Integer, ArrayList<IMacReceiver>> ipToMacReceiverTable = new HashMap<Integer, ArrayList<IMacReceiver>>();
/*
* 數(shù)據(jù)包含數(shù)據(jù)鏈路層包頭:dest_mac(6byte) + source_mac(6byte) + frame_type(2byte)
* 因此讀取ARP數(shù)據(jù)時需要跳過開頭14字節(jié)
*/
private static int ARP_OPCODE_START = 20;
private static int ARP_SENDER_MAC_START = 22;
private static int ARP_SENDER_IP_START = 28;
private static int ARP_TARGET_IP_START = 38;
@Override
public void receivePacket(Packet packet) {
if (packet == null) {
return;
}
//確保收到數(shù)據(jù)包是arp類型
EthernetPacket etherHeader = (EthernetPacket)packet.datalink;
/*
* 數(shù)據(jù)鏈路層在發(fā)送數(shù)據(jù)包時會添加一個802.3的以太網(wǎng)包頭,格式如下
* 0-7字節(jié):[0-6]Preamble , [7]start fo frame delimiter
* 8-22字節(jié): [8-13] destination mac, [14-19]: source mac
* 20-21字節(jié): type
* type == 0x0806表示數(shù)據(jù)包是arp包, 0x0800表示IP包,0x8035是RARP包
*/
if (etherHeader.frametype != EthernetPacket.ETHERTYPE_ARP) {
return;
}
byte[] header = packet.header;
analyzeARPMessage(header);
}
private boolean analyzeARPMessage(byte[] data) {
/*
* 解析獲得的APR消息包,從中獲得各項信息,此處默認返回的mac地址長度都是6
*/
//先讀取2,3字節(jié),獲取消息操作碼,確定它是ARP回復(fù)信息
byte[] opcode = new byte[2];
System.arraycopy(data, ARP_OPCODE_START, opcode, 0, 2);
//轉(zhuǎn)換為小端字節(jié)序
short op = ByteBuffer.wrap(opcode).getShort();
if (op != ARPPacket.ARP_REPLY) {
return false;
}
//獲取接受者ip,確定該數(shù)據(jù)包是回復(fù)給我們的
byte[] ip = DataLinkLayer.getInstance().deviceIPAddress();
for (int i = 0; i < 4; i++) {
if (ip[i] != data[ARP_TARGET_IP_START + i]) {
return false;
}
}
//獲取發(fā)送者IP
byte[] senderIP = new byte[4];
System.arraycopy(data, ARP_SENDER_IP_START, senderIP, 0, 4);
//獲取發(fā)送者mac地址
byte[] senderMac = new byte[6];
System.arraycopy(data, ARP_SENDER_MAC_START, senderMac, 0, 6);
//更新arp緩存表
ipToMacTable.put(senderIP, senderMac);
//通知接收者mac地址
int ipToInteger = ByteBuffer.wrap(senderIP).getInt();
ArrayList<IMacReceiver> receiverList = ipToMacReceiverTable.get(ipToInteger);
if (receiverList != null) {
for (IMacReceiver receiver : receiverList) {
receiver.receiveMacAddress(senderIP, senderMac);
}
}
return true;
}
public void getMacByIP(byte[] ip, IMacReceiver receiver) {
if (receiver == null) {
return;
}
//查看給的ip的mac是否已經(jīng)緩存
int ipToInt = ByteBuffer.wrap(ip).getInt();
if (ipToMacTable.get(ipToInt) != null) {
receiver.receiveMacAddress(ip, ipToMacTable.get(ipToInt));
}
if (ipToMacReceiverTable.get(ipToInt) == null) {
ipToMacReceiverTable.put(ipToInt, new ArrayList<IMacReceiver>());
//發(fā)送ARP請求包
sendARPRequestMsg(ip);
}
ArrayList<IMacReceiver> receiverList = ipToMacReceiverTable.get(ipToInt);
if (receiverList.contains(receiver) != true) {
receiverList.add(receiver);
}
return;
}
private void sendARPRequestMsg(byte[] ip) {
if (ip == null) {
return;
}
DataLinkLayer dataLinkInstance = DataLinkLayer.getInstance();
byte[] broadcast=new byte[]{(byte)255,(byte)255,(byte)255,(byte)255,(byte)255,(byte)255};
int pointer = 0;
byte[] data = new byte[28];
data[pointer] = 0;
pointer++;
data[pointer] = 1;
pointer++;
//注意將字節(jié)序轉(zhuǎn)換為大端
ByteBuffer buffer = ByteBuffer.allocate(2);
buffer.order(ByteOrder.BIG_ENDIAN);
buffer.putShort(ARPPacket.PROTOTYPE_IP);
for (int i = 0; i < buffer.array().length; i++) {
data[pointer] = buffer.array()[i];
pointer++;
}
data[pointer] = 6;
pointer++;
data[pointer] = 4;
pointer++;
//注意將字節(jié)序轉(zhuǎn)換為大端
buffer = ByteBuffer.allocate(2);
buffer.order(ByteOrder.BIG_ENDIAN);
buffer.putShort(ARPPacket.ARP_REQUEST);
for (int i = 0; i < buffer.array().length; i++) {
data[pointer] = buffer.array()[i];
pointer++;
}
byte[] macAddress = dataLinkInstance.deviceMacAddress();
for (int i = 0; i < macAddress.length; i++) {
data[pointer] = macAddress[i];
pointer++;
}
byte[] srcip = dataLinkInstance.deviceIPAddress();
for (int i = 0; i < srcip.length; i++) {
data[pointer] = srcip[i];
pointer++;
}
for (int i = 0; i < broadcast.length; i++) {
data[pointer] = broadcast[i];
pointer++;
}
for (int i = 0; i < ip.length; i++) {
data[pointer] = ip[i];
pointer++;
}
dataLinkInstance.sendData(data, broadcast, EthernetPacket.ETHERTYPE_ARP);
}
}
getMacByIP是它提供給上層協(xié)議的接口,當(dāng)上層協(xié)議需要獲得指定ip設(shè)備的mac地址時就調(diào)用該接口,它先從緩存表中看指定ip對應(yīng)的mac地址是否存在,如果不存在就調(diào)用sendARPRequestMsg發(fā)送ARP請求包。
sendARPRequestMsg的實現(xiàn)其實就是按照我們前面描述的填表規(guī)則進行填表。值得注意的是,我們把接收者的mac地址設(shè)置成[0xff, 0xff, 0xff, 0xff, 0xff, 0xff],這是一個廣播硬件地址,于是所有設(shè)備都可以讀取這個消息,如果接收設(shè)備的IP與數(shù)據(jù)包里對應(yīng)的target ip相同,那么它就應(yīng)該構(gòu)造同一個表,把自己的硬件地址存儲在表中,返回給消息的發(fā)起者。
ARPProtocolLayer繼承了PacketReceiver接口,這意味著它希望鏈路層收到數(shù)據(jù)包后,把數(shù)據(jù)包推送給它。如果接收者收到我們發(fā)出的ARP請求包后,構(gòu)造一個回復(fù)消息發(fā)送到我們網(wǎng)卡上,鏈路層就會調(diào)用ARPProtocolLayer的PacketReceiver接口來解讀數(shù)據(jù)包。數(shù)據(jù)就存儲在packet.head里面,我們調(diào)用analyzeARPMessage接口來讀取返回的ARP包。
在解析數(shù)據(jù)包時,我們注意packet.head對應(yīng)的內(nèi)容包含著鏈路層包頭,也就是前面講到的14字節(jié),因此我們要讀取相應(yīng)的字節(jié)時,在計算偏移時要跳過開始14字節(jié),在代碼里定義ARP_OPCODE_START這些常量時,注釋中提到這一點。在接收到數(shù)據(jù)包時,它先從鏈路層包頭確定該包是ARP包,然后再調(diào)用analyzeARPMessage解析包的內(nèi)容。在后者的實現(xiàn)中,我們先取出opcode兩字節(jié),看看它是否是2,也就是ARP回應(yīng)包,如果是那么再從target protocoal address對應(yīng)4字節(jié)里讀取數(shù)據(jù)包接收者的ip地址,如果該地址與我們的地址相同,那就能確定數(shù)據(jù)包是發(fā)給我們的,然后我們從sender hardware address中獲得發(fā)送者的mac地址。
ARPProtocolLayer要求所有通過它獲取mac地址的對象都必須實現(xiàn)IMacReceiver接口,有可能很多個上層協(xié)議對象都需要獲得同一個ip對應(yīng)設(shè)備的mac地址,它會把這些對象存儲在一個隊里中,一旦給定ip設(shè)備返回包含它mac地址的ARP消息后,ARPProtocolLayer從消息中解讀出mac地址,它就會把該地址推送給所有需要的接收者,IMacReceiver的定義如下:
package utils;
public interface IMacReceiver {
public void receiveMacAddress(byte[] ip, byte[] mac);
}
在我們的代碼中,DataLinkLayer就繼承了這個接口,它在初始化ARPProtocolLayer時把自己進行了注冊,病調(diào)用getMacByIP去獲取對應(yīng)設(shè)備的mac地址,代碼如下:
private void testARPProtocol() {
ARPProtocolLayer arpLayer = new ARPProtocolLayer();
this.registerPacketReceiver(arpLayer);
byte[] ip;
try {
ip = InetAddress.getByName("192.168.2.1").getAddress();
arpLayer.getMacByIP(ip, this);
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void receiveMacAddress(byte[] ip, byte[] mac) {
System.out.println("receive arp reply msg with sender ip: ");
for (byte b: ip) {
System.out.print(Integer.toUnsignedString(b & 0xff) + ".");
}
System.out.println("with sender mac :");
for (byte b : mac)
System.out.print(Integer.toHexString(b&0xff) + ":");
}
代碼中192.168.2.1對應(yīng)我家路由器ip,一旦DataLinkLayer接收到路由器回復(fù)的ARP數(shù)據(jù)包,從中解讀出mac地址后,就調(diào)用上面的receiveMacAddress,把mac地址推送過來。
上面代碼運行后,情況如下,我們用wireshark抓到了代碼發(fā)送的數(shù)據(jù)包和接收到路由器返回的ARP包:
第一行時我們代碼發(fā)出的數(shù)據(jù)包,第二行是路由器返回的數(shù)據(jù)包,我們點開第一行得到數(shù)據(jù)包內(nèi)容如下:
其中sender mac address是我機器的mac地址,sender ip address是我機器的ip,opcode值是1表示它是一個arp請求包,我們點開第二行,起內(nèi)容如下:
其中sender ip address正是路由器的ip,sender mac address 是路由器的mac地址,我們程序接收到這個數(shù)據(jù)包,并進行解讀后得到結(jié)果如下:
更多技術(shù)信息,包括操作系統(tǒng),編譯器,面試算法,機器學(xué)習(xí),人工智能,請關(guān)照我的公眾號: