开发者社区> 技术小阿哥> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Raw_Socket原始套接字

简介:
+关注继续查看

一、创建raw socket的权限:只有root权限才能够创建.


二、raw socket的用途:主要有三个方面

(1):通过raw socket来接收发向本机的ICMP,IGMP协议包,或者用来发送这些协议包.

(2):接收发向本机但TCP/IP栈不能够处理的IP包:现在许多操作系统在实现网络部分的时候,通常只实现了常用的几种协议,如tcp,udp,icmp等,但象其它的如ospf,ggp等协议,操作系统往往没有实现,如果自己有必要编写位于其上的应用,就必须借助raw socket来实现,这是因为操作系统遇到自己不能够处理的数据包(ip头中的protocol所指定的上层协议不能处理)就将这个包交给协议对应的raw socket.

(3):用来发送一些自己制定源地址等特殊作用的IP包(自己写IP头,TCP头等等),因为内核不能识别的协议、格式等将传给原始套接字,因此,可以使用原始套接字定义用户自己的协议格式


三、socket的分类:根据网路的7层模型,socket可以分为3种

(1):传输层socket-最常用的socket,非raw socket

    建立方式如下:

    sockfd = socket(AF_INET,SOCK_STREAM/SOCK_DGRAM, protocol);

    收发数据格式:用于TCP和UDP协议,发送和接收的数据都不包含UDP头或TCP头,使用ip地址+端口号作为地址。

(2):网络层socket - 网络层 raw socket

    建立方式如下:

    sockfd = socket(PF_INET, SOCK_RAW, protocol);

    收发数据格式:用于IP层,发送数据可以使用setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &flag, sizeof(int))指明是否由作者自己填充IP头;接收的数据中包含IP头。

    第一个参数:PF_INET和AF_INET的区别-指定address family时一般设置为AF_INET,即使用IP;指定protocal family时一般设置PF_INET。在头文件中有如下定义 #define AF_INET 0
#define PF_INET  AF_INET  所以AF_INET与PF_INET完全一样
    第二个参数说明建立的是一个raw socket
    第三个参数分三种情况: (socket(AF_INET, SOCK_RAW, IPPROTO_TCP/IPPROTO_UDP/IPPROTO_ICMP)可以同时接收都过传输层协议,也可以自定义传输层的协议号)
    a.参数protocol用来指明所要接收的协议号,如果是象IPPROTO_TCP(6)这
    种非0,非255号的协议,则内核碰到ip头中protocol域和创建socket所使用参数protocol相同的IP包,就会交给这个rawsocket来处理.

     接收IP包:当内核向此raw socket交付数据包的时候,是包括整个IP头的,并且已经是重组好的IP包. 如下:
    ---------------------------------------------------------------
    |ip header|tcp header(or x header)|             data          |
    ---------------------------------------------------------------         
    发送IP包时,默认不用包含IP包头,只需要填充参数protocol所指定的相应的协议头.也就是说,用sendto的时候,只需要构造这么一个缓冲区就可以了
    --------------------------------------------------------------
    |tcp header(or udp header or x header)|           data       |
    --------------------------------------------------------------
    发送IP包时,如果想自己也想亲自处理IP头,则需要IP_HDRINCL的socket选项.如下:int flag = 1;
    setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &flag, sizeof(int));这样,发送时所要提供的缓冲区有成如下形式:

    ---------------------------------------------------------------
    |ip header|tcp header(or x header)|             data          |
    ---------------------------------------------------------------         
    但是,即使是这种情况,在发送IP包的时候.也不是填充ip头的所有字段,而是应该将ip头的id(identification)字段设置为0,表示让内核来处理这个字段.同时,内核自动完成ip头的校验和的计算,并随后填充check字段.

    b.如果protocol是IPPROTO_RAW(255),这个socket只能用来发送IP包,而不能接收任何数据.发送的数据需要自己填充IP包头,并且自己计算校验和.
    c.如果protocol是IPPROTO_IP(0).在linux和sco unix上是不允许建立的.

网络层raw socket的特点:该套接字可以接收协议类型为(icmp,igmp等)发往本机的ip数据包;不能收到非发往本地ip的数据包(ip软过滤会丢弃这些不是发往本机ip的数据包);不能收到从本机发送出去的数据包;发送时需要自己组织tcp udp icmp等传输层协议头部,可以setsockopt来自己包装ip头部;接收的UDPTCP协议号的数据不会传给任何原始套接字接口,UDPTCP协议的数据需要通过MAC层原始套接字来获得。

传输层和网络层socket使用的收发地址:
struct sockaddr_in {   
 short sin_family;/*Addressfamily一般来说AF_INET(地址族)PF_INET(协议族)*/  
 unsigned short sin_port;/*Portnumber(必须要采用网络数据格式,-htons()*/  
 struct in_addr sin_addr;/*Internetaddress*/  
 unsigned char sin_zero[8];/*Samesizeasstructsockaddr没有实际意义,只是为了跟SOCKADDR结构在内存中对齐*/  
}; 
typedef struct in_addr   {   
 unsigned long s_addr;   
};


