当前位置: 首页 > news >正文

Linux下使用原始socket收发数据包

在Linux系统中,使用非原始的socket,可以收发TCP或者UDP等网络层数据包。如果要处理网络层以下的数据包,比如ICMP、ARP等,或者更底层,比如链路层数据包,就得使用原始socket了。

创建socket

创建socket要使用socket函数,socket函数的原型为:

#include <sys/types.h>  
#include <sys/socket.h>  int socket(int domain, int type, int protocol);

其中,domain比较常用的值有:

  • AF_UNIX Unix本地通讯
  • AF_INET IPv4网络协议
  • AF_INET6 IPv6网络协议
  • AF_NETLINK 内核用户界面设备
  • AF_PACKET 底层包设备

我们要处理网络层以下的数据包,就得使用AF_PACKET

type比较常用的值有:

  • SOCK_STREAM 用于面向字节流的协议,如TCP
  • SOCK_DGRAM 用于面向数据报的协议,如UDP
  • SOCK_RAW 用于非处理的原始数据包

我们这里要使用SOCK_RAW

protocol是要使用的网络协议。

如下代码,创建一个原始socket:

#include <sys/types.h>
#include <sys/socket.h>int raw_socket_new () {int fd;fd = socket (PF_PACKET, SOCK_RAW, IPPROTO_RAW);if (fd < 0){fprintf (stderr, "socket error: %s\n", strerror (errno));}return fd;

绑定网口

如果我们处理非原始的数据包,可以使用网络层绑定IP,或者连接IP(其实socket也会自动绑定IP),协议栈会自动根据IP把数据包发往特定的网络接口,不需要绑定网口。

我们通过原始socket发送底层数据包,就需要绑定网口了。

绑定网口的方法很简单,先使用if_nametoindex函数,找到对应网口名的编号,之后设置到struct sockaddr_ll结构中,调用bind函数就行了。

`if_nametoindex`的原型为:

#include <net/if.h>unsigned int if_nametoindex(const char *ifname);

struct sockaddr_ll的定义为:

#include <linux/if_packet.h>struct sockaddr_ll {  unsigned short  sll_family;  __be16          sll_protocol;  int             sll_ifindex;  unsigned short  sll_hatype;  unsigned char   sll_pkttype;  unsigned char   sll_halen;  unsigned char   sll_addr[8];  
};

以下函数,就把给定的网络接口,绑定到了给定的socket上:

#include <net/if.h>
#include <linux/if_packet.h>int raw_socket_bind(int fd, const char *eth) {struct sockaddr_ll sa;memset (&sa, 0, sizeof (struct sockaddr_ll));// 原始数据包sa.sll_family = AF_PACKET;// 自定义二层协议sa.sll_protocol = htons (CUSTOM_TYPE);sa.sll_ifindex = if_nametoindex (eth);if (bind (jdpdk->fd, (struct sockaddr *)&sa, sizeof (struct sockaddr_ll))!= 0){fprintf (stderr, "bind error: %s\n", strerror (errno));return -1;}return 0;
}

二层包结构

socket创建成功,绑定了网口,就可以发送自定义的二层数据包了。

根据TCP/IP标准,二层的数据包包头为14个字节,即6字节的目标MAC地址、6字节的源MAC地址再加2字节的包类型。

net/ethernet.h中,结构定义为:

struct ether_header  
{uint8_t  ether_dhost[ETH_ALEN]; uint8_t  ether_shost[ETH_ALEN];uint16_t ether_type;
} __attribute__ ((__packed__));

在我们使用网口发送的时候,源MAC地址可以设成全0,但是目标MAC地址,必须设置。

我们可以实现一个组包函数,把给定的数据,设上预订的目标地址:

int raw_encode (const char *dst_mac, unsigned short type, const char *data, size_t data_len, char *packet, size_t packet_len) {assert (14 + data_len <= packet_len);memcpy (packet, dst_mac, 6);memset (packet + 6, 0, 6);* (unsigned short *) (packet + 12) = htons (type);memcpy (packet + 14, data, data_len);return 14 + data_len;
}

发送

发送函数就比较简单,跟网络层的发送没有区别。

int raw_send (int fd, const char *data, int data_len) {int ret;ret = send (fd, data, data_len, 0);  if (ret != data_len)  {  fprintf (stderr, "send error: %s\n", strerror (errno));       }return ret;
}

接收

接收函数也同样,只是记得接收成功以后,偏移14字节(即二层包头以后)才是我们的应用层数据:

