当前位置: 首页 > 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.dtcms.com/a/286438.html

相关文章:

  • 4644电源管理芯片在微波射频组件中的技术优势与国产化实践
  • 《5分钟开发订单微服务!飞算JavaAI实战:IDEA插件安装→空指针修复→K8s部署全流程》
  • 高并发、高性能、高可用
  • LeetCode热题100【第4天】
  • openinstall九周年:聚焦安全防御,护航业务持续增长
  • 40+个常用的Linux指令——上
  • 【ACL系列论文写作指北19-科研中角色定位与自我认知】-如何在一篇论文中摆正自己的位置
  • 由于热爱,我选PGCE专家学习
  • 自动化测试面试中常见的问题
  • 从碎片设备到全球算力:Sollong引领AI时代的基础资源革命
  • Mysql定位慢查询
  • 内存泄漏系列专题分析之二十九:高通相机CamX--Android通用GPU内存分配和释放原理
  • 主流编程语言全景图:从Python到Rust的深度解析
  • 优先算法——专题九:链表
  • vc配置使用预编译
  • Android性能优化
  • 搜广推校招面经九十五
  • 【PTA数据结构 | C语言版】根据后序和中序遍历输出前序遍历
  • 更适合后端宝宝的前端三件套之CSS
  • 域名备案的注册地址怎么更改
  • 基于pandas,按日期时间排序,计算每个连续段的开始时间、结束时间,以及时长
  • Selenium自动化浏览器操作指南
  • Deep Multi-scale Convolutional Neural Network for Dynamic Scene Deblurring 论文阅读
  • DTW算法解决时序问题的解析实践
  • 【C++】std::exchange 原子性 返回值优化RVO
  • js基本数据类型之字符串类型
  • Python 数据分析与可视化:从基础到进阶的技术实现与优化策略
  • svn如何设置忽略文件夹或者文件的提交
  • PyQt5信号与槽(信号与槽的高级玩法)
  • 四足机器人远程视频与互动控制的全链路方案