sockaddr_in addr_in;
memset((char*)&addr_in,0,sizeof(addr_in));
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(desPort);//可以不需要
addr_in.sin_addr.s_addr=inet_addr("192.168.0.1");
//inet_pton(AF_INET,"192.168.0.1", &servaddr.sin_addr);
int addr_in_len=sizeof(addr_in);
sendto(sockfd,"a",1,0,(struct sockaddr *)&servaddr, sizeof(servaddr));
recvfrom(sockfd, buf, 2, 0, (struct sockaddr *)&servaddr, &addr_in_len);
收发时进行强制地址转化。

 (3):MAC层socket - raw socket,收发数据链路层数据

 建立方式如下:

    sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP/ETH_P_ARP/ETH_P_ALL))

收发数据格式:发送和接收数据使用MAC地址,可以在MAC层发送和接收任意的数据,如果有上层协议需要自行添加协议头。

第三个参数:protocol协议类型一共有四个,意义如下

    ETH_P_IP 0x800 只接收发往本机mac的ip类型的数据帧 
ETH_P_ARP 0x806 只接受发往本机mac的arp类型的数据帧 
ETH_P_ARP 0x8035 只接受发往本机mac的rarp类型的数据帧 
ETH_P_ALL 0x3 接收发往本机mac的所有类型ip arp rarp的数据帧, 接收从本机发出的所有类型的数据帧.(混杂模式打开的情况下,会接收到非发往本地mac的数据帧),

MAC接收和发送的数据类型:接收和发送都是数据帧。  数据帧格式如下:

    ---------------------------------------------------------------
    |eth header|             data          |
    ---------------------------------------------------------------    

即: ---------------------------------------------------------------
    |MACdesAddr(6)|MACsrcAddr(6)|protocal(2)|        data      |
    ---------------------------------------------------------------   

 protocal为以太网协议:0x0806

MAC层使用的收发地址:- 用以指定本机收发使用的网卡信息,而不是目的地址

sll_protocol 是在 linux/if_ether.h 
struct sockaddr_ll 

unsigned short sll_family; /* 总是 AF_PACKET */ 
unsigned short sll_protocol; /* 物理层的协议 */ 
int sll_ifindex; /* 接口号 */ 
unsigned short sll_hatype; /* 报头类型 */ 
unsigned char sll_pkttype; /* 分组类型 */ 
unsigned char sll_halen; /* 地址长度 */ 
unsigned char sll_addr[8]; /* 物理层地址 */ 
}; 
sll_protocol 是在 linux/if_ether.h 头文件中定义的按网络层排序的标准的以太桢协议类型。
sll_ifindex 是接口的索引号-0 匹配所有的接口(当然只有合法的才用于绑定)。接口索引号sll_ifindex为0时表示使用有效的所有接口。接口的sll_ifindex值可以通过ioctl获得,如下面是获得名字为“eth0”的接口的索引号:strcpy(ifr.ifr_name,"eth0"); ioctl(fd_packet,SIOCGIFINDEX,&ifr);取得的值保存在ifr结构体的ifr_ifindex中,ifr结构类型为“struct ifreq”
sll_hatype 是在 linux/if_arp.h 中定义的 ARP 硬件地址类型。 
sll_pkttype 包含分组类型。有效的分组类型是:目标地址是本地主机的分组用的 PACKET_HOST,物理层广播分组用的 PACKET_BROADCAST ,发送到一个物理层多路广播地址的分组用的 PACKET_MULTICAST,在混杂(promiscuous)模式下的设备驱动器发向其他主机的分组用的 PACKET_OTHERHOST,本源于本地主机的分组被环回到分组套接口用的 PACKET_OUTGOING。这些类型只对接收到的分组有意义。
sll_addr 和 sll_halen 包括物理层(例如 IEEE 802.3)地址和地址长度。要获得接口的物理地址同样使用ioctl可以得到,ioctl(fd_packet,SIOCGIFHWADDR,&ifr);以数据形式保存在ifr的ifr_hwaddr.sa_data中
在调用recvfrom函数时返回的地址长度信息为18字节,原因是在sockaddr_ll结构中的sll_addr[8]为8字节,MAC地址只使用了其中的前6字节。在用sendto发送时需要将目的地址结构体强制转换为struct sockaddr 类型,而且指定的长度必须为20字节,而不能是18或其它值。

收发数据时,只需指定以下三个字段就行。

struct sockaddr_ll sll;
    memset((char *)&sll,0, sizeof(struct sockaddr_ll));
    sll.sll_family    = AF_PACKET;
    sll.sll_protocol  = htons(ETH_P_ALL);
    sll.sll_ifindex   = BspGetIfaceIndex (iRecvSocket,"eth1");
struct ifreq ifr;   
memset(&ifr,0,sizeof(ifr));
strcpy(ifr.ifr_name,"eth0");
if( (int)BSP_ERROR == ioctl(sock_fd,SIOCGIFINDEX,&ifr))
    {return BSP_ERROR;}