int raw_recv (int fd, char *packet, size_t packet_len) {int ret;ret = recv (fd, packet, packet_len, 0);return ret;
}

原始socket示例:arp组包

根据TCP/IP协议,当我们网络层使用TCP、UDP或者ICMP等使用IP地址作为目标的数据包时,协议栈要查询本机的arp表,找到IP地址对应的MAC地址。

如果查询失败,就会发送arp请求,通过arp响应来得到目标MAC地址。

所以,arp协议有两种数据包:一种是arp请求,一种是arp响应。对应到arp结构中,就是操作码的值为1或者2。

linux/if_arp.h中,有arp包的包头结构:

struct arphdr {  __be16          ar_hrd;__be16          ar_pro;unsigned char   ar_hln;unsigned char   ar_pln;__be16          ar_op;  
};

在arp的包体中,分别是发送MAC、发送IP、目标MAC与目标IP。

  • 当arp请求的时候,设置arp包头中的操作码为1,另外设置发送MAC(即本机MAC)与目标IP,发送IP与目标MAC置空。

当arp响应的时候,设置arp包头中的操作码为2,另外设置发送MAC(即本机MAC)、发送IP(即本机IP)、目标MAC(即请求时的发送MAC),目标IP置空。

我们写一个组包函数:

#include <net/ethernet.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#include <memory.h>void
buf_encode_arp (uint8_t *buf, uint16_t opcode, const uint8_t *source_mac,  const uint8_t *dst_mac, unsigned long sip, unsigned long dip)  
{struct ether_header *hdr;  hdr = (struct ether_header *)buf;  if (source_mac)  {  memcpy (hdr->ether_shost, source_mac, sizeof hdr->ether_shost);}// arp请求的时候,二层目标MAC地址填全0xFF,否则填全0memset (hdr->ether_dhost, opcode == 1 ? 0xFF : 0, sizeof hdr->ether_dhost);if (dst_mac)  {  memcpy (hdr->ether_dhost, dst_mac, sizeof hdr->ether_dhost);}hdr->ether_type = htons (ETHERTYPE_ARP);struct ether_arp *arp = (struct ether_arp *)(hdr + 1);// 设置arp包头arp->ea_hdr.ar_hrd = htons (1);arp->ea_hdr.ar_pro = htons (0x800);arp->ea_hdr.ar_hln = 6;arp->ea_hdr.ar_pln = sizeof (uint32_t);arp->ea_hdr.ar_op = htons (opcode);// 根据输入参数,设置arp包体memset (arp->arp_sha, 0, sizeof arp->arp_sha);if (source_mac)  {  memcpy (arp->arp_sha, source_mac, sizeof arp->arp_sha);}  memset (arp->arp_tha, 0, sizeof arp->arp_tha);if (dst_mac)  {  memcpy (arp->arp_tha, dst_mac, sizeof arp->arp_tha);}memcpy (&arp->arp_spa, &sip, 4);memcpy (&arp->arp_tpa, &dip, 4);
}
http://www.xdnf.cn/news/1145503.html

相关文章:

  • LatentSync: 一键自动生成对嘴型的视频
  • 域名WHOIS信息查询免费API使用指南
  • 笔试——Day12
  • Java Map 集合详解:从基础语法到实战应用,彻底掌握键值对数据结构
  • 爬虫小知识(二)网页进行交互
  • 持续同调文章阅读(四)
  • 二刷 黑马点评 附近商户
  • Diffusion-VLA 中的 Reasoning Token 注入机制解析:语言推理如何控制扩散模型?
  • 深入解析文本分类技术全景:从特征提取到深度学习架构
  • Python 之地址编码识别
  • 《Web安全之深度学习实战》读书笔记总结
  • 去中心化交易所(DEX)深度解析:解码行业头部项目
  • 堆的实现,堆排序,咕咕咕
  • 【RK3576】【Android14】开发板概述
  • Node.js链接MySql
  • 数据结构-3(双向链表、循环链表、栈、队列)
  • 进阶数据结构:红黑树
  • uniapp 动态控制横屏(APP 端)
  • spring boot 实战之分布式锁
  • linux 的list_for_each_entry
  • 数字化转型:概念性名词浅谈(第三十一讲)
  • 怎么判断一个对象是不是vue的实例
  • STM32-CAN
  • 根据用户id自动切换表查询
  • STM32 RTOS 开发基础:从任务管理到同步机制的全面解析
  • Git 团队协作完全指南:从基础到高级应用
  • Docker面试题
  • 饿了么app 抓包 hook
  • HTTP 性能优化:五条建议
  • 控制鼠标和键盘