
1.什么是数据链路层原始套接字?
数据链路层原始套接字是Linux内核提供的一种套接字,该套接字可以让用户程序直接发送和接收以太网数据,用户程序发送和接收的数据包必须是完整的以太网数据,内核不会再处理该数据而只是做一些简单的接收和发送工作。数据链路层原始套接字在实际环境中有很多的应用,比如可以用来监听和抓包,可以对数据包进行修改和定制等等,很多的开源软件也用到了原始套接字。 基于原始套接字这样的特性,可能会被一些不法分子利用,所以大家在使用原始套接字的时候,需要注意网络安全问题。
2.常用套接字的区别
主要区别:处理数据网络层级不一样。
- 普通套接字:
普通套接字包括(SOCK_STREAM)和(SOCK_DGRAM)只能处理TCP,UDP等负载数据,不能对头部进行处理。
- 网络层原始套接字:
网络层原始套接字(AF_INET,SOCK_RAW)不仅能对负载数据进行处理,还能处理IP,TCP,UDP,ICMP等头部,但是不能处理以太网头部信息。
- 数据链路层原始套接字:
数据链路层原始套接字(PF_PACKET, SOCK_RAW)能处理以太网数据帧所有的信息。

图 1 套接字区别
## 3.发送和接收原理- 发送原理:
用户程序必须组装好整个以太网数据帧,才能进行正常发送,所以用户程序得知道以下信息:源MAC地址,目的MAC地址,源IP地址,目的IP地址,源端口,目的端口等。
- 接收原理:
原始套接字将接收所有以太网数据帧,除非用户创建socket的时候对协议进行了过滤设置,比如说用户只想收到IP数据包,则协议需要设置为ETH_P_IP,网卡驱动接收到数据后,会将数据包传入Linux内核协议栈,内核协议栈会克隆一份数据包给原始套接字,另外一份数据包将继续走内核路径。
4.工作流程