sll.sll_ifindex=ifr.ifr_ifindex;
int sll_len=sizeof(sll);

//收发时进行强制地址转化。

sendto(sockfd,"a",1,0,(struct sockaddr *)&sll, sizeof(sll));
recvfrom(sockfd, buf, 2, 0,(struct sockaddr *)&sll, &sll_len);

如果recvfrom的接收地址为NULL,表示从当前主机的所有网卡上接收。

MAC层socket的特点:可以接收发往本地mac的数据帧;可以接收从本机发送出去的数据帧(第3个参数需要设置为ETH_P_ALL);可以接收非发往本地mac的数据帧(网卡需要设置为promisc混杂模式)。


(4):socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL)),用于抓包程序,过时了,不要用啊


四、被bind绑定的sock_fd和被connect连接的sock_fd

    接收指定目的地址的数据:如果原始套接字bind绑定了一个地址,核心只将目的地址为本机IP地址的数包传递给原始套接字,如果某个原始套接字没有bind地址,核心就会把收到的所有IP数据包发给这个原始套接字. 
    接收指定源地址的数据:如果原始套接字调用了connect函数,则核心只将源地址为connect连接的IP地址的IP数据包传递给这个原始套接字. 
    接收所以数据:如果原始套接字没有调用bind和connect函数,则核心会将所有协议匹配的IP数据包传递给这个原始套接字.


五、socket的收发函数

    原始套接字可以使用也可以不使用bind()函数绑定地址,因为进行发送和接收数据的时候可以使sendto(),sendmsg()和函数recvfrom(),recvmsg()来发送和接收数据指定要发送和接收的目的地址的IP。但是使用bind只能接收到与该地址相同的网络包。

    当系统对socket进行了绑定的时候,发送和接收的函数可以使用send()和recv()及read()和write()等不需要指定目的地址的函数。


