目录
1. 什么是原始套接字?
原始套接字(sock_raw)分为链路层原始套接字和网络层原始套接字,今天讨论的是网络层原始套接字,后续原始套接字指的是网络层原始套接字。网络层原始套接字提供了一个方法或者说工具,用户可以使用该工具完成IP层数据包的组装,包括IP头部,传输层头部(TCP头部,UDP头部)和负载数据的组装,原始套接字可以跳过内核的数据包组包和分包流程,直接发送和接收数据包。
2. 原始套接字和普通套接字的区别?
原始套接字和普通套接字最大的区别在于处理数据的网络层级不一样。普通套接字(SOCK_STREAM)和(SOCK_DGRAM)只能处理传输层数据,普通套接字只能将传输层负载数据通过API接口传入Linux内核,由内核完成数据组装。原始套接字(SOCK_RAW)能处理网络层数据,不仅能处理负载数据,而且还能处理网络头部,如IP头部,TCP头部,UDP头部等。
图 1
3. 原始套接字工作流程
图 2
4. 原始套接字编程基础
4.1创建原始套接字
原始套接字通过socket函数创建,domain参数设置AF_INET表示为IPv4网络层原始套接字,type参数设置为SOCK_RAW表示为原始套接字,protocol可以设置为只处理TCP,UDP,ICMP等协议。
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数:
domain:地址族,AF_INET
type:套接字类型,SOCK_RAW
protocol:
IPPROTO_UDP:只处理UDP数据
IPPROTO_TCP:只处理TCP数据
IPPROTO_ICMP:只处理ICMP数据
返回值:
成功:返回原始套接字文件描述符
失败:返回-1
4.2 设置IP_HDRINCL选项
IP_HDRINCL选项为头部包含选项(header include),通过设置该选项,可以决定用户程序是否需要处理IP头部信息,该选项只针对原始套接字发送有效。
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
参数:
sockfd:原始套接字文件描述符
level:选项级别,IPPROTO_IP
optname:选项名,IP_HDRINCL
optval:选项,int on=1开启,on=0关闭
optlen:选项长度
返回值:
成功:返回0
失败:返回-1
4.3 原始套接字发送数据
原始套接字发送函数通常为sendto函数,如果想使用send函数发送,需要调用connect函数指定目的地址。
#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:发送套接字地址
addr_len:发送套接字地址长度
返回值:
成功:返回发送数据字节数
失败:返回0或者-1,并设置errno
4.4 原始套接字接收数据
原始套接字接收函数通常为revfrom函数,如果想使用recv函数发送,需要调用connect函数指定目的地址。
#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:接收套接字地址
addr_len:接收套接字地址长度
返回值:
成功:返回接收数据字节数
失败:返回0或者-1,并设置errno
5. 原始套接字UDP示例代码
5.1 服务端代码
#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>
#define SRC_IP "127.0.0.1"
#define DST_IP "127.0.0.1"
#define SPORT (5678)
#define DPORT (1234)
#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 statistics(uint16_t sport, uint16_t dport, char *str) {
printf("sport:%u, dport:%u, str:%s\n", sport, dport, str);
}
void parse_pack(char *buf) {
uint16_t sport;
uint16_t dport;
struct iphdr *iph = (struct iphdr *)buf;
printf("recv ip type:%d\n", iph->protocol);
if (iph->protocol == IPPROTO_UDP) {
printf("recv UDP packet\n");
struct udphdr *uh = (struct udphdr *)(buf + sizeof(struct iphdr));
sport = ntohs(uh->uh_sport);
dport = ntohs(uh->uh_dport);
uint32_t offset = sizeof(struct iphdr) + sizeof(struct udphdr);
statistics(sport, dport, buf + offset);
} else if (iph->protocol == IPPROTO_ICMP) {
printf("recv ICMP packet\n");
} else {
}
}
int main(int argc , char *argv[]) {
int sockfd;
int ret;
char recv_buf[MAX_BUF_SIZE] = {0};
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
if (sockfd == -1) {
perror("socket error");
return -1;
}
struct sockaddr_in peer;
socklen_t peerlen = sizeof(peer);
while(1) {
memset(recv_buf, 0, MAX_BUF_SIZE);
ret = recvfrom(sockfd, recv_buf, MAX_BUF_SIZE, 0, (struct sockaddr *)&peer, &peerlen);
if (ret <= 0) {
printf("ret:%d, errno:%d(%s)\n", ret, errno, strerror(errno));
} else {
print_buf("recv buf:", (uint8_t *)recv_buf, ret > MAX_BUF_SIZE ? MAX_BUF_SIZE : ret);
printf("peer src:port->%s:%d\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));
parse_pack(recv_buf);
}
}
close(sockfd);
return 0;
}
5.2 客户端代码
#include <stdio.h>
#include <string.h>
#include <stdint.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>
#define SRC_IP "127.0.0.1"
#define DST_IP "127.0.0.1"
#define SPORT (1234)
#define DPORT (5678)
#define IP_HDRLEN (20)
#define UDP_HDRLEN (8)
#define MAX_BUF_SIZE (1500)
#define TEST_STRING "Hello world"
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");
}
void statistics(uint16_t sport, uint16_t dport, char *str) {
printf("sport:%u, dport:%u, str:%s\n", sport, dport, str);
}
uint32_t create_ip_pack(char *buf, const char *payload, uint32_t payload_len) {
struct iphdr *iph = (struct iphdr *)buf;
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 + 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 + sizeof(struct iphdr) + sizeof(struct udphdr), payload, payload_len);
return IP_HDRLEN + UDP_HDRLEN + payload_len;
}
uint32_t create_udp_pack(char *buf, const char *payload, uint32_t payload_len) {
struct udphdr *uh = (struct udphdr *)buf;
uh->uh_sport = htons(SPORT);
uh->uh_dport = htons(DPORT);
uh->uh_ulen = htons(UDP_HDRLEN + payload_len);
uh->uh_sum = 0;
memcpy(buf + sizeof(struct udphdr), payload, payload_len);
return UDP_HDRLEN + payload_len;
}
void parse_pack(char *buf) {
uint16_t sport;
uint16_t dport;
struct iphdr *iph = (struct iphdr *)buf;
struct udphdr *uh = (struct udphdr *)(buf + sizeof(struct iphdr));
sport = ntohs(uh->uh_sport);
dport = ntohs(uh->uh_dport);
uint32_t offset = sizeof(struct iphdr) + sizeof(struct udphdr);
statistics(sport, dport, buf + offset);
}
int main(int argc , char *argv[]) {
int sockfd;
char send_buf[MAX_BUF_SIZE] = {0};
char recv_buf[MAX_BUF_SIZE] = {0};
uint32_t slen = 1;
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
if (sockfd == -1) {
perror("socket error");
return -1;
}
int on = 1;
int ret = setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));
#if 0
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = inet_addr(SRC_IP);
local.sin_port = htons(SPORT);
ret = bind(sockfd, (struct sockaddr *)&local, sizeof(local));
if (ret == -1) {
close(sockfd);
perror("bind error");
return -1;
}
#endif
struct sockaddr_in peer;
bzero(&peer, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_addr.s_addr = inet_addr(DST_IP);
peer.sin_port = htons(DPORT);
#if 0
ret = connect(sockfd, (struct sockaddr *)&peer, sizeof(peer));
if (ret == -1) {
close(sockfd);
perror("connect error");
return -1;
}
#endif
if (on == 1) {
slen = create_ip_pack(send_buf, TEST_STRING, strlen(TEST_STRING));
} else {
slen = create_udp_pack(send_buf, TEST_STRING, strlen(TEST_STRING));
}
socklen_t addrlen = 0;
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);
}
close(sockfd);
return 0;
}