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.mfbz.cn/a/2617.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

cocosCreator2.4 Android 输入法遮挡

这里是 调用显示系统的输入法&#xff0c;然后在 Cocos2dxEditBox.java 创建UI,用于处理输入&#xff0c;这里可以看到会ui 会被系统的输入法遮挡&#xff0c;无法点击&#xff0c;是因为 计算ui位置时没有算上刘海区域&#xff0c;需要处理一下&#xff1a; private int getTo…

7.18 Java基础 |

以下内容&#xff0c;参考Java 教程 | 菜鸟教程&#xff0c;下边是我边看边记的内容&#xff0c;以便后续复习使用。 多态&#xff1a; 继承&#xff0c;接口就是多态的具体体现方式。生物学上&#xff0c;生物体或物质可以具有许多不同的形式或者阶段。 多态分为运行时多态&…

【Lua】闭包可能会导致的变量问题

先思考下面这个问题&#xff1a;local function counter()local count 0return function()count count 1return countend endlocal a counter() local b counter()print(a()) --> ? print(a()) --> ? print(b()) --> ? print(a()) --> ?输出结果&#xff…

网络基础12--可靠性概述及要求

一、可靠性基础概念定义可靠性&#xff08;Availability&#xff09; MTBF / (MTBF MTTR)MTBF&#xff08;平均无故障时间&#xff09;&#xff1a;衡量系统稳定性的指标&#xff08;如1年&#xff09;。MTTR&#xff08;平均修复时间&#xff09;&#xff1a;衡量故障响应与…

【Dv3Admin】菜单管理集成阿里巴巴自定义矢量图标库

图标选择是后台管理系统中高频功能。相比用 Element UI、Ant Design 等自带的 icon 集&#xff0c;阿里巴巴 iconfont.cn 支持上传和管理自定义图标&#xff0c;并生成矢量字体&#xff0c;便于统一维护和扩展。 本文目标是支持自定义 iconfont 图标的展示和选择&#xff0c;并…

有n棍棍子,棍子i的长度为ai,想要从中选出3根棍子组成周长尽可能长的三角形。请输出最大的周长,若无法组成三角形则输出0。

题目描述&#xff1a; 有n棍棍子&#xff0c;棍子i的长度为ai&#xff0c;想要从中选出3根棍子组成周长尽可能长的三角形。请输出最大的周长&#xff0c;若无法组成三角形则输出0。 算法为O(nlogn) 初始理解题目 首先&#xff0c;我们需要清楚地理解题目要求&#xff1a; 输入…

企业级网络综合集成实践:VLAN、Trunk、STP、路由协议(OSPF/RIP)、PPP、服务管理(TELNET/FTP)与安全(ACL)

NE综合实验4 一、实验拓扑二、实验需求 按照图示配置IP地址。Sw7和sw8之间的直连链路配置链路聚合。公司内部业务网段为vlan10和vlan20&#xff0c;vlan10是市场部&#xff0c;vlan20是技术部&#xff0c;要求对vlan进行命名以便区分识别&#xff1b;pc10属于vlan10&#xff0c…

deep learning(李宏毅)--(六)--loss

一&#xff0c;关于分类问题及其损失函数的一些讨论。 在构建分类模型是&#xff0c;我们的最后一层往往是softmax函数&#xff08;起到归一化的作用&#xff09;&#xff0c;如果是二分类问题也可以用sigmoid函数。 在loss函数的选择上&#xff0c;一般采用交叉熵损失函数(…

机器学习:数据清洗与预处理 | Python

个人主页-爱因斯晨 文章专栏-Python学习 文章目录个人主页-爱因斯晨文章专栏-Python学习前言了解数据清洗数据清洗的步骤1. 环境准备与库导入2. 数据加载3. 数据初探与理解4. 缺失值处理5. 重复值处理6. 异常值处理7. 数据类型转换8. 数据标准化 / 归一化&#xff08;预处理&a…

【代码随想录】+ leetcode hot100:栈与队列算法专题总结、单调栈

大家好&#xff0c;我是此林。 今天分享的是【代码随想录】栈与队列算法专题总结&#xff0c;分享刷算法的心得体会。 1. 用栈实现队列、用队列实现栈 232. 用栈实现队列 - 力扣&#xff08;LeetCode&#xff09; 225. 用队列实现栈 - 力扣&#xff08;LeetCode&#xff09;…

每次启动服务器都要手动选择启动项

存在的问题 如下图所示&#xff1a; 每次启动服务器的时候&#xff0c;都需要手动将光标选择到第二条&#xff0c;敲回车&#xff0c;才能正常启动系统。从图片可以看到&#xff0c;这是一个 GRUB 启动菜单&#xff0c;显示了三个选项&#xff1a; CentOS Linux (3.10.0-1160.1…

C#.NET BackgroundService 详解

简介 BackgroundService 是 .NET Core 引入的用于实现长时间运行后台任务的基类&#xff0c;位于 Microsoft.Extensions.Hosting 命名空间。它是构建 Worker Service 和后台处理的核心组件。 为什么使用 BackgroundService&#xff1f;优雅的生命周期管理&#xff1a;自动处理启…