Linux底层网络编程--ARP,PING等
一linux系統中獲取網卡信息
??????? 獲取網卡信息有兩種方法。一種是讀取系統文件。另外一種是通過系統API進行獲取。
????????? 1、? 讀取系統文件
??????? 程序中通過讀取/proc/net/dev文件即可以讀取到系統中的所有網卡信息。該文件的內容
大致如下:
???????? ?容易發現左邊紅的方框中的就是主機的所有網絡接口。然后讀取該文件,解析出這些接口就可以了。
????????? 2、? 通過系統API進行獲取
??????? 主要是使用兩個結構struct ifconf 和 struct ifreq。Ifconf結構主要有兩個成員,一個是用來表示長度的,還有一個是指向struct ifreq的指針。通過宏ifc_buf和ifc_req來分別訪問。具體步驟如下。首先創建一個socket套接字(SOCK_DGRAME類型),然后再通過ioctl(sock_fd, SIOCGCONF, &ifconf);這樣就獲得了所有的網卡信息。然后再通過操作irconf,把ifc_req指向的ifreq數組中的網卡信息取出來就可以了。
?
二、獲取IP地址、MAC地址等
??????? 這個方法和上面的API類似。使用ioctl(sock_fd, SIOCGADDR, &ifreq);來獲取ip的信息,該ip信息保存在ifreq結構中。使用ioctl(sock_fd, SIOCGHWADDR, &ifreq);來獲取硬件MAC地址信息。
??????? 使用舉例:
??????? /*使用API接口獲取指定網卡的物理地址*/
void GetEthMAC(uint8 * eth_name , uint8 *mac)
{
????? struct ifreq eth_if;
????? int sock_fd;
????? strncpy(eth_if.ifr_name,(char *)eth_name,sizeof(eth_if.ifr_name));
????? sock_fd=socket(AF_INET,SOCK_DGRAM,0);
????? ioctl(sock_fd,SIOCGIFHWADDR,ð_if);
????? int i;
????? for(i=0;i<6;i++)
????? {
?????????? ?mac[i]=eth_if.ifr_hwaddr.sa_data[i];
????? }
}
/*使用API接口獲取指定網卡的IP地址*/
void GetEthIP(uint8 * eth_name,uint8 *ip_addr)
{
????? struct ifreq eth_if;
????? int sock_fd;
????? strncpy(eth_if.ifr_name,(char *)eth_name,sizeof(eth_if.ifr_name));
????? sock_fd=socket(AF_INET,SOCK_DGRAM,0);
????? ioctl(sock_fd,SIOCGIFADDR,ð_if);
????? int i;
????? for(i=2;i<6;i++)
????? {
?????????? ip_addr[i-2]=eth_if.ifr_addr.sa_data[i];
????? }
}
?
三、獲取網關的地址?
??????? 通過讀取系統文件可以獲得網卡信息,難點在于對文件的處理。通過/proc/net/route文件即可以獲取網關的地址。該文件的內容如下:
?
???????? 紅色方框中的即為網關地址,但這是一個整形的網關地址。
???????? 使用舉例:
/*proc方法獲取網關地址*/
void GetGateWayIP(uint8 *ip_addr)
{
????? char inf[100];
????? FILE *file_fd;
????? uint8 high=0,low=0,value;
????? int i;
????? file_fd = fopen("/proc/net/route","r");
????? if(file_fd==NULL)
????? {
??????????? printf("can not open /proc/net/route\n");
????? }
????? else
???? {
???????????? while(!feof(file_fd))
???????????? {
???????????????????? memset(inf,0,sizeof(inf));
????????? ?????????? fgets(inf,100,file_fd);
???????? ??????????? if(inf[5]=='0'&&inf[6]=='0'&&inf[7]=='0'&&inf[8]=='0'&&inf[9]=='0'&&inf[10]=='0'&&inf[11]=='0'&&inf[12]=='0')
???????????????????? {
???????????????????????????? ?for(i=20;i>=14;i-=2)
????????????????????????????? {
??????????????????????????????????????? ?if(inf[i]>=65)
??????????????????????????????????????? ???????? high = inf[i]-55;
???????????????????????????????????????? else
???????????????????? ?????? ???????????????????? high = inf[i]-48;
???????????????????????????????????????? if(inf[i+1]>=65)
?????????????????????????????????????????????? ? low = inf[i+1]-55;
??????????????????????????????????????? ?else
??????????????????????????????????????????????? ?low = inf[i+1]-48;
???????????????????? ?????????????????? value = high*16+low;
????????????????????????????????????? ?ip_addr[10-i/2] = value;
??????????????????????????????}
??????????????? ????????????? break;
?????????????????????? }
????? ??????? }
???? ?}
}
四、底層網絡編程
??????? 這里的底層是相對于TCP/IP系統的應用層來說的。應用層的網絡編程可以不用關注底層的實現細節,這些對于編程者來說都是透明的。而底層的編程主要是和TCP/IP協議緊密關聯的。比如設置IP頭信息,設置TCP、UDP頭信息,設置ICMP數據包,ARP地址解析協議等等。這一切能夠實現的基礎,就是SOCK_RAW——原始套接字。原始套接字的使用對于學習和掌握TCP/IP協議時非常有幫助的。總的來說,有兩種用法:
socket(AF_INET, SOCK_RAW, IPPROTO_TCP);? ??? // IPPROTO_UDP;? IPPOTO_ICMP等
socket(AF_PACKET, SOCK_RAW, ETH_P_ALL);?? ? // ETH_P_ARP;? ETH_P_RARP等
這里的第一類用法主要用在組裝傳輸層的數據包上使用。比如組裝一個ICMP數據包,用來實現ping程序。這一層次的調用不關心ip數據頭的信息,只關心ip頭以上的信息。至于ip數據頭以及以太網數據頭的信息由系統來組裝。使用sockadr_in結構來指明組裝的ip頭。但是注意此時的sockaddr_in結構中的端口號是不會起作用的。另外還需要非常注意的是這種方式收取到的數據是沒有被剝離ip頭部的。必須手動進行剝離,才能讀取到需要的數據。
???????? 使用這種方法雖然由系統自己組裝了IP頭,然而還是可以通過一定的方法來強制的設置IP頭的。該方法就是setsockopt方法。例如下面所列舉的方法幾乎可以設置任何的IP頭部數據:
setsockopt (socket_fd, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl));?? //設置TTL
setsockopt (socket_fd, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(tos)); //設置TOS服務
setsockopt (socket_fd, IPPROTO_IP, IP_OPTIONS, ***, ***);?? ?????//設置IP選項
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&bflag, sizeof(bflag); //設置IP標志位
所以,總的來說,使用第一種原始套接字的方法,除了無法完成數據鏈路層的編程控制之外,幾乎可以做任何IP層及以上的事情,所以這種套接字的作用是十分強大的。
???????? 第二種套接字方法,對于發送的每一個數據包,需要從頭到尾由程序員進行包裝。包括以太網數據頭。使用一個叫做sockaddr_ll(數據鏈路層通用頭結構)的結構頭來發送數據。該結構體需要指明協議名字以及使用哪個index來發送即可。如下所示填充該數據結構:
???????? Sockaddr_ll eth;
???????? eth.sll_family = PF_PACKET;
???????? eth.sll_ifindex = if_nametoindex(“eth0”);
用這種方法實現ARP地址解析是一個非常不錯的選擇。但是很多時候我們不需要自己動手組裝一個以太網數據包,因為這是一件很困難的事,所以這種方法一般用來發送ARP或者RARP數據包以及用來偵聽網卡信息,監控所有發送至本機的數據包之用。
?
五、linux網絡底層編程實例
1)? ARP地址解析協議發送接收程序
/*
*?? 作者:newstar
*?? 用于簡單的實現arp地址解析協議
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <linux/if.h>
#include <sys/ioctl.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
//獲取硬件網卡的相應信息
void GetEthInfor(char *name , char *MAC_addr , struct in_addr * IP_addr);
//arp包的結構定義
struct ARP_PACKET
{
?????? //以太網首部
?unsigned char dest_mac[6]; //6字節
?unsigned char sorce_mac[6];//6字節
?unsigned short type;?????? //2字節
? //arp——內容
?unsigned short hw_type;?? //2字節:硬件地址類型???? 0x0001 表示mac地址
?unsigned short pro_type;? //2字節:軟件地址類型??? 0x0806 表示IPV4地址
?unsigned char hw_len;???? //1字節:硬件地址長度??
?unsigned char pro_len;??? //1字節:軟件地址長度
?unsigned short op;??????? //2字節:操作類型?????????? 0x0001表示ARP請求;0x0002表示ARP應答
?unsigned char from_mac[6];//6字節
?unsigned char from_ip[4]; //4字節
?unsigned char to_mac[6];? //6字節
?unsigned char to_ip[4];?? //4字節
?unsigned char padding[18];//18字節:填充字節,因為以太網數據最少要46字節
};
//主函數
int main()
{
?int i = 0;
?int fd = 0;
?int num=0;
?unsigned char MAC_ADDR[6];
?struct in_addr IP_ADDR;
?struct ARP_PACKET arp_pk={0};
?struct sockaddr_ll eth_info;
?
??????
?
?//第一步:獲取指定網卡的信息(MAC地址和IP地址)
?GetEthInfor("eth0",MAC_ADDR,&IP_ADDR);
?? /*printf("The MAC_addr is:");
?for(i =0 ;i<6;i++)
??? printf("%4X",MAC_ADDR[i]);?
?printf("\n");
??? printf("the IP is:%s\n",inet_ntoa(IP_ADDR));*/
??????
??? //第二步:填充ARP數據包的內容
?for(i=0;i<6;i++)???????????????? //填充以太網首部的目的mac地址
?{
??arp_pk.dest_mac[i]=0XFF;??????
?}
??
?for(i=0;i<6;i++)???????????????? //填充以太網首部的源mac地址
?{
??arp_pk.sorce_mac[i]=MAC_ADDR[i];
?}
?? arp_pk.type = htons(0x0806);??? //填充以太網首部的偵類型
?? arp_pk.hw_type = htons(0x0001); //填充硬件地址類型:0x0001表示的是MAC地址
?? arp_pk.pro_type = htons(0x0800);//填充協議地址類型:0x0800表示的是IP地址
?? arp_pk.hw_len = 6;????????????? //填充硬件地址長度
?? arp_pk.pro_len = 4;???????????? //填充協議地址長度
?? arp_pk.op = htons(0x0001);????? //填充操作類型:0x0001表示ARP請求
?? for(i=0;i<6;i++)???????????????? //填充源mac地址
?? {
??arp_pk.from_mac[i]=MAC_ADDR[i];
?? }
?? for(i=0;i<4;i++)???????????????? //填充源IP地址
?? {
??arp_pk.from_ip[i]=(inet_ntoa(IP_ADDR))[i];
?? }
?? for(i=0;i<6;i++)???????????????? //填充欲獲取的目的mac地址
?? {
??? arp_pk.to_mac[i]=0X00;
?? }
??
?? arp_pk.to_ip[0]=0X0A;??????? //填充想要裝換為MAC地址的IP地址。可以使用命令行參數來做
?? arp_pk.to_ip[1]=0X40;
?? arp_pk.to_ip[2]=0X39;
?? arp_pk.to_ip[3]=0X0A;
?
?//第三步:填充sockaddr_ll eth_info結構
??? eth_info.sll_family = PF_PACKET;
?eth_info.sll_ifindex = if_nametoindex("eth0");
?//printf("number is:%d\n",eth_info.sll_family);
?
?//第四步:創建原始套接字
?fd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));? //疑問:為什么這里需要htons函數進行轉換
?if(fd<0)
?{
??printf("socket SOCK_RAW failed!\n");
??exit(1);
?}
?
?//第五步:發送ARP數據包
?num = sendto(fd , &arp_pk , sizeof(struct ARP_PACKET) , 0 ,(struct sockaddr*)(ð_info),sizeof(eth_info));
?if(num<0)
?{
??printf("sendto failed!\n");
??exit(1);
?}
??????? //第六步:接受ARP應答
?num = recvfrom(fd , &arp_pk , sizeof(struct ARP_PACKET) ,0,NULL,0);
?if(num<0)
?{
??printf("rcvfrom failed!\n");
??exit(1);
?}
?else
?{
??printf("I receive %d bytes!\n",num);
??printf("the mac? is:");
??for(i=0;i<6;i++)
??{
???printf("%4X ",arp_pk.from_mac[i]);
??}
??printf("\n");
?}
?close(fd);
?return 0;
}
void GetEthInfor(char *name ,? char *MAC_addr , struct in_addr * IP_addr)
{
?struct ifreq? eth;? //夠結構用于存放最初多獲取的接口信息
?? int fd;???????????? //用于創建套接字
?int temp=0;???????? //用于驗證接口調用
?int i=0;??????????? //用于循環
?
?strncpy(eth.ifr_name,name,sizeof(struct ifreq)-1);
?fd = socket(AF_INET,SOCK_DGRAM,0);
?if(fd<0)
?{
???printf("socket failed!\n");
???exit(1);
?}
??????? //獲取并且保存和打印指定的物理接口MAC地址信息
?temp = ioctl(fd,SIOCGIFHWADDR,ð);
?if(temp<0)
?{
??printf("ioctl--get hardware addr failed!\n");
??exit(1);
??????? }
?strncpy(MAC_addr,eth.ifr_hwaddr.sa_data,6);
?/*printf("The MAC_addr is:");
?for(i =0 ;i<6;i++)
??printf("%4X",(unsigned char)eth.ifr_hwaddr.sa_data[i]);?
?printf("\n");*/
???????
??????? //獲取并且保存和打印指定的物理接口IP地址信息
?? temp = ioctl(fd,SIOCGIFADDR,ð);
?if(temp<0)
?{
??printf("ioctl--get hardware addr failed!\n");
??exit(1);
??????? }
?memcpy(IP_addr ,&(((struct sockaddr_in *)(ð.ifr_addr))->sin_addr),4);
?//關閉套接口
?? close(fd);
}
?
2)? Ping程序
??????? 未完待續。。。
3)? 路徑MTU測試程序
??????? 未完待續。。。
4)? 網絡ip設備掃描程序
??????? 未完待續。。。
?
總結
以上是生活随笔為你收集整理的Linux底层网络编程--ARP,PING等的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: delete和backspace删除键的
- 下一篇: Github上传代码指南(Window版