日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux网络编程:原始套接字的魔力【续】

發布時間:2025/3/15 linux 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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協議而言,分別為64 ?操作碼:1表示ARP請求;2表示ARP應答;3表示RARP請求;4表示RARP應答。 ? ? ? ?我們這里只討論硬件地址為以太網地址、協議地址為IP地址的情形,所以剩下四個字段就分別表示發送方的MACIP地址、接收方的MACIP地址了。 ? ? ? ?注意:對于一個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地址以及我本地主機的IPMAC地址。網關收到該請求后,對我的這個報文進行了回應,將它的MAC地址在ARP應答報文中發給我了。 ? ? ? ?在這個示例程序中,我們完全自己手動構造了以太幀頭部,并完成了整個ARP請求報文的填充,最后用sendto函數,將我們的數據通過eth0接口發送出去。這個程序的靈活性還在于支持多網卡,使用時只要指定網卡名稱(eth0eth1),程序便會自動去獲取指定接口相應的IPMAC地址,然后用它們去填充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等的認識應該會有一個較高的突破。當然,你也必須趕 快對它們熟悉起來,因為后面我們不但要“知其然”,還要知其“所以然”。后面,我們會以這些函數調用為主線,看看它們到底在內核中做些哪些事情,而這又對 我們理解協議棧的實現原理有什么幫助做進一步的分析和討論。

    轉載于:https://blog.51cto.com/yehubilee/1069078

    總結

    以上是生活随笔為你收集整理的Linux网络编程:原始套接字的魔力【续】的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。