原始套接字编程分析
与普通socket套接字编程,区别在于,原始套接字可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有的数据帧(数据包)。另外,必须在管理员权限下才能使用原始套接字。
通常情况下所接触到的套接字(Socket)为两类:
- 流式套接字(SOCK_STREAM):一种面向连接的Socket,针对于面向连接的TCP 服务应用;
- 数据报式套接字(SOCK_DGRAM):一种无连接的Socket,对应于无连接的UDP 服务应用。
而原始套接字(SOCK_RAW)与标准套接字(SOCK_STREAM、SOCK_DGRAM)的区别在于原始套接字直接置“根”于操作系统网络核心(Network Core),而 SOCK_STREAM、SOCK_DGRAM 则“悬浮”于 TCP 和 UDP 协议的外围,如图,
流式套接字只能收发 TCP 协议的数据,数据报套接字只能收发 UDP 协议的数据,原始套接字可以收发没经过内核协议栈的数据包
原始套接字编程
int socket ( int family, int type, int protocol );
- family:协议簇 写 PF_PACKET(AF_PACKET)
- type: 套接字类,写 SOCK_RAW
protocol:协议类别,指定可以接收或发送的数据包类型,不能写 “0”,取值如下,注意,传参时需要用 htons() 进行字节序转换。
ETH_P_IP:IPV4数据包
ETH_P_ARP:ARP数据包
ETH_P_ALL:任何协议类型的数据包返回值:
成功( >0 ):套接字,这里为链路层的套接字
失败( <0 ):出错
原始套接字demo
下面主要通过PF_PACKET和SOCK_RAW,实现发送和接收自定义type以太网数据包。
以太网的侦结构如下:
------------------------------------------------------
| 目的地址 | 源地址 | 类型 | 数据 |
------------------------------------------------------
| 6 byte | 6 byte | 2 byte | 46~1500 byte |
/**
* recv.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <bits/ioctls.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <errno.h>
#define ETH_P_NP 0x0807
#define IFRNAME0 "eth0"
#define IFRNAME2 "eth2"
unsigned char dest_mac[6] = {0};
int main(int argc,char **argv)
{
int i ,datalen;
int sd ;
unsigned char data[IP_MAXPACKET] = {0};
unsigned char *buf = NULL;
struct sockaddr_ll device;
struct ifreq ifr;
socklen_t sll_len = sizeof(struct sockaddr_ll);
bzero(&device,sizeof(struct sockaddr_ll));
bzero(&ifr,sizeof(struct ifreq));
if((sd = socket(PF_PACKET,SOCK_DGRAM,htons(ETH_P_ALL))) < 0) {
perror("socket() failed to get socket descriptor for using ioctl()");
exit(EXIT_FAILURE);
}
memcpy(ifr.ifr_name,IFRNAME2,sizeof(struct ifreq));
if(ioctl(sd,SIOCGIFHWADDR,&ifr) < 0) {
perror("ioctl() failed to get source MAC address");
return (EXIT_FAILURE);
}
close(sd);
memcpy(dest_mac,ifr.ifr_hwaddr.sa_data,6);
memcpy(device.sll_addr,dest_mac,6);
device.sll_ifindex = ifr.ifr_ifindex;
device.sll_family = PF_PACKET;
device.sll_halen = htons(6);
device.sll_protocol = htons(ETH_P_NP);
if((sd = socket (PF_PACKET,SOCK_RAW,htons(ETH_P_NP))) < 0) {
perror("socket() failed");
exit(EXIT_FAILURE);
}
datalen = recvfrom(sd,data,1024,0,(struct sockaddr *)&device,&sll_len);
if (datalen < 0) { printf("error\n"); exit(-1); }
buf = data + 14;
printf("ip data : %s\n",buf);
return 0;
}
/**
* send.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <bits/ioctls.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <errno.h>
#define ETH_P_NP 0x0807
#define IFRNAME0 "eth0"
#define IFRNAME1 "eth1"
unsigned char source_mac[6] = {0};
unsigned char dest_mac[6] = {0};
int main(int argc,char **argv)
{
int i ,datalen ,frame_length;
int sd ,bytes;
uint8_t data[IP_MAXPACKET];
uint8_t ether_frame[IP_MAXPACKET];
struct sockaddr_ll device;
struct ifreq ifr;
bzero(&device,sizeof(struct sockaddr_ll));
bzero(&ifr,sizeof(struct ifreq));
if((sd = socket(PF_PACKET,SOCK_DGRAM,htons(ETH_P_ALL))) < 0) {
perror("socket() failed to get socket descriptor for using ioctl()");
exit(EXIT_FAILURE);
}
memcpy(ifr.ifr_name,IFRNAME1,sizeof(struct ifreq));
if(ioctl(sd,SIOCGIFHWADDR,&ifr) < 0) {
perror("ioctl() failed to get source MAC address");
return (EXIT_FAILURE);
}
close(sd);
//copy source MAC address
memcpy(source_mac,ifr.ifr_hwaddr.sa_data,6);
memcpy(device.sll_addr,source_mac,6);
if((device.sll_ifindex = if_nametoindex(IFRNAME1)) == 0) {
perror("if_nametoindex() failed to obtain interface index");
exit(EXIT_FAILURE);
}
// /*or*/ device.sll_addr = ifr.ifr_ifindex;
device.sll_family = PF_PACKET;
device.sll_halen = htons(6);
//device.sll_protocol = htons(ETH_P_NP);
// ifconfig eth2 /*00:0c:29:78:65:12*/
dest_mac[0] = 0x00;
dest_mac[1] = 0x0c;
dest_mac[2] = 0x29;
dest_mac[3] = 0x78;
dest_mac[4] = 0x65;
dest_mac[5] = 0x12;
datalen = 9;
data[0] = 'h';
data[1] = 'e';
data[2] = 'l';
data[3] = 'l';
data[4] = 'o';
data[5] = ' ';
data[6] = 'z';
data[7] = 'o';
data[8] = 'u';
frame_length = 6 + 6 + 2 + datalen;
/* 如果希望方便的话,可以尝试使用libnet */
memcpy(ether_frame,dest_mac,6);
memcpy(ether_frame + 6,source_mac,6);
ether_frame[12] = ETH_P_NP / 256;
ether_frame[13] = ETH_P_NP % 256;
memcpy(ether_frame + 14,data,datalen);
//one question : Here third parameters make me feel confused
//if((sd = socket (PF_PACKET,SOCK_RAW,htons(ETH_P_NP))) < 0)
if((sd = socket (PF_PACKET,SOCK_RAW,htons(ETH_P_ALL))) < 0) {
perror("socket() failed");
exit(EXIT_FAILURE);
}
while(1) {
if ((bytes = sendto (sd, ether_frame, frame_length, 0, (struct sockaddr *)&device, sizeof (device))) <= 0) {
perror("sendto() failed");
exit(EXIT_FAILURE);
}
sleep(4);
}
close(sd);
return 0;
}
其他类型的原始套接字,可参考以下的博文
linux原始套接字(1)-arp请求与接收:http://www.cnblogs.com/yuuyuu/p/5164685.html
linux原始套接字(2)-icmp请求与接收:http://www.cnblogs.com/yuuyuu/p/5167525.html
linux原始套接字(3)-构造IP_TCP发送与接收:http://www.cnblogs.com/yuuyuu/p/5169931.html
linux原始套接字(4)-构造IP_UDP:http://www.cnblogs.com/yuuyuu/p/5170056.html