六、 把网卡置为混杂模式 - 接收eth上所有数据

  在正常的情况下,一个网络接口在数据链路层应该只响应两种数据帧: 
  a.与自己硬件地址相匹配的数据帧 ; b.发向所有机器的广播数据帧 
  如果要网卡接收所有通过它的数据, 而不管是不是发给它的, 那么必须把网卡置于混杂模式. 
 用 Raw Socket 实现代码如下:

    setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag);//设置IP头操作选项 
    bind(sockRaw, (PSOCKADDR)&addrLocal, sizeof(addrLocal); //把 sockRaw 绑定到本地网卡上 
    ioctlsocket(sockRaw, SIO_RCVALL, &dwValue);       //让 sockRaw 接受所有的数据

参数:flag 标志是用来设置 IP 头操作的, 也就是说要亲自处理 IP 头: bool flag = ture; 

      addrLocal 为本地地址: SOCKADDR_IN addrLocal; 
      dwValue 为输入输出参数, 为 1 时执行, 0 时取消: DWORD dwValue = 1;


七、内核接收网络数据后在rawsocket上处理原则
    (1):对于UDP/TCP产生的IP数据包,内核不将它传递给任何网络层的原始套接字,而只是将这些数据直接交给对应的传输层UDP/TCP数据socket处理句柄。所以,只能通过MAC层原始套接字将第3个参数指定为htons(ETH_P_IP)来访问TCP/UDP数据。

    (2):对于ICMP和EGP等使用IP数据包承载数据但又在传输层之下的协议类型的IP数据包,内核不管是否已经有注册了句柄来处理这些数据,都会将这些IP数据包复制一份传递给协议类型匹配的原始套接字.(网络层套接字能截获除TCP/UDP以外传输层协议号protocol相同的ip数据) 
    (3):对于不能识别协议类型的数据包,内核进行必要的校验,然后会查看是否有类型匹配的原始套接字负责处理这些数据,如果有的话,就会将这些IP数据包复制一份传递给匹配的原始套接字,否则,内核将会丢弃这个IP数据包,并返回一个ICMP主机不可达的消息给源主机.
    (4):如果原始套接字bind绑定了一个地址,核心只将目的地址为本机IP地址的数包传递给原始套接字,如果某个原始套接字没有bind地址,核心就会把收到的所有IP数据包发给这个原始套接字. 
    (5):如果原始套接字调用了connect函数,则核心只将源地址为connect连接的IP地址的IP数据包传递给这个原始套接字. 
    (6):如果原始套接字没有调用bind和connect函数,则核心会将所有协议匹配的IP数据包传递给这个原始套接字.


八、数据流动与数据被提交到socket句柄的时机

    网卡首先收到了一个以太网数据帧。 首先,网卡对该数据帧进行硬过滤(根据网卡的模式不同会有不同的动作,如果设置了promisc混杂模式的话,则不做任何过滤直接交给上一层输入例程,否则非本机mac或者广播mac会被直接丢弃).如果成功的话,会进入ip输入例程.但是在进入ip输入例程之前,系统会检查系统中是否有通socket(AF_PACKET, SOCK_RAW, ..)创建的套接字.如果有并且协议相符,系统就给每个这样的socket接收缓冲区发送一个数据帧拷贝。 然后,进入了ip输入例程,ip层会对该数据包进行软过滤,就是检查校验或者丢弃非本机ip或者广播ip的数据包等,如果成功的话会进入传输层输入例程.但是在交给传输层输入例程之前,系统会检查系统中是否有通过socket(AF_INET, SOCK_RAW, ..)创建的套接字.如果有的话并且协议相符,系统就给每个这样的socket接收缓冲区发送一个数据帧拷贝。 最后,进入传输层输入例程。




九、例程

例子1:网络层raw socket。下面的程序利用Raw Socket发送TCP报文,并完全手工建立报头:
int sendTcp(unsigned short desPort, unsigned long desIP)
{
WSADATA WSAData;
SOCKET sock;
SOCKADDR_IN addr_in;
IPHEADER ipHeader;
TCPHEADER tcpHeader;
PSDHEADER psdHeader;
char szSendBuf[MAX_LEN] = { 0 };
BOOL flag;
int rect, nTimeOver;
if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0)
{
printf("WSAStartup Error!\n");
return false;
}
if ((sock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_RAW, NULL, 0,
WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("Socket Setup Error!\n");
return false;
}
flag = true;
if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*) &flag, sizeof(flag)) 
==SOCKET_ERROR)
{
printf("setsockopt IP_HDRINCL error!\n");
return false;
}
nTimeOver = 1000;
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*) &nTimeOver, sizeof
(nTimeOver)) == SOCKET_ERROR)
{
printf("setsockopt SO_SNDTIMEO error!\n");
return false;
}
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(desPort);
addr_in.sin_addr.S_un.S_addr = inet_addr(desIP);
//填充IP报头
ipHeader.h_verlen = (4 << 4 | sizeof(ipHeader) / sizeof(unsigned long));
// ipHeader.tos=0;
ipHeader.total_len = htons(sizeof(ipHeader) + sizeof(tcpHeader));
ipHeader.ident = 1;
ipHeader.frag_and_flags = 0;
ipHeader.ttl = 128;
ipHeader.proto = IPPROTO_TCP;
ipHeader.checksum = 0;
ipHeader.sourceIP = inet_addr("localhost");
ipHeader.destIP = desIP;
//填充TCP报头
tcpHeader.th_dport = htons(desPort);
tcpHeader.th_sport = htons(SOURCE_PORT); //源端口号
tcpHeader.th_seq = htonl(0x12345678);
tcpHeader.th_ack = 0;
tcpHeader.th_lenres = (sizeof(tcpHeader) / 4 << 4 | 0);
tcpHeader.th_flag = 2; //标志位探测,2是SYN 
tcpHeader.th_win = htons(512);
tcpHeader.th_urp = 0;
tcpHeader.th_sum = 0;
psdHeader.saddr = ipHeader.sourceIP;
psdHeader.daddr = ipHeader.destIP;
psdHeader.mbz = 0;
psdHeader.ptcl = IPPROTO_TCP;
psdHeader.tcpl = htons(sizeof(tcpHeader));
//计算校验和
memcpy(szSendBuf, &psdHeader, sizeof(psdHeader));
memcpy(szSendBuf + sizeof(psdHeader), &tcpHeader, sizeof(tcpHeader));
tcpHeader.th_sum = checksum((unsigned short*)szSendBuf, sizeof(psdHeader) + sizeof
(tcpHeader));

memcpy(szSendBuf, &ipHeader, sizeof(ipHeader));
memcpy(szSendBuf + sizeof(ipHeader), &tcpHeader, sizeof(tcpHeader));
memset(szSendBuf + sizeof(ipHeader) + sizeof(tcpHeader), 0, 4);
ipHeader.checksum = checksum((unsigned short*)szSendBuf, sizeof(ipHeader) + sizeof
(tcpHeader));
memcpy(szSendBuf, &ipHeader, sizeof(ipHeader));
rect = sendto(sock, szSendBuf, sizeof(ipHeader) + sizeof(tcpHeader), 0,
(struct sockaddr*) &addr_in, sizeof(addr_in));
if (rect == SOCKET_ERROR)
{
printf("send error!:%d\n", WSAGetLastError());
return false;
}
else
printf("send ok!\n");
closesocket(sock);
WSACleanup();
return rect;
}



