原始套接字编程,图文并茂解决网络编程烦恼

本文详细介绍了网络层原始套接字(SOCK_RAW)的概念,与普通套接字的区别,以及如何通过编程创建、设置选项、发送和接收数据。示例代码展示了如何使用原始套接字处理UDP数据包,包括组装和解析IP及UDP头部。
摘要由CSDN通过智能技术生成

目录

1. 什么是原始套接字?

2. 原始套接字和普通套接字的区别?

图 1

3. 原始套接字工作流程

​编辑图 2

4. 原始套接字编程基础

5. 原始套接字UDP示例代码



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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

物联网心球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值