图 2 工作流程
## 5.编程基础5.1 创建原始套接字
原始套接字创建地址域为PF_PACKET,套接字类型为SOCK_RAW,通过把protocol设置成不同的协议能够实现过滤的数据包的功能。
# include <sys/types.h>#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数:
domain:地址族,PF_PACKET
type:套接字类型,SOCK_RAW
protocol:以太网协议类型htons(ETH_P_ALL):处理所有以太网数据包htons(ETH_P_IP):处理IP数据包htons(ETH_P_ARP):处理ARP数据包
返回值:
成功:返回原始套接字文件描述符
失败:返回-1
5.2 发送数据
发送数据采用sendto函数,如果想使用send函数,可以通过bind函数绑定地址。发送数据缓冲区需指向以太网数据帧,发送套接字地址为struct sockaddr_ll结构,具体可以参考链接:套接字地址
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
sockfd:原始套接字描述符
buf:发送缓冲区,以太网数据
len:发送数据长度
flags:标识
dest_addr:发送套接字地址,struct sockaddr_lladdr_len:发送套接字地址长度
返回值:
成功:返回发送数据字节数
失败:返回0或者-1,并设置errno
5.3 接收数据
接收数据采用recvfrom函数,如果需要知道接收地址,可以将src_addr转换为struct sockaddr_ll结构。
#include <sys/types.h>
#include <sys/socket.h>ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数:
sockfd:原始套接字描述符
buf:接收缓冲区
len:接收数据长度
flags:标识
dest_addr:接收套接字地址,struct sockaddr_lladdr_len:接收套接字地址长度
返回值:成功:返回接收数据字节数失败:返回0或者-1,并设置errno
6.UDP示例代码
6.1 服务端代码
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <linux/in.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#define IP_HDRLEN (20)
#define UDP_HDRLEN (8)
#define MAX_BUF_SIZE (1500)
void print_buf(char *msg, const uint8_t *buf, uint32_t len) {
printf("%s\n", msg);
for (int i = 0; i < len; i++) {
printf("%02x%s", buf[i], ((i + 1) % 16) ? " ": "\n");
}
printf("\n");
}
void print_addr(struct sockaddr_ll *sll) {
printf("family:%u, protocol:%u, ifindex:%d, hatype:%u, pkttype:%u, halen:%u\n",
sll->sll_family,
sll->sll_protocol,
sll->sll_ifindex,
sll->sll_hatype,
sll->sll_pkttype,
sll->sll_halen);
printf("addr:");
for (int i = 0; i < 8; i++) {
printf("%02x", sll->sll_addr[i]);
}
printf("\n");
}
int parse_pack(char *msg, char *buf) {
struct ethhdr *eh = (struct ethhdr *)buf;
if (ntohs(eh->h_proto) != ETH_P_IP) {
perror("recv not ip packet error");
return -1;
}
struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN);
printf("ip protocol:%d\n", iph->protocol);
if (iph->protocol == IPPROTO_UDP) {
printf("recv udp packet\n");
struct udphdr *uh = (struct udphdr *)(buf + ETH_HLEN + sizeof(struct iphdr));
printf("%s eth type:%04x, ip protocol:%u, %s:%u->%s:%u %s\n",
msg,
ntohs(eh->h_proto),
iph->protocol,
inet_ntoa((struct in_addr){.s_addr = iph->saddr}),
ntohs(uh->uh_sport),
inet_ntoa((struct in_addr){.s_addr = iph->daddr}),
ntohs(uh->uh_dport),
buf + ETH_HLEN + sizeof(struct iphdr) + sizeof(struct udphdr));
} else if (iph->protocol == IPPROTO_TCP) {
printf("recv tcp packet\n");
} else if (iph->protocol == IPPROTO_ICMP) {
printf("recv icmp packet\n");
} else {
printf("recv other pack\n");
}
return 0;}
bool isvalid(const char *buf) {
struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN);
struct udphdr *uh = (struct udphdr *)(buf + ETH_HLEN + sizeof(struct iphdr));
if (ntohs(uh->uh_sport) == 22 || ntohs(uh->uh_dport) == 22) return false;
return true;
}
int main(int argc , char *argv[]) {
int ret;
int sockfd;
char recv_buf[MAX_BUF_SIZE] = {0};
struct sockaddr_ll peer;
socklen_t peerlen = 0;
sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
if (sockfd == -1) {
perror("socket error");
return -1;
}
while(1) {
memset(recv_buf, 0, MAX_BUF_SIZE);
memset(&peer, 0, sizeof(peer));
ret = recvfrom(sockfd, recv_buf, MAX_BUF_SIZE, 0, (struct sockaddr *)&peer, &peerlen);
if (ret <= 0) {
printf("recvfrom ret:%d error\n", ret);
continue;
} else {
if (!isvalid(recv_buf)) continue;
print_buf("recv buf:", (uint8_t *)recv_buf, ret > MAX_BUF_SIZE ? MAX_BUF_SIZE : ret);
print_addr(&peer);
parse_pack("recv packet", recv_buf);
}
}
return 0;}
6.2 客户端代码
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <linux/in.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
uint8_t src_mac[ETH_ALEN] = {0x00,0x0c,0x29,0xcd,0x32,0x63};uint8_t nexthop_mac[ETH_ALEN] = {0x00,0x0c,0x29,0xfb,0xc6,0x41};
#define SRC_IP "192.168.0.137"
#define DST_IP "192.168.0.140"
#define SPORT (1234)
#define DPORT (5678)
#define IP_HDRLEN (20)
#define UDP_HDRLEN (8)
#define MAX_BUF_SIZE (1500)
#define TEST_STRING "l2 udp test"
uint16_t ip_checksum (uint16_t *buf, int size) {
int count = size;
register uint32_t sum = 0;
uint16_t answer = 0;
while (count > 1) {
sum += *(buf++);
count -= 2;
}
if (count > 0) {
sum += *(uint8_t *) buf;
}
while (sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}
answer = ~sum;
return (answer);}
void print_buf(char *msg, const uint8_t *buf, uint32_t len) {
printf("%s\n", msg);
for (int i = 0; i < len; i++) {
printf("%02x%s", buf[i], ((i + 1) % 16) ? " ": "\n");
}
printf("\n");
}
uint32_t create_pack(char *buf, const char *payload, uint32_t payload_len) {
struct ethhdr *eh = (struct ethhdr *)buf;
memcpy(eh->h_dest, nexthop_mac, ETH_ALEN);
memcpy(eh->h_source, src_mac, ETH_ALEN);
eh->h_proto = htons(ETH_P_IP);
struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN);
iph->ihl = IP_HDRLEN / sizeof(uint32_t);
iph->version = 4;
iph->tos = 0;
iph->tot_len = htons(IP_HDRLEN + UDP_HDRLEN + payload_len);
iph->id = htons(0);
iph->frag_off = htons(0);
iph->ttl = 255;
iph->protocol = IPPROTO_UDP;
iph->saddr = inet_addr(SRC_IP);
iph->daddr = inet_addr(DST_IP);
iph->check = 0;
iph->check = ip_checksum((uint16_t *)iph, IP_HDRLEN);
struct udphdr *uh = (struct udphdr *)(buf + ETH_HLEN + sizeof(struct iphdr));
uh->uh_sport = htons(SPORT);
uh->uh_dport = htons(DPORT);
uh->uh_ulen = htons(UDP_HDRLEN + payload_len);
uh->uh_sum = 0;
memcpy(buf + ETH_HLEN + sizeof(struct iphdr) + sizeof(struct udphdr), payload, payload_len);
return ETH_HLEN + IP_HDRLEN + UDP_HDRLEN + payload_len;}
int main(int argc , char *argv[]) {
int ret;
int sockfd;
char send_buf[MAX_BUF_SIZE] = {0};
struct sockaddr_ll local;
struct sockaddr_ll peer;
sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
if (sockfd == -1) {
perror("socket error");
return -1;
}
#if 0 bzero(&local, sizeof(struct sockaddr_ll));
local.sll_family = PF_PACKET;
local.sll_protocol = htons(ETH_P_IP);
local.sll_ifindex = 2;
ret = bind(sockfd, (struct sockaddr *)&local, sizeof(local));
if (ret == -1) {
perror("bind error");
return -1;
}
#endif
#if 1 bzero(&peer, sizeof(struct sockaddr_ll));
peer.sll_family = PF_PACKET;
peer.sll_protocol = htons(ETH_P_IP);
peer.sll_ifindex = 2;
peer.sll_hatype = ARPHRD_ETHER;
peer.sll_pkttype = PACKET_OTHERHOST;
peer.sll_halen = ETH_ALEN;
memcpy(peer.sll_addr, nexthop_mac, ETH_ALEN);
#endif
uint32_t slen = create_pack(send_buf, TEST_STRING, strlen(TEST_STRING));
while(1) {
ret = sendto(sockfd, send_buf, slen, 0, (struct sockaddr *)&peer, sizeof(peer));
//ret = send(sockfd, send_buf, slen, 0);
if (ret <= 0) {
printf("sendto ret:%d, errno:%d(%s)\n", ret, errno, strerror(errno));
} else {
print_buf("send buf:", (uint8_t *)send_buf, slen > MAX_BUF_SIZE ? MAX_BUF_SIZE : slen);
}
sleep(1);
}
return 0;}