例子2:MAC层raw socket

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>       // close()
#include <string.h>       // strcpy, memset(), and memcpy()
#include <netdb.h>         // struct addrinfo
#include <sys/types.h>     // needed for socket(), uint8_t, uint16_t, uint32_t
#include <sys/socket.h>     // needed for socket()
#include <netinet/in.h>     // IPPROTO_ICMP, INET_ADDRSTRLEN
#include <netinet/ip.h>     // struct ip and IP_MAXPACKET (which is 65535)
#include <netinet/ip_icmp.h> // struct icmp, ICMP_ECHO
#include <arpa/inet.h>     // inet_pton() and inet_ntop()
#include <sys/ioctl.h>     // macro ioctl is defined
#include <bits/ioctls.h>     // defines values for argument "request" of ioctl.
#include <net/if.h>       // struct ifreq
#include <linux/if_ether.h>   // ETH_P_IP = 0x0800, ETH_P_IPV6 = 0x86DD
#include <linux/if_packet.h> // struct sockaddr_ll (see man 7 packet)
#include <net/ethernet.h>
#include <errno.h>         // errno, perror()
#define ETH_P_DEAN 0x8874 //自定义的以太网协议type
int main (int argc, char **argv)
{
  int i, datalen,frame_length, sd, bytes;
  char *interface="eth1";;
  uint8_t data[IP_MAXPACKET];
  uint8_t src_mac[6];
  uint8_t dst_mac[6];;
  uint8_t ether_frame[IP_MAXPACKET];
  struct sockaddr_ll device;
  // Submit request for a socket descriptor to look up interface.
  if ((sd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0) {//第一次创建socket是为了获取本地网卡信息
        perror ("socket() failed to get socket descriptor for using ioctl() ");
        exit (EXIT_FAILURE);
      }
  // Use ioctl() to look up interface name and get its MAC address.
  struct ifreq ifr;
  memset (&ifr, 0, sizeof (ifr));
  snprintf (ifr.ifr_name, sizeof (ifr.ifr_name), "%s", interface);

  if (ioctl (sd, SIOCGIFHWADDR, &ifr) < 0) {
        perror ("ioctl() failed to get source MAC address ");
        return (EXIT_FAILURE);
      }
  close (sd);
  memcpy (src_mac, ifr.ifr_hwaddr.sa_data, 6); // Copy source MAC address.
  memset (&device, 0, sizeof (device));
if ((device.sll_ifindex = if_nametoindex(interface)) == 0) {
    perror ("if_nametoindex() failed to obtain interface index ");
    exit (EXIT_FAILURE);
  }

  // Set destination MAC address: you need to fill these out
 //设置目的网卡地址  

  dst_mac[0] = 0x10; dst_mac[1] = 0x78; dst_mac[2] = 0xd2; 

  dst_mac[3] = 0xc6; dst_mac[4] = 0x2f; dst_mac[5] = 0x89;


  device.sll_family = AF_PACKET;
  memcpy (device.sll_addr, src_mac, 6);
  device.sll_halen = htons (6);
  // 发送的data,但是抓包时看到最小数据长度为46,以太网协议规定以太网帧数据域最小为46字节,不足的自动补零处理
  datalen = 12;
  data[0] = 'h';data[1] = 'e';data[2] = 'l';data[3] = 'l';data[4] = 'o';data[5] = ' ';
  data[6] = 'w';data[7] = 'o';data[8] = 'r';data[9] = 'l';data[10] = 'd';data[11] = '!';

  memcpy (ether_frame, dst_mac, 6);
  memcpy (ether_frame + 6, src_mac, 6);
  ether_frame[12] = ETH_P_DEAN / 256;
  ether_frame[13] = ETH_P_DEAN % 256;
  memcpy (ether_frame + 14 , data, datalen); 

  frame_length = 6 + 6 + 2   + datalen;


  if ((sd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0) {//创建正真发送的socket
    perror ("socket() failed ");
    exit (EXIT_FAILURE);
  }
  if ((bytes = sendto (sd, ether_frame, frame_length, 0, (struct sockaddr *) &device, sizeof (device))) <= 0) {
        perror ("sendto() failed");
        exit (EXIT_FAILURE);
     }  
  close (sd);
  return (EXIT_SUCCESS);
}

例子3:ARP地址解析

ARP协议帧:

wKioL1YD_t6gWNZdAAC2hBp0Ya4959.jpg

以太网的ARP数据结构:  

struct arppacket {   

unsigned short  ar_hrd;             /*硬件类型*/   

unsigned short  ar_pro;             /*协议类型*/   

unsigned char   ar_hln;            /*硬件地址长度*/   

unsigned char   ar_pln;            /*协议地址长度*/   

unsigned short  ar_op;             /*ARP操作码*/       

unsigned char   ar_sha[ETH_ALEN];       /*发送方MAC地址*/       

unsigned char   ar_sip[4];           /*发送方IP地址*/       

unsigned char   ar_tha[ETH_ALEN];        /*目的MAC地址*/       

unsigned char   ar_tip[4];           /*目的IP地址*/   

};  

 ARP请求包的构建包含了以太网头部部分、ARP头部部分、ARP的数据部分。其中特别要注意目的以太网地址,由于ARP的作用就是查找目的IP地 址的MAC地址,所以目的以太网地址是未知的。而且需要在整个以太网上查找其IP地址,所以目的以太网地址是一个全为1的值,即为 {0xFF,0xFF,0xFF ,0xFF ,0xFF ,0xFF}。


#include <sys/socket.h>  

#include <sys/ioctl.h>                  /*ioctl 命令*/   

#include <Linux/if_ether.h>             /*ethhdr 结构*/   

#include <net/if.h>                     /*ifreq 结构*/   

#include <netinet/in.h>                 /*in_addr结构*/   

#include <Linux/ip.h>                   /*iphdr 结构*/   

#include <Linux/udp.h>                  /*udphdr 结构*/  

#include <Linux/tcp.h>                  /*tcphdr 结构*/   

struct arppacket   {   

unsigned short  ar_hrd;             /*硬件类型*/   

unsigned short  ar_pro;             /*协议类型*/   

unsigned char   ar_hln;             /*硬件地址长度*/   

unsigned char   ar_pln;             /*协议地址长度*/   

unsigned short  ar_op;              /*ARP操作码*/   

unsigned char   ar_sha[ETH_ALEN];   /*发送方MAC地址*/   

unsigned char   ar_sip[4];          /*发送方IP地址*/   

unsigned char   ar_tha[ETH_ALEN];   /*目的MAC地址*/   

unsigned char   ar_tip[4];          /*目的IP地址*/   

};   

int main(int argc, char*argv[])   

  {   

     char ef[ETH_FRAME_LEN];             /*以太帧缓冲区*/   

      struct ethhdr*p_ethhdr;             /*以太网头部指针*/  

        

      char eth_dest[ETH_ALEN]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};    /*目的以太网地址*/   

      char eth_source[ETH_ALEN]={0x00,0x0C,0x29,0x73,0x9D,0x15}; /*源以太网地址*/                 char eth_dest[4]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};    /*目的IP地址*/  


      int n;           

      int fd;                               

      fd = socket(AF_INET, SOCK_PACKET, htons(0x0003));  /*fd是套接口的描述符*/  

   

      p_ethhdr = (struct ethhdr*)ef;   /*使p_ethhdr指向以太网帧的帧头*/ 

      memcpy(p_ethhdr->h_dest, eth_dest, ETH_ALEN);    /*复制目的以太网地址*/ 

      memcpy(p_ethhdr->h_source, eth_source, ETH_ALEN);  /*复制源以太网地址*/   

      p_ethhdr->h_proto = htons(0x0806);    /*设置协议类型,以太网0x0806*/  

         

      struct arppacket*p_arp;   

      p_arp = ef + ETH_HLEN;              /*定位ARP包地址*/   

      p_arp->ar_hrd = htons(0x1);         /*arp硬件类型*/   

      p_arp->ar_pro = htons(0x0800);      /*协议类型*/   

      p_arp->ar_hln = 6;                  /*硬件地址长度*/   

      p_arp->ar_pln = 4;                  /*IP地址长度*/   

         

      memcpy(p_arp->ar_sha, eth_source, ETH_ALEN);   /*复制源以太网地址*/   

     (unsigned int*)p_arp->ar_sip = inet_addr("192.168.1.152");   /*源IP地址*/ 

      memcpy(p_arp->ar_tha, eth_dest, ETH_ALEN);   /*复制目的以太网地址*/   

     (unsigned int*)p_arp->ar_tip = inet_addr("192.168.1.1");    /*目的IP地址*/    

        

      /*发送ARP请求8次,间隔1s*/   

      int i = 0;   

      for(i=0;i<8;i++){   

          n = write(fd, ef, ETH_FRAME_LEN);/*发送*/   

          sleep(1);                       /*等待1s*/   

      }   

      close(fd);   

      return 0;   

  }

