Linux网络编程:原始套接字的魔力【续】
生活随笔
收集整理的這篇文章主要介紹了
Linux网络编程:原始套接字的魔力【续】
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
如何從鏈路層直接發送數據幀? ? ? ?本來以為這部分都弄完了,結果有朋友反映說看了半天還是沒看到如何從鏈路層直接發送數據。因為上一篇里面提到的是從鏈路層“收發”數據,結果只“收”完,忘了“發”,實在抱歉,所以就有這篇續出來了。? ? ? ?上一節我們主要研究了如何從鏈路層直接接收數據幀,可以通過bind函數來將原始套接字綁定到本地一個接口上,然后該套接字就只接收從該接口收上來的對應的數據包。今天我們用原始套接字來手工實現鏈路層ARP報文的發送和接收,以便大家對原始套接字有更深刻的掌握和理解。 ? ? ? ?ARP全稱為地址解析協議,是鏈路層廣泛使用的一種尋址協議,完成32比特IP地址到48比特MAC地址的映射轉換。在以太網中,當一臺主機需要向另外一臺主機發送消息時,它會首先在自己本地的ARP緩存表中根據目的主機的IP地址查找其對應的MAC地址,如果找到了則直接向其發送消息。如果未找到,它首先會在全網發送一個ARP廣播查詢,這個查詢的消息會被以太網中所有主機接收到,然后每個主機就根據ARP查詢報文中所指定的IP地址來檢查該報文是不是發給自己的,如果不是則直接丟棄;只有被查詢的目的主機才會對這個消息進行響應,然后將自己的MAC地址通告給發送者。 ? ? ? ?也就是說,鏈路層中是根據MAC地址來確定唯一一臺主機。以太幀格式如下:? ? ? ?以太幀首部中2字節的幀類型字段指定了其上層所承載的具體協議,常見的有0x0800表示是IP報文、0x0806表示RARP協議、0x0806即為我們將要討論的ARP協議。 ?硬件類型: 1表示以太網。 ?協議類型: 0x0800表示IP地址。和以太頭部中幀類型字段相同。 ?硬件地址長度和協議地址長度:對于以太網中的ARP協議而言,分別為6和4; ?操作碼:1表示ARP請求;2表示ARP應答;3表示RARP請求;4表示RARP應答。 ? ? ? ?我們這里只討論硬件地址為以太網地址、協議地址為IP地址的情形,所以剩下四個字段就分別表示發送方的MAC和IP地址、接收方的MAC和IP地址了。 ? ? ? ?注意:對于一個ARP請求報文來說,除了接收方硬件地址外,其他字段都要填充。當系統收到一個ARP請求時,會查詢該請求報文中接收方的協議地址是否和自己的IP地址相等,如果相等,它就把自己的硬件地址和協議地址填充進去,將發送和接收方的地址互換,然后將操作碼改為2,發送回去。
???????下面看一個使用原始套接字發送ARP請求的例子:點擊(此處)折疊或打開#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#include <net/if_arp.h>
#include <netpacket/packet.h>
#include <net/if.h>
#include <net/ethernet.h>
#define BUFLEN 42
int main(int argc,char** argv){
????int skfd,n;
? ? char buf[BUFLEN]={0};
? ? struct ether_header *eth;
? ? struct ether_arp *arp;
? ? struct sockaddr_ll toaddr;
? ? struct in_addr targetIP,srcIP;
? ? struct ifreq ifr;
? ? unsigned char src_mac[ETH_ALEN]={0};
? ? unsigned char dst_mac[ETH_ALEN]={0xff,0xff,0xff,0xff,0xff,0xff}; //全網廣播ARP請求
????if(3 != argc){
? ? ? ? ? ? printf("Usage: %s netdevName dstIP\n",argv[0]);
? ? ? ? ? ??exit(1);
? ? }
? ? if(0>(skfd=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL)))){
? ? ? ? ? ? perror("Create Error");
? ? ? ? ? ??exit(1);
? ? }
? ? bzero(&toaddr,sizeof(toaddr));
? ? bzero(&ifr,sizeof(ifr));
? ? strcpy(ifr.ifr_name,argv[1]);
? ? //獲取接口索引
? ? if(-1 == ioctl(skfd,SIOCGIFINDEX,&ifr)){
? ? ? ? ? ?perror("get dev index error:");
? ? ? ? ? ?exit(1);
? ? }
? ? toaddr.sll_ifindex = ifr.ifr_ifindex;
? ? printf("interface Index:%d\n",ifr.ifr_ifindex);
? ??//獲取接口IP地址 ? ??if(-1 == ioctl(skfd,SIOCGIFADDR,&ifr)){
? ? ? ? ? ?perror("get IP addr error:");
? ? ? ? ? ?exit(1);
? ??}
? ? srcIP.s_addr = ((struct sockaddr_in*)&(ifr.ifr_addr))->sin_addr.s_addr;
? ? printf("IP addr:%s\n",inet_ntoa(((struct sockaddr_in*)&(ifr.ifr_addr))->sin_addr));
? ??//獲取接口的MAC地址
? ??if(-1 == ioctl(skfd,SIOCGIFHWADDR,&ifr)){
? ? ? ? ? ?perror("get dev MAC addr error:");
? ? ? ? ? ?exit(1);
? ??}
? ? memcpy(src_mac,ifr.ifr_hwaddr.sa_data,ETH_ALEN);
? ? printf("MAC :%02X-%02X-%02X-%02X-%02X-%02X\n",src_mac[0],src_mac[1],src_mac[2],src_mac[3],src_mac[4],src_mac[5]);
? ??//開始填充,構造以太頭部
? ? eth=(struct ether_header*)buf;
? ? memcpy(eth->ether_dhost,dst_mac,ETH_ALEN);
? ? memcpy(eth->ether_shost,src_mac,ETH_ALEN);
? ? eth->ether_type = htons(ETHERTYPE_ARP);
? ??//手動開始填充用ARP報文首部 ? ? arp=(struct arphdr*)(buf+sizeof(struct ether_header));
? ? arp->arp_hrd = htons(ARPHRD_ETHER); //硬件類型為以太
? ? arp->arp_pro = htons(ETHERTYPE_IP); //協議類型為IP
? ??//硬件地址長度和IPV4地址長度分別是6字節和4字節
? ? arp->arp_hln = ETH_ALEN;
? ? arp->arp_pln = 4;
? ??//操作碼,這里我們發送ARP請求
? ? arp->arp_op = htons(ARPOP_REQUEST);
? ? ??
? ??//填充發送端的MAC和IP地址
? ? memcpy(arp->arp_sha,src_mac,ETH_ALEN);
? ? memcpy(arp->arp_spa,&srcIP,4);
? ??//填充目的端的IP地址,MAC地址不用管 ? ? inet_pton(AF_INET,argv[2],&targetIP);
? ? memcpy(arp->arp_tpa,&targetIP,4);
? ? toaddr.sll_family = PF_PACKET;
? ? n=sendto(skfd,buf,BUFLEN,0,(struct sockaddr*)&toaddr,sizeof(toaddr));
? ? close(skfd);
? ? return 0;
} ? ? ?結果如下:? ? ? ?可以看到,我向網關發送一個ARP查詢請求,報文中攜帶了網關的IP地址以及我本地主機的IP和MAC地址。網關收到該請求后,對我的這個報文進行了回應,將它的MAC地址在ARP應答報文中發給我了。 ? ? ? ?在這個示例程序中,我們完全自己手動構造了以太幀頭部,并完成了整個ARP請求報文的填充,最后用sendto函數,將我們的數據通過eth0接口發送出去。這個程序的靈活性還在于支持多網卡,使用時只要指定網卡名稱(如eth0或eth1),程序便會自動去獲取指定接口相應的IP和MAC地址,然后用它們去填充ARP請求報文中對應的各字段。 ? ? ? ?在頭文件<net/thernet.h>里,主要對以太幀首部進行了封裝:點擊(此處)折疊或打開struct ether_header
{
? ?u_int8_t ether_dhost[ETH_ALEN]; /* destination eth addr */
? ?u_int8_t ether_shost[ETH_ALEN]; /* source ether addr */
? ?u_int16_t ether_type; /* packet type ID field */
} __attribute__ ((__packed__)); ? ? ?在頭文件<net/if_arp.h>中,對ARP首部進行了封裝:點擊(此處)折疊或打開struct arphdr
{
? ? unsigned short ar_hrd; /* format of hardware address */
? ? unsigned short ar_pro; /* format of protocol address */
? ? unsigned char ar_hln; /* length of hardware address */
? ? unsigned char ar_pln; /* length of protocol address */
? ? unsigned short ar_op; /* ARP opcode (command) */
} ? ? ??而頭文件<netinet/if_ether.h>里,又對ARP整個報文進行了封裝:點擊(此處)折疊或打開struct ether_arp {
? ? struct arphdr ea_hdr; /* fixed-size 8 bytes header */
? ? u_int8_t arp_sha[ETH_ALEN]; /* sender hardware address */
? ? u_int8_t arp_spa[4]; /* sender protocol address */
? ? u_int8_t arp_tha[ETH_ALEN]; /* target hardware address */
? ? u_int8_t arp_tpa[4]; /* target protocol address */
};
#define arp_hrd ea_hdr.ar_hrd
#define arp_pro ea_hdr.ar_pro
#define arp_hln ea_hdr.ar_hln
#define arp_pln ea_hdr.ar_pln
#define arp_op ea_hdr.ar_op ? ? 最后再看一個簡單的接收ARP報文的小程序:?
點擊(此處)折疊或打開#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#include <net/if_arp.h>
#include <netpacket/packet.h>
#include <net/if.h>
#define BUFLEN 60
int main(int argc,char** argv){
????int i,skfd,n;
????char buf[ETH_FRAME_LEN]={0};
????struct ethhdr *eth;
????struct ether_arp *arp;
????struct sockaddr_ll fromaddr;
????struct ifreq ifr;
????unsigned char src_mac[ETH_ALEN]={0};
????if(2 != argc){
????????printf("Usage: %s netdevName\n",argv[0]);
????????exit(1);
????}
????//只接收發給本機的ARP報文
????if(0>(skfd=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ARP)))){
????????perror("Create Error");
????????exit(1);
????}
????bzero(&fromaddr,sizeof(fromaddr));
????bzero(&ifr,sizeof(ifr));
????strcpy(ifr.ifr_name,argv[1]);
????//獲取接口索引
????if(-1 == ioctl(skfd,SIOCGIFINDEX,&ifr)){
????????perror("get dev index error:");
????????exit(1);
????}
????fromaddr.sll_ifindex = ifr.ifr_ifindex;
????printf("interface Index:%d\n",ifr.ifr_ifindex);
????//獲取接口的MAC地址
????if(-1 == ioctl(skfd,SIOCGIFHWADDR,&ifr)){
????????perror("get dev MAC addr error:");
????????exit(1);
????}
????memcpy(src_mac,ifr.ifr_hwaddr.sa_data,ETH_ALEN);
????printf("MAC :%02X-%02X-%02X-%02X-%02X-%02X\n",src_mac[0],src_mac[1],src_mac[2],src_mac[3],src_mac[4],src_mac[5]);
????fromaddr.sll_family = PF_PACKET;
????fromaddr.sll_protocol=htons(ETH_P_ARP);
????fromaddr.sll_hatype=ARPHRD_ETHER;
????fromaddr.sll_pkttype=PACKET_HOST;
????fromaddr.sll_halen=ETH_ALEN;
????memcpy(fromaddr.sll_addr,src_mac,ETH_ALEN);
????bind(skfd,(struct sockaddr*)&fromaddr,sizeof(struct sockaddr));
????while(1){
????????memset(buf,0,ETH_FRAME_LEN);
????????n=recvfrom(skfd,buf,ETH_FRAME_LEN,0,NULL,NULL);
????????eth=(struct ethhdr*)buf;
????????arp=(struct ether_arp*)(buf+14);
????????printf("Dest MAC:");
????????for(i=0;i<ETH_ALEN;i++){
????????????printf("%02X-",eth->h_dest[i]);
????????}
????????printf("Sender MAC:");
????????for(i=0;i<ETH_ALEN;i++){
????????????printf("%02X-",eth->h_source[i]);
????????}
????????printf("\n");
????????printf("Frame type:%0X\n",ntohs(eth->h_proto));
????????if(ntohs(arp->arp_op)==2){
????????????printf("Get an ARP replay!\n");
????????}
????}
????close(skfd);
????return 0;
} ?該示例程序中,調用recvfrom之前我們調用了bind系統調用,目的是僅從指定的接口接收ARP報文(由socket函數的第三個參數“ETH_P_ARP”決定)。可以對比一下,該程序與博文“Linux網絡編程:原始套接字的魔力【下】”里介紹的抓包程序的區別。?小 結:通過這幾個章節的熱身,相信大家對網絡編程中常見的一系列API函數 socket,bind,listen,connect,sendto,recvfrom,close等的認識應該會有一個較高的突破。當然,你也必須趕 快對它們熟悉起來,因為后面我們不但要“知其然”,還要知其“所以然”。后面,我們會以這些函數調用為主線,看看它們到底在內核中做些哪些事情,而這又對 我們理解協議棧的實現原理有什么幫助做進一步的分析和討論。
???????下面看一個使用原始套接字發送ARP請求的例子:點擊(此處)折疊或打開
點擊(此處)折疊或打開
轉載于:https://blog.51cto.com/yehubilee/1069078
總結
以上是生活随笔為你收集整理的Linux网络编程:原始套接字的魔力【续】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: memcached完全剖析
- 下一篇: 更改“我的文档”文件夹的默认位置