• lv8 嵌入式开发-网络编程开发 19 原始套接字


    目录

    1 链路层原始套接字用法

    1.1 利用原始套接字实现类似wireshark的功能

    1.2 利用原始套接字实现ping命令

    2 网络层原始套接字用法

    2.1 TCP原始套接字用法


    1 链路层原始套接字用法

    1. Linux中的原始套接字(Raw Socket)是一种高级套接字类型,允许应用程序直接访问网络协议栈,发送和接收自定义的网络数据包。使用原始套接字,你可以实现各种网络工具、网络协议分析和网络攻防等功能。
    2. 下面是使用原始套接字的一般步骤:
    3. 1 创建原始套接字:通过调用socket()函数创建一个原始套接字。指定参数AF_PACKET表示使用Packet套接字族,参数SOCK_RAW表示使用原始套接字类型。
    4. int sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    5. 2
    6. 绑定套接字到网络设备:使用bind()函数将套接字绑定到特定的网络设备上。你需要指定网络设备的名称或索引。可以使用ifconfig命令或者ioctl()函数来获取设备名称和索引。
    7. struct sockaddr_ll sa;
    8. memset(&sa, 0, sizeof(sa));
    9. sa.sll_family = AF_PACKET;
    10. sa.sll_protocol = htons(ETH_P_ALL);
    11. sa.sll_ifindex = if_nametoindex("eth0");
    12. bind(sockfd, (struct sockaddr*)&sa, sizeof(sa));
    13. 3 构造自定义数据包:使用结构体来构造自定义的网络数据包,如以太网帧(struct ethhdr)和IP数据报头(struct iphdr)。在构造数据包时,你需要设置正确的协议头信息和有效载荷。
    14. struct ethhdr ether_header;
    15. struct iphdr ip_header;
    16. char payload[100];
    17. // 构建以太网帧、IP数据报头和有效载荷
    18. // ...
    19. 4 发送和接收数据包:使用sendto()函数发送数据包,指定目标地址和端口。使用recvfrom()函数接收数据包,获取发送者的信息。
    20. // 发送数据包
    21. sendto(sockfd, ðer_header, sizeof(ether_header) + sizeof(ip_header) + payload_length, 0, (struct sockaddr*)&sa, sizeof(sa));
    22. // 接收数据包
    23. struct sockaddr_ll sa_recv;
    24. socklen_t sa_len = sizeof(sa_recv);
    25. recvfrom(sockfd, recv_buffer, sizeof(recv_buffer), 0, (struct sockaddr *)&sa_recv, &sa_len);
    26. 5 关闭套接字:当你完成使用原始套接字时,记得使用close()函数关闭套接字。
    27. close(sockfd);
    28. 请注意,在使用原始套接字时需要具有足够的权限,通常需要以root用户身份运行程序。此外,使用原始套
    29. 接字需要对网络协议有深入的了解,并且要小心操作,以避免对网络造成不良影响。

    1.1 利用原始套接字实现类似wireshark的功能

    tcp_all.c 接收所用链路层的数据包,但是以TCP为主(实现功能类似wireshark)

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #define MTU 1500
    11. int main()
    12. {
    13. /* 定义变量 */
    14. int sockfd, len;
    15. uint8_t buf[MTU]={};
    16. uint16_t ether_type;
    17. /*
    18. struct iphdr是一个定义在中的IP包头结构体,用于表示IPv4协议的包头信息。
    19. struct tcphdr是一个定义在中的TCP包头结构体,用于表示TCP协议的包头信息。
    20. struct ether_header是一个定义在中的以太网帧头结构体,用于表示以太网帧头的信息。
    21. */
    22. struct iphdr *iph; //IP包头
    23. struct tcphdr *tcph;//TCP包头
    24. struct ether_header *eth;
    25. /* 创建一个链路层原始套接字 */
    26. if( (sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) ) < 0){
    27. perror("socket");
    28. return 0;
    29. }
    30. printf("sockfd = %d\n", sockfd);
    31. /* 接收(只接收TCP数据协议)并处理IP数据报 */
    32. while(1)
    33. {
    34. /* 接收包含TCP协议的IP数据报 */
    35. len = recvfrom(sockfd, buf, sizeof(buf),0,NULL,NULL);
    36. eth = (struct ether_header *)buf;
    37. ether_type = htons(eth->ether_type);
    38. switch(ether_type){
    39. case ETHERTYPE_IP:
    40. printf("IP协议\n");
    41. break;
    42. case ETHERTYPE_ARP:
    43. printf("ARP协议\n");
    44. break;
    45. case ETHERTYPE_LOOPBACK:
    46. printf("loop back\n");
    47. break;
    48. default:
    49. printf("其他协议 %x\n",eth->ether_type);
    50. }
    51. if(ether_type != ETHERTYPE_IP) //如果是IP包才往下执行
    52. continue;
    53. /* 打印源IP和目的IP */
    54. iph = (struct iphdr *)(buf+14);
    55. if(iph->protocol != IPPROTO_TCP) //如果是ip包并且是TCP的包才往下执行并打印
    56. continue;
    57. printf("源IP:%s\n",inet_ntoa(*(struct in_addr *)&iph->saddr) );
    58. printf("目的IP%s\n",inet_ntoa(*(struct in_addr *)&iph->daddr) );
    59. /* 打印TCP包头的源端口号和目的端口号 */
    60. tcph = (struct tcphdr *)(buf+14+iph->ihl*4);
    61. printf("%hu--->", ntohs(tcph->source));
    62. printf("%hu\n", ntohs(tcph->dest));
    63. /* 打印TCP数据段的长度 */
    64. printf("TCP首部长度:%d\n", tcph->doff*4);
    65. }
    66. //关闭套接字
    67. close(sockfd);
    68. return 0;
    69. }

    运行效果: 

    后期改进,也可以更细的去打印各类协议,详细包内容。

    1.2 利用原始套接字实现ping命令

    send_ping.c

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. uint16_t SetChetSum(uint16_t *buf, int size);
    9. void Set_IcmpH(struct icmphdr *icmph, int size);
    10. #define MSG_SIZE 40
    11. int main(int argc, char *argv[])
    12. {
    13. /* 定义变量 */
    14. int fd;
    15. char buf[MSG_SIZE]={};
    16. if(argc < 3)
    17. {
    18. printf("%s \n", argv[0]);
    19. exit(EXIT_FAILURE);
    20. }
    21. /* 创建原始套接字 */
    22. if((fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0)
    23. {
    24. perror("socket");
    25. return 0;
    26. }
    27. struct sockaddr_in sin;
    28. sin.sin_family = AF_INET;
    29. sin.sin_port = htons(atoi(argv[2]));
    30. if(inet_aton(argv[1], &sin.sin_addr) == 0)
    31. {
    32. printf("Invalid address\n");
    33. exit(EXIT_FAILURE);
    34. }
    35. Set_IcmpH((struct icmphdr *)&buf, MSG_SIZE);
    36. sendto(fd, buf, MSG_SIZE, 0, (struct sockaddr *)&sin, sizeof(sin));
    37. close(fd);
    38. return 0;
    39. }
    40. void Set_IcmpH(struct icmphdr * icmph, int size)
    41. {
    42. static int seq;
    43. icmph->type = ICMP_ECHO; //icmp宏定义
    44. icmph->code = 0; //每个icmp的编号,发一次,简化逻辑
    45. icmph->un.echo.id = getpid();
    46. icmph->un.echo.sequence = seq++;
    47. icmph->checksum = SetChetSum((uint16_t *)icmph, MSG_SIZE);//校验和
    48. }
    49. uint16_t SetChetSum(uint16_t *buf, int size)
    50. {
    51. uint32_t checksum = 0;
    52. while(size > 1)
    53. {
    54. checksum += *buf++;
    55. size -= 16;
    56. }
    57. if(size)
    58. checksum += *(unsigned char *)buf;
    59. checksum = (checksum >> 16) + (checksum & 0xffff);
    60. checksum += (checksum >> 16);
    61. return (uint16_t)(~checksum);
    62. }

    实验效果

    2 网络层原始套接字用法

    原始套接字(Raw Socket)和标准套接字(Standard Socket)是在网络编程中使用的两种不同类型的套接字。

    1. 原始套接字(Raw Socket): 原始套接字允许应用程序直接访问网络协议栈,可以发送和接收原始的网络数据包。使用原始套接字,开发者可以自定义协议头部信息,以及对底层网络数据包进行更底层的控制和处理。它提供了更高级别的网络访问权限,但需要更高的权限级别(如管理员或特权用户)来使用。(整个数据包,包括IP的包头、TCP包头)

    2. 标准套接字(Standard Socket): 标准套接字是通过操作系统提供的网络套接字API进行通信的一种方式,使用TCP(Transmission Control Protocol)或UDP(User Datagram Protocol)等传输协议。标准套接字隐藏了底层网络协议的细节,提供了更高层次的抽象和简化的网络编程接口,使得开发人员可以更方便地进行网络通信和应用程序开发。

    区别:

    • 功能差异:原始套接字允许直接处理和操作底层的网络数据包,可以实现更高级别的网络控制和定制化,而标准套接字则提供了更高层的、抽象化的网络编程接口,适合日常的网络通信需求。
    • 权限要求:原始套接字需要更高的权限级别来访问和使用,通常需要管理员或特权用户的权限。而标准套接字可以由一般用户进行操作。
    • 使用场景:原始套接字通常用于进行网络嗅探、网络扫描、包分析等高级网络操作,以及特定网络协议的开发和研究。标准套接字则广泛应用于一般的网络通信场景,如客户端和服务器之间的数据传输。

    在实际应用中,一般情况下使用标准套接字就能满足大部分的网络通信需求,而原始套接字主要由网络工具和专业开发者使用。

    2.1 TCP原始套接字用法

    tcp.c (主要用于网络工具的开发)

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #define MTU 1500
    11. int main()
    12. {
    13. /* 定义变量 */
    14. int sockfd = -1, len, datalen, i;
    15. uint8_t buf[MTU]={}, *data;
    16. struct iphdr *iph; //IP包头
    17. struct tcphdr *tcph;//TCP包头
    18. struct winsize size;
    19. /* 创建一个原始套接字 */
    20. if( (sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP) ) < 0)
    21. {
    22. perror("socket");
    23. return 0;
    24. }
    25. printf("sockfd = %d\n", sockfd);
    26. /* 接收(只接收TCP数据协议)并处理IP数据报 */
    27. while(1)
    28. {
    29. /* 接收包含TCP协议的IP数据报 */
    30. len = recvfrom(sockfd, buf, sizeof(buf),0,NULL,NULL);
    31. printf("IP数据报长度 = %d\n", len);
    32. /* 打印源IP和目的IP */
    33. iph = (struct iphdr *)buf; //首地址是一样的,强转成ip数据报结构体
    34. printf("源IP:%s",inet_ntoa(*(struct in_addr *)&iph->saddr) );
    35. printf("目的IP%s\n",inet_ntoa(*(struct in_addr *)&iph->daddr) );
    36. /* 打印TCP包头的源端口号和目的端口号 */
    37. tcph = (struct tcphdr *)(buf+iph->ihl*4); //强转buf中tcp部分
    38. printf("%hu--->", ntohs(tcph->source));
    39. printf("%hu\n", ntohs(tcph->dest));
    40. /* 打印TCP数据段的长度 */
    41. printf("TCP首部长度:%d\n", tcph->doff*4);
    42. if(iph->ihl*4+tcph->doff*4 < len) { //tcp数据报里面有没有放数据
    43. data = buf + iph->ihl*4 + tcph->doff*4;
    44. datalen = len - iph->ihl*4 + tcph->doff*4;
    45. ioctl(STDIN_FILENO,TIOCGWINSZ,&size); //terminal 结构体
    46. for(i = 0; i < size.ws_col; i++) //显示一行 =
    47. putchar('=');
    48. putchar('\n');
    49. printf("TCP数据字符:\n");
    50. for(i = 0; i < size.ws_col; i++)
    51. putchar('=');
    52. putchar('\n');
    53. for(i = 0; i < datalen-1; i++) {
    54. printf("%c", data[i]);
    55. }
    56. for(i = 0; i < size.ws_col; i++)
    57. putchar('=');
    58. putchar('\n');
    59. printf("TCP数据16进制:\n");
    60. for(i = 0; i < size.ws_col; i++)
    61. putchar('=');
    62. putchar('\n');
    63. for(i = 0; i < datalen-1; i++){
    64. printf("%x ", data[i]);
    65. }
    66. putchar('\n');
    67. for(i = 0; i < size.ws_col; i++)
    68. putchar('=');
    69. putchar('\n');
    70. }
    71. }
    72. //关闭套接字
    73. close(sockfd);
    74. return 0;
    75. }

    执行效果

     IP协议包头结构体

    /usr/include/linux/ip.h

    1. struct iphdr {
    2. #if defined(__LITTLE_ENDIAN_BITFIELD) //小段模式
    3. __u8 ihl:4, //位域操作,两个合起来占8个字节
    4. version:4;
    5. #elif defined (__BIG_ENDIAN_BITFIELD) //大端模式
    6. __u8 version:4,
    7. ihl:4;
    8. #else
    9. #error "Please fix "
    10. #endif
    11. unsigned char tos; // 服务类型
    12. unsigned short tot_len; // 总长度
    13. unsigned short id; // 标识
    14. unsigned short frag_off; // 分片偏移
    15. unsigned char ttl; // 存活时间
    16. unsigned char protocol; // 协议类型
    17. unsigned short check; // 校验和
    18. unsigned int saddr; // 源IP地址
    19. unsigned int daddr; // 目标IP地址
    20. // 其他字段...
    21. };

     /usr/include/linux/in.h

    1. enum {
    2. IPPROTO_IP = 0, /* Dummy protocol for TCP */
    3. #define IPPROTO_IP IPPROTO_IP
    4. IPPROTO_ICMP = 1, /* Internet Control Message Protocol */
    5. #define IPPROTO_ICMP IPPROTO_ICMP
    6. IPPROTO_IGMP = 2, /* Internet Group Management Protocol */
    7. #define IPPROTO_IGMP IPPROTO_IGMP
    8. IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94) */
    9. #define IPPROTO_IPIP IPPROTO_IPIP
    10. IPPROTO_TCP = 6, /* Transmission Control Protocol */
    11. #define IPPROTO_TCP IPPROTO_TCP
    12. IPPROTO_EGP = 8, /* Exterior Gateway Protocol */
    13. #define IPPROTO_EGP IPPROTO_EGP
    14. IPPROTO_PUP = 12, /* PUP protocol
    15. ...
    16. }

    如何找到这些结构体

    grep -irn "xxx" /usr/inclue/linux
    

  • 相关阅读:
    【小5聊】通过Task例子来简单理解下串行和并行如何节省执行时间
    YOLOv5/YOLOv7损失函数改进:SlideLoss创新升级,结合IOU动态调整困难样本的困难程度,提升小目标、遮挡物性能
    电脑图片无损放大怎么操作?怎么无损放大图片?
    模式识别与人工智能(程序与算法)系列讲解 - 总目录
    18.C++中模板参数类型推断与引用
    Golang 快速上手 (3)
    ElasticSearch系列——分词器
    npm常用命令详解
    双十一购物狂欢节准备好买什么了吗?双十一这些好物不能错过
    EIP-3523:半同质代币介绍
  • 原文地址:https://blog.csdn.net/m0_60718520/article/details/133895762