例子4. ARP完全实现

获取局域网ip-mac例子:    

/***************************************************************************  

*   Copyright (C) 2007 by qzc   *  *   qzc1998@126.com   * 

*  This program is free software; you can redistribute it and/or modify  *  

*   it under the terms of the GNU General Public License as published by  *  

*   the Free Software Foundation; either version 2 of the License, or     *  

*   (at your option) any laterversion.                                  

*   This program is distributed in the hope that it will be useful,       *  

*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *  

*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *  

*   GNU General Public License for more details.                          *   

*   You should have received a copy of the GNU General Public License     *  

*   along with this program; if not, write to the                         *  

*   Free Software Foundation, Inc.,                                       *  

*   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             * 


 ***************************************************************************/  

#ifdef HAVE_CONFIG_H 

#include <config.h> 

#endif  

#include <stdio.h> 

#include <sys/time.h> 

#include <unistd.h> 

#include <string.h> 

#include <errno.h>

#include <netdb.h>  

#include <sys/socket.h> 

#include <sys/types.h>

#include <sys/ioctl.h>  

#include <netinet/if_ether.h> 

#include <netinet/in.h>  

#include <arpa/inet.h> 

#include <net/if.h>  

/* 得到本机的mac地址和ip地址 */ 

int GetLocalMac ( const char *device,char *mac,char *ip ) {     

    int sockfd;     

    struct ifreq req;     

    struct sockaddr_in * sin;      

if ( ( sockfd = socket ( PF_INET,SOCK_DGRAM,0 ) ) ==-1 ) {         

    fprintf ( stderr,"Sock Error:%s\n\a",strerror ( errno ) );         

    return ( -1 );     

}     

memset ( &req,0,sizeof ( req ) );     

strcpy ( req.ifr_name,device );     

if ( ioctl ( sockfd,SIOCGIFHWADDR, ( char * ) &req ) ==-1 )     {         

    fprintf ( stderr,"ioctl SIOCGIFHWADDR:%s\n\a",strerror ( errno ) );         

    close ( sockfd );         

    return ( -1 ); 

 }    

memcpy ( mac,req.ifr_hwaddr.sa_data,6 );      

req.ifr_addr.sa_family = PF_INET;     

if ( ioctl ( sockfd,SIOCGIFADDR, ( char * ) &req ) ==-1 )     {         

    fprintf ( stderr,"ioctl SIOCGIFADDR:%s\n\a",strerror ( errno ) );         

    close ( sockfd );         

    return ( -1 );     

 }     

sin = ( struct sockaddr_in * ) &req.ifr_addr;     

memcpy ( ip, ( char * ) &sin->sin_addr,4 );      

return ( 0 ); 

}  

