linux sock_raw原始套接字编程

sock_raw原始套接字编程可以接收到本机网卡上的数据帧或者数据包,对与监听网络的流量和分析是很有作用的.一共可以有3种方式创建这种socket
 
1.socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)发送接收ip数据包
2.socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))发送接收以太网数据帧
 
 socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
能:该套接字可以接收协议类型为(tcp udp icmp等)发往本机的ip数据包,从上面看的就是20+8+100.
不能:不能收到非发往本地ip的数据包(ip软过滤会丢弃这些不是发往本机ip的数据包).
不能:不能收到从本机发送出去的数据包.
发送的话需要自己组织tcp udp icmp等头部.可以setsockopt来自己包装ip头部
     
 socket(PF_PACKET, SOCK_RAW, htons(x)); 
这个套接字比较强大,创建这种套接字可以监听网卡上的所有数据帧.从上面看就是20+20+8+100.最后一个以太网crc从来都不算进来的,因为内核已经判断过了,对程序来说没有任何意义了.
能: 接收发往本地mac的数据帧
能: 接收从本机发送出去的数据帧(第3个参数需要设置为ETH_P_ALL)
能: 接收非发往本地mac的数据帧(网卡需要设置为promisc混杂模式)
协议类型一共有四个
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的数据帧)
 
发送的时候需要自己组织整个以太网数据帧.所有相关的地址使用struct sockaddr_ll 而不是struct sockaddr_in(因为协议簇是PF_PACKET不是AF_INET了),比如发送给某个机器,对方的地址需要使用struct sockaddr_ll.
 
总结使用方法: 1.只想收到发往本机某种协议的ip数据包的话用第一种就足够了
            2. 更多的详细的内容请使用第二种.包括ETH_P_ALL参数和混杂模式都可以使它的能力不断的加强.
 
例程:
1。 构建发送ip数据包到rawsocket
                int  m_nSockfd;
		struct sockaddr_in m_AddrSock;
                memset(&m_AddrSock, 0, sizeof(struct sockaddr_in));
		m_AddrSock.sin_family = AF_INET;
		if ((m_nSockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP)) < 0)
		{
			printf("build send raw error\n");
			return;
		}
		unsigned int tmp = 1;
		if (setsockopt(m_nSockfd, IPPROTO_IP, IP_HDRINCL, &tmp, sizeof(tmp)) < 0)
		{
			printf("set raw sock opt error\n");
			return;
		}
//通过该socket 发送ip包
	struct iphdr *ipHead = (iphdr*)buf;
	m_AddrSock.sin_addr.s_addr = ipHead->daddr; //将sockaddr_in 结构体的sin_addr.s_addr 设为目的ip地址
	int sendBytes =  sendto(m_nSockfd, buf, len, 0, (struct sockaddr *)&m_AddrSock, sizeof(m_AddrSock));

2.构建绑定网卡接收以太网帧的rawsocket
	int m_nSockfd;
        struct sockaddr_ll         m_AddrSll;
       if ((m_nSockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) < 0)
	{
		perror("build recv raw socket error!\n");
		return;
	}
	struct ifreq ifstruct;
	strcpy(ifstruct.ifr_name, m_strInetName);
	if (ioctl(m_nSockfd, SIOCGIFINDEX, &ifstruct) < 0)
	{
		perror("get inet index error!\n");
		return;
	}
	memset(&m_AddrSll, 0, sizeof(struct sockaddr_ll));
	m_AddrSll.sll_family = AF_PACKET;
	m_AddrSll.sll_ifindex = ifstruct.ifr_ifindex;
	m_AddrSll.sll_protocol = htons(ETH_P_IP);

	if(bind(m_nSockfd, (struct sockaddr *) &m_AddrSll, sizeof(m_AddrSll)) == -1 )
	{
	   perror("bind socket error!\n");
	   return;
	}
<span style="font-family: Arial;">             //收到以太网帧</span>
<span style="font-family: Arial;">            int recvBytes = recv(m_nSockfd, buf, len, 0);</span>

关于raw socket 编程的几点注意:
1. 发送以太网帧时候,不需要设置最后的4字节crc校验,由内核自动完成。
2. 协议类型可以自定义,如
#define ETH_P_NEW   0xAAAA
 就可以设置 
<span style="color: rgb(51, 51, 51);">    socket(PF_PACKET, SOCK_RAW, htons</span><span style="color:#ff0000;">(ETH_P_NEW)</span>
<span style="color: rgb(51, 51, 51);">    m_AddrSll.sll_protocol = htons(</span><span style="color:#ff0000;">ETH_P_NEW)</span><span style="color:#333333;">;</span>
send 函数
<span style="color:#333333;">    struct ethhdr* ethHead = (ethhdr*)pos;
    memcpy(ethHead->h_source, mac_src_, MAC_LEN);
    memcpy(ethHead->h_dest, mac_dst_, MAC_LEN);
    ethHead->h_proto = htons</span><span style="color:#ff0000;">(ETH_P_NEW)</span><span style="color:#333333;">;
    pos += ETH_HEAD_LEN;
    memcpy(pos, buf, len);  //将ip包的内容拷贝到以太网帧中</span>
<span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px; white-space: pre;">	</span><span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;">int sendBytes = sendto(socket_fd_, etherPack, pos - etherPack, 0, (struct sockaddr*)&socket_addr_ll_, sizeof(sockaddr_ll));</span>
<span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;">recv 函数</span>
<span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;">int RawSocket::RecvPack(char*buf, int len)
{
<span style="white-space:pre">	</span>int recvedBytes = recv(socket_fd_, buf, len, 0);  //收到的内容为以太网帧,不包括后面的crc校验
<span style="white-space:pre">	</span>if (recvedBytes == -1)
<span style="white-space:pre">		</span>printf("socket recved error: %d!\n", errno);
<span style="white-space:pre">	</span>return recvedBytes;
}</span>
来进行自定义协议类型发送接收数据包(以太网帧)。




版权声明:本文为skc361原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。