数据链路层原始套接字

数据链路层原始套接字

目录

数据链路层原始套接字 图1


1.什么是数据链路层原始套接字?

数据链路层原始套接字是Linux内核提供的一种套接字,该套接字可以让用户程序直接发送和接收以太网数据,用户程序发送和接收的数据包必须是完整的以太网数据,内核不会再处理该数据而只是做一些简单的接收和发送工作。数据链路层原始套接字在实际环境中有很多的应用,比如可以用来监听和抓包,可以对数据包进行修改和定制等等,很多的开源软件也用到了原始套接字。 基于原始套接字这样的特性,可能会被一些不法分子利用,所以大家在使用原始套接字的时候,需要注意网络安全问题。

2.常用套接字的区别

主要区别:处理数据网络层级不一样。

  • 普通套接字:

普通套接字包括(SOCK_STREAM)和(SOCK_DGRAM)只能处理TCP,UDP等负载数据,不能对头部进行处理。

  • 网络层原始套接字:

网络层原始套接字(AF_INET,SOCK_RAW)不仅能对负载数据进行处理,还能处理IP,TCP,UDP,ICMP等头部,但是不能处理以太网头部信息。

  • 数据链路层原始套接字:

数据链路层原始套接字(PF_PACKET, SOCK_RAW)能处理以太网数据帧所有的信息。

数据链路层原始套接字 图2

图 1 套接字区别

## 3.发送和接收原理
  • 发送原理:

用户程序必须组装好整个以太网数据帧,才能进行正常发送,所以用户程序得知道以下信息:源MAC地址,目的MAC地址,源IP地址,目的IP地址,源端口,目的端口等。

  • 接收原理:

原始套接字将接收所有以太网数据帧,除非用户创建socket的时候对协议进行了过滤设置,比如说用户只想收到IP数据包,则协议需要设置为ETH_P_IP,网卡驱动接收到数据后,会将数据包传入Linux内核协议栈,内核协议栈会克隆一份数据包给原始套接字,另外一份数据包将继续走内核路径。

4.工作流程

数据链路层原始套接字 图3

图 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;}
← 返回文章列表