char *mac_ntoa ( const unsigned char *mac ) {   

    static char buffer[18];     

memset ( buffer,0,sizeof ( buffer ) );     

sprintf ( buffer,"%02X:%02X:%02X:%02X:%02X:%02X",               

mac[0],mac[1],mac[2],mac[3],mac[4],mac[5] );     

return ( buffer ); 

 /* 根据 RFC 0826 修改*/ 

typedef struct _Ether_pkg Ether_pkg; 


struct _Ether_pkg {     /* 前面是ethernet头 */     

unsigned char ether_dhost[6]; /* 目地硬件地址 */     

unsigned char ether_shost[6]; /* 源硬件地址 */     

unsigned short int ether_type; /* 网络类型 */      /* 下面是arp协议 */    

unsigned short int ar_hrd; /* 硬件地址格式 */     

unsigned short int ar_pro; /* 协议地址格式 */     

unsigned char ar_hln; /* 硬件地址长度(字节) */     

unsigned char ar_pln; /* 协议地址长度(字节) */ 

unsigned short int ar_op; /* 操作代码 */     

unsigned char arp_sha[6]; /* 源硬件地址 */     

unsigned char arp_spa[4]; /* 源协议地址 */     

unsigned char arp_tha[6]; /* 目地硬件地址 */     

unsigned char arp_tpa[4]; /* 目地协议地址 */ 

}; 

 

    void parse_ether_package ( const Ether_pkg *pkg ) {     

    printf ( "源   IP=[%s] MAC=[%s]\n",inet_ntoa ( * ( struct in_addr * ) pkg->arp_spa ),mac_ntoa ( pkg->arp_sha ) );     

    printf ( "目地 IP=[%s] MAC=[%s]\n",inet_ntoa ( * ( struct in_addr * ) pkg->arp_tpa ),mac_ntoa ( pkg->arp_tha ) ); 

}  


int sendpkg ( char * mac,char * broad_mac,char * ip,char * dest ) {     

    Ether_pkg pkg;     

    struct hostent *host =NULL;     

    struct sockaddr sa;     

    int sockfd,len;     

    char buffer[255];     

    unsigned char temp_ip[5];     

    memset ( ( char * ) &pkg,'\0',sizeof ( pkg ) );      /* 填充ethernet包文 */     

    memcpy ( ( char * ) pkg.ether_shost, ( char * ) mac,6 );     

    memcpy ( ( char * ) pkg.ether_dhost, ( char * ) broad_mac,6 );     

    pkg.ether_type = htons ( ETHERTYPE_ARP );       /* 下面填充arp包文 */     

    pkg.ar_hrd = htons ( ARPHRD_ETHER );     

    pkg.ar_pro = htons ( ETHERTYPE_IP );    

    pkg.ar_hln = 6;     

    pkg.ar_pln = 4;     

    pkg.ar_op = htons ( ARPOP_REQUEST );     

    memcpy ( ( char * ) pkg.arp_sha, ( char * ) mac,6 );     

    memcpy ( ( char * ) pkg.arp_spa, ( char * ) ip,4 );     

    memcpy ( ( char * ) pkg.arp_tha, ( char * ) broad_mac,6 );      

   

fflush ( stdout );    

memset ( temp_ip,0,sizeof ( temp_ip ) );     

if ( inet_aton ( dest, ( struct in_addr * ) temp_ip ) ==0 )     {         

    if ( ( host = gethostbyname ( dest ) ) ==NULL )         {             

        fprintf ( stderr,"Fail! %s\n\a",hstrerror ( h_errno ) );             

        return ( -1 );         

     }      

   memcpy ( ( char * ) temp_ip,host->h_addr,4 );     

 }      

memcpy ( ( char * ) pkg.arp_tpa, ( char * )temp_ip,4 ); /* 实际应该使用PF_PACKET */     

if ( ( sockfd = socket ( PF_INET,SOCK_PACKET,htons ( ETH_P_ALL ) ) ) ==-1 )     {        

    fprintf ( stderr,"Socket Error:%s\n\a",strerror ( errno ) );         

    return ( 0 );     

}      

memset ( &sa,'\0',sizeof ( sa ) );     

strcpy ( sa.sa_data,"eth0" );     

 len = sendto ( sockfd,&pkg,sizeof ( pkg ),0,&sa,sizeof ( sa ) );     

if ( len != sizeof ( pkg ) )     {         

    fprintf ( stderr,"Sendto Error:%s\n\a",strerror ( errno ) );         

    close(sockfd);         

    return ( 0 );     

    }    

   Ether_pkg *parse;     

   parse = ( Ether_pkg * ) buffer;     

   fd_set readfds;     

   struct timeval tv;     

while(1)     {         

    tv.tv_sec = 0;         

    tv.tv_usec = 500000; //500毫秒         

    FD_ZERO ( &readfds );         

    FD_SET ( sockfd, &readfds );         

    len = select ( sockfd+1, &readfds, 0, 0, &tv );         

    if ( len>-1 )         {             

        if ( FD_ISSET ( sockfd,&readfds ) ) {                 

            memset ( buffer,0,sizeof ( buffer ) );                 

            len=recvfrom ( sockfd,buffer,sizeof ( buffer ),0,NULL,&len );                 

            if ( ( ntohs ( parse->ether_type ) ==ETHERTYPE_ARP ) &&( ntohs ( parse->ar_op ) == ARPOP_REPLY ) ){                     

                parse_ether_package ( parse );

                   }            

            }         

      }         

  break;     

}     

close(sockfd);     

return 1; 

}  

int main ( int argc,char **argv ) {     

    struct timeval tvafter,tvpre;     

    struct timezone tz;     

    gettimeofday ( &tvpre , &tz );      

    unsigned char mac[7];     

    unsigned char ip[5];     

    char dest[16]={0};     

    unsigned char broad_mac[7]={0xff,0xff,0xff,0xff,0xff,0xff,0x00};      

memset ( mac,0,sizeof ( mac ) );     

memset ( ip,0,sizeof ( ip ) );     

if ( GetLocalMac ( "eth0",mac,ip ) ==-1 )         

    return ( -1 );      

printf ( "本地 Mac=[%s] Ip=[%s]\n", mac_ntoa ( mac ),

inet_ntoa ( * ( struct in_addr * ) ip ) );          

sprintf ( dest,"255.255.255.255");         

sendpkg ( mac,broad_mac,ip,dest );      

gettimeofday ( &tvafter , &tz );      

printf ( "\n程序执行完毕:%d毫秒\n", ( tvafter.tv_sec-tvpre.tv_sec ) *1000+ ( tvafter.tv_usec-tvpre.tv_usec ) /1000 );      

return 0; 

}




本文转自 a_liujin 51CTO博客,原文链接:http://blog.51cto.com/a1liujin/1697465,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
套接字Socket编程(下)
Socket,原意插座、插口。写软件程序时,可以想象成一根网线,一头插在客户端,一头插在服务端,然后进行通信。所以通信前,双方都要建立一个Socket。
62 0
webSocket
webSocket是什么 webSocket是HTML5新出的一种协议,底层是基于TCP/IP协议的。跟http没有关系,只是复用了http握手通道,用来升级协议。
906 0
socket
通用的网络编程接口 unix系统中,网络应用编程界面 unix BSD的套接字(socket) unix system V 的TLI bsd socket systemV tli(transport layer interface)是一个调用库和在stream环境上面的运行时(run time)模块...
729 0
WebSocket兼容到低版本浏览器
就目前而言,WebSocket是最好的Web通信解决方案了。但是IE从10才开始兼容它,对于目前大量IE8存在的市场,原生的WebSocket显然不太实用,我们需要低版本兼容的解决方案。于是我模拟WebSocket在浏览器上的行为,用AS3写了个兼容的版本。
2296 0
socket 网摘
一、基本socket函数 Linux系统是通过提供套接字(socket)来进行网络编程的。网络的socket数据传输是一种特殊的I/O,socket也是一种文件描述符。socket也有一个类似于打 开文件的函数:socket(),调用socket(),该函数返回一个整型的socket的描述符,随后的连接建立、数据传输等操作也都是通过该socket实现。
668 0
socket
socket这个词可以表示很多概念: 在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP地址+端口号”就称为socket。 在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。socket本身有“插座”的意思,因此用来描述网络连接的一对一关系。 TCP/IP协议
1459 0
Socket(一)
  我刚开始学习,有什么不对的还希望大家指出。   由于客户端的编写与服务端的编写互相影响,所以两个的代码我是交互着写下的。   下面写的是一个客户端与一个服务端的例子。   对服务端,首先创建ServerSocket的实例,参数端口号的值是0-65535,但是1024以下的端号口会被系统征用,所以我给它分配的端口号是10086。
591 0
【转】Windows Socket网络编程(二)----套接字编程原理
Windows Socket 网络编程(二) —— 套接字编程原理作者: 冰点工作室 小鹰 一、客户机/服务器模式在TCP/IP网络中两个进程间的相互作用的主机模式是客户机/服务器模式(Client/Server model)。
845 0
文章
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载