Linux网络编程 深入Linux网络栈:原始套接字链路层实战解析

之前我们编程都是在应用层,只需在地址结构体中传 地址与端口号。然后协议栈在传输层,与网络层帮我们进行数据的封装。但这里我们要学的是在链路层进行编程

这里我想说一下,当数据到达链路层,有三个分支:ARP,IP,RARP 当数据凑够IP,到达网络层又有四个分支:ICMP,IGMP,TCP,UDP

当数据到达传输层,只看端口,不看分支

步入正题:

知识点1【原始套接字】

1、概述

原始套接字,是实现与系统核心的套接字,可以接受本机网卡上所有的数据帧(只要到达网卡的数据帧都可以收到)。

当我们利用标准套接字(SOCK_DGRAM,SOCK_STREAM),都需要借助传输层协议,然后借助网络层协议,再到达网络核心。

而原始套接字,可以直接到达网络层,或者系统核心,而我们这里要学习的,就是直接到达系统核心,在系统核心上进行编程的。

补充

2、创建原始套接字

int socket(PF_PACKET,SOCK_RAW,protocol)
//第三个参数没有固定
  • 函数介绍

    功能

    创建链路层的原始套接字

    参数

    protocol:指定可以接受或发送的数据包类型

    ETH_P_IP:IPV4数据包

    ETH_P_ARP:ARP数据包

    ETH_P_ALL:任何协议类型的数据包

    返回值

    成功:>0 链路层套接字

    失败:<0 出错

    代码演示

    代码运行结果

    这里我想说一下,因为我们实操偏底层的代码,因此

    所有的原始套接字都需要加sudo权限来执行可执行文件./a.out

这里补充一下,如果要使用宏ETH_P_…宏需要包含头文件

#include <netinet/ether.h>

知识点2【数据包】

大家可以看到每个分支都有编号,我们下面将介绍

上图,是头部的添加以及解析流程。

1、UDP报文

UDP是传输层协议,因此它的数据是它的上一层:应用层

UDP的头部是8个字节

0-15 源端口

16-31 目的端口

32-47 UDP数据长度

48-63 UDP检验和

一定要对报文有印象,这是我们组包和解包的前提

2、TCP报文

我们可以看到头部长度4位最大也只能表示15.

但是TCP就算不算选项,也需要20个字节,该如何存储呢?

此时最大是15,只需要让15中的每一个1,代表4B即可。这样最多可以表示64个字节。

数据是源自应用层

0-15 源端口号

16-31 目的端口号

96-99 头部长度

3、IP报文

数据包是来自传输层 的数据

0-3 版本:区分IPv4与IPv6 4→IPv4 6→IPv6

4-7 首部长度:数值0-15,单位是4B

16-31 总长度:头部长度+来自传输层的数据 总长度

72-79 协议类型

1:ICMP

2:IGMP

6:TCP

17:UDP

96-127 源IP地址

128-159 目的IP地址

4、mac报文

数据包是来自网络层的数据

0-47 目的mac地址

48-95 源mac地址

96-111 类型

0x0806 ARP数据包

0x0800 IP数据包

0x8035 RARP数据包

5、ICMP报文

我们的ping命令

不同的类型值和代码值的组合代表不同的功能

8 0代表请求

0 8代表应答

知识点3【利用原始套接字捕获网络数据】

原始套接字使用 recvfrom函数 接收

这里我补充一下,原始套接字实在链路层,我们在链路层收数据,recvfrom的参数有地址结构体指针,这里就无需传参了→NULL,因为传参也没有用,不经过网络层,传输层,无法利用其协议,因此需要用户自行解包。

代码演示

代码运行结果

这里运行之后 由于recvfrom带阻塞仍能 源源不断的收到数据,为什么呢?

这是因为我们xshell使用windows终端控制Linux终端,需要反复通信,因此会不断发送数据

1、分析mac报文头部

xx:xx:xx:xx:xx:xx\0

我们知道mac地址存储时冒分法,16进制,并且高位补零。如上,因此如果打印成为字符串的形式总计18个字节

下面我们展示分析过程(提取mac报文头部)

代码演示

    // 接收数据while (1){// recvfrom 收到的是一个完整的帧数据unsigned char buf[1500] = "";int len = recvfrom(fd_sock, buf, sizeof(buf), 0, NULL, NULL);if (len < 0){perror("recvfrom");_exit(-1);}printf("len == %d\\n", len);// 分析mac报文头部char mac_src_addr[18] = "";char mac_dst_addr[18] = "";//提取源mac地址sprintf(mac_src_addr, "%02x:%02x:%02x:%02x:%02x:%02x",buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);//提取目的mac地址sprintf(mac_dst_addr, "%02x:%02x:%02x:%02x:%02x:%02x",buf[0 + 6], buf[1 + 6], buf[2 + 6], buf[3 + 6], buf[4 + 6], buf[5 + 6]);//这里补充说明:网络字节序大端存储,buf[0]存储高位数据,因此需要按照上面方法提取//提取类型unsigned short mac_type = ntohs(*((unsigned short *)(buf + 12)));//遍历printf("%s---->%s, ",mac_src_addr,mac_dst_addr);switch (mac_type){case 0x0800:printf("type:IP\\n");break;case 0x0806:printf("type:ARP\\n");break;case 0x8035:printf("type:RARP\\n");break;default:break;}}

代码运行结果

我们查看一下5a和ef分别是谁?

这里我们验证了,我们一直收到的数据 就是虚拟机和主机进行的通信

2、分析IP报文头部

要分析ip头部,需要先跳过mac头

这里看一下IP的格式

10进制点分发,我们用字符串提取,16个字节(按照最多的算)

代码演示

        //分析IP报文头部//跳过mac地址报文头部unsigned char *ip = buf + 14;//这里一定要是无符号的//提取源IP与目的IPchar src_ip_addr[16] = "";char dst_ip_addr[16] = "";//提取IP的方法1//sprintf(src_ip_addr,"%d.%d.%d.%d",ip[12],ip[13],ip[14],ip[15]);//sprintf(dst_ip_addr,"%d.%d.%d.%d",ip[12 + 4],ip[13 + 4],ip[14 + 4],ip[15 + 4]);//提取IP的方法二inet_ntop(AF_INET,ip + 12,src_ip_addr,sizeof(src_ip_addr));inet_ntop(AF_INET,ip + 12,dst_ip_addr,sizeof(dst_ip_addr));printf("\\t%s---->%s, ",src_ip_addr,dst_ip_addr);//提取类型unsigned char ip_type = ip[9];switch (ip_type){case 1:printf("type:ICMP, ");break;case 2:printf("type:IGMP, ");break;case 6:printf("type:TCP, ");break;case 17:printf("type:UCP, ");break;default:break;}//提取一下版本与首部长度unsigned char version = ip[0];unsigned char len_head = ip[0];version >>= 4;len_head &= 0x0F;printf("version :%d, len_head = %d\\n",version,len_head * 4); //注意这里一定不要用%c遍历,因为%c默认会遍历其ASCII码值,而并非数值}

代码运行结果

代码中的注意事项:

1、当遍历char 类型数据的时候,要显示数值使用%d,如果要显示ascll码才使用%c

2、ip无符号字符数组类型,buf也是无符号字符数组类型

3、ip的提取有两种方式一种是组包法(sprintf),另一种是inet_ntop()法

3、分析TCP和UDP报文头部

代码演示(含 数据遍历

                //分析TCP报文//从IP报文位置跳转到TCP报文位置char *tcp = ip + ip_len_head;//提取目的端口号和源端口号unsigned short src_port_id_tcp = ntohs(*((unsigned short *)tcp));unsigned short dst_port_id_tcp = ntohs(*((unsigned short *)(tcp + 2)));printf("\\t\\t%hu---->%hu\\n",src_port_id_tcp,dst_port_id_tcp);//提取数据内容//跳转到数据报文位置char tcp_len_head = (tcp[12]>>4) * 4;char *data_udp = tcp + tcp_len_head;printf("%s\\n",data_udp);

代码运行结果

4、整体代码

由于 数据内容遍历影响 结果的查看,我们这里不遍历数据

#include <stdio.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h> //socket()
#include <unistd.h>
#include <netinet/ether.h> //ETH_P_ALLint main(int argc, char const *argv[])
{// 创建原始套接字int fd_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));if (fd_sock < 0){perror("sock");_exit(-1);}printf("fd_sock == %d\\n", fd_sock);// 接收数据while (1){// recvfrom 收到的是一个完整的帧数据unsigned char buf[1500] = "";int len = recvfrom(fd_sock, buf, sizeof(buf), 0, NULL, NULL);if (len < 0){perror("recvfrom");_exit(-1);}printf("len == %d\\n", len);// 分析mac报文头部char mac_src_addr[18] = "";char mac_dst_addr[18] = "";// 提取源mac地址sprintf(mac_src_addr, "%02x:%02x:%02x:%02x:%02x:%02x",buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);// 提取目的mac地址sprintf(mac_dst_addr, "%02x:%02x:%02x:%02x:%02x:%02x",buf[0 + 6], buf[1 + 6], buf[2 + 6], buf[3 + 6], buf[4 + 6], buf[5 + 6]);// 这里补充说明:网络字节序大端存储,buf[0]存储高位数据,因此需要按照上面方法提取// 提取类型unsigned short mac_type = ntohs(*((unsigned short *)(buf + 12)));// 遍历printf("%s---->%s, ", mac_src_addr, mac_dst_addr);switch (mac_type){case 0x0800:printf("type:IP\\n");// 分析IP报文头部// 跳过mac地址报文头部unsigned char *ip = buf + 14; // 这里一定要是无符号的// 提取源IP与目的IPchar src_ip_addr[16] = "";char dst_ip_addr[16] = "";// 提取IP的方法1// sprintf(src_ip_addr,"%d.%d.%d.%d",ip[12],ip[13],ip[14],ip[15]);// sprintf(dst_ip_addr,"%d.%d.%d.%d",ip[12 + 4],ip[13 + 4],ip[14 + 4],ip[15 + 4]);// 提取IP的方法二inet_ntop(AF_INET, ip + 12, src_ip_addr, sizeof(src_ip_addr));inet_ntop(AF_INET, ip + 12, dst_ip_addr, sizeof(dst_ip_addr));printf("\\t%s---->%s, ", src_ip_addr, dst_ip_addr);// 提取一下版本与首部长度unsigned char version = ip[0];unsigned char ip_len_head = ip[0];version >>= 4;ip_len_head &= 0x0F;ip_len_head *= 4;printf("IP_version :%d, IP_len_head = %d, ", version, ip_len_head);// 注意这里一定不要用%c遍历,因为%c默认会遍历其ASCII码值,而并非数值// 提取类型unsigned char ip_type = ip[9];switch (ip_type){case 1:printf("type:ICMP\\n");break;case 2:printf("type:IGMP\\n");break;case 6:printf("type:TCP\\n");//分析TCP报文//从IP报文位置跳转到TCP报文位置char *tcp = ip + ip_len_head;//提取目的端口号和源端口号unsigned short src_port_id_tcp = ntohs(*((unsigned short *)tcp));unsigned short dst_port_id_tcp = ntohs(*((unsigned short *)(tcp + 2)));printf("\\t\\t%hu---->%hu\\n",src_port_id_tcp,dst_port_id_tcp);//提取数据内容//跳转到数据报文位置char tcp_len_head = (tcp[12]>>4) * 4;char *data_tcp = tcp + tcp_len_head;//printf("%s\\n",data_udp);break;case 17:printf("type:UCP\\n");//分析UDP报文//从IP报文位置跳转到UDP报文位置char *udp = ip + ip_len_head;//提取目的端口号和源端口号unsigned short src_port_id_udp = ntohs(*((unsigned short *)udp));unsigned short dst_port_id_udp = ntohs(*((unsigned short *)(udp + 2)));printf("\\t\\t%hu---->%hu\\n",src_port_id_udp,dst_port_id_udp);//提取数据内容//跳转到数据报文位置char *data_udp = udp + 8;//printf("%s\\n",data_udp);break;default:break;}break;case 0x0806:printf("type:ARP\\n");break;case 0x8035:printf("type:RARP\\n");break;default:break;}}// 关闭套接字close(fd_sock);return 0;
}

代码运行结果

结束

代码重在练习!

代码重在练习!

代码重在练习!

今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏夹关注,谢谢大家!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/78748.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用python写一个相机选型的简易程序

最近有点忙&#xff0c;上来写的时间不多。 今天就把之前写的一个选型的简易程序&#xff0c;供大家参考。 代码&#xff1a; import sys from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,QLabel, QLineEdit, QPushButton, QGro…

【实战篇】数字化打印——打印格式设计器的功能说明

前言 myBuilder内置了覆盖丰富场景的打印格式设计器&#xff0c;效果统一&#xff0c;功能完善。 设计器一&#xff1a;小票 用于设计小票、水单等滚筒纸张的场景&#xff0c;例如&#xff1a;超市购物小票 主要功能 打印格式的保存、下载、上传设计时功能&#xff1a;撤销…

Qt 中 QSQLITE 和 QODBC 数据库连接的区别

Qt 中 QSQLITE 和 QODBC 数据库连接的区别 这两行代码都是创建 Qt 数据库连接&#xff0c;但使用了不同的数据库驱动和连接方式&#xff1a; 1. QSqlDatabase::addDatabase("QSQLITE") 特点&#xff1a; 使用 SQLite 数据库的 原生驱动直接与 SQLite 数据库文件(…

Eigen核心矩阵/向量类 (Matrix, Vector, Array)

1. Matrix 类&#xff08;稠密矩阵&#xff09; 模板参数 cpp Matrix<Scalar, Rows, Cols, Options, MaxRows, MaxCols> Scalar: 元素类型&#xff08;如 float, double, int&#xff09;。 Rows/Cols: 行数和列数&#xff08;Dynamic 表示动态大小&#xff09;。 O…

汽车免拆诊断案例 | 2016款奔驰C200L车组合仪表上多个故障灯偶尔点亮

故障现象 一辆2016款奔驰C200L车&#xff0c;搭载274 920发动机&#xff0c;累计行驶里程约为13万km。该车组合仪表上的防侧滑故障灯、转向助力故障灯、安全气囊故障灯等偶尔异常点亮&#xff0c;且此时将挡位置于R挡&#xff0c;中控显示屏提示“后视摄像头不可用”&#xff…

实现 Babylon.js 鼠标输入管理单例 (MouseController) 的最佳实践

在现代 Web3D 开发中&#xff0c;高效的输入管理是创建流畅交互体验的关键。本文将详细介绍如何在 Babylon.js 中实现一个强大的鼠标输入管理单例&#xff0c;帮助你优雅地处理所有指针事件。 为什么需要鼠标输入管理单例&#xff1f; 在复杂的 3D 场景中&#xff0c;鼠标/指…

【LLM+Code】Cursor Agent 46.11 版本PromptTools最细致解读

一、cursor Agent cursor的agent模式, 多说一句&#xff0c;cursor目前我付费使用&#xff0c;是我目前为止使用过AI coding工具里最喜欢的一个&#xff0c;cursor nb&#xff01; https://gist.github.com/sshh12/25ad2e40529b269a88b80e7cf1c38084version&#xff1a;46.11 …

Flask + ajax上传文件(二)--多文件上传

Flask多文件上传完整教程 本教程将详细介绍如何使用Flask实现多文件上传功能,并使用时间戳为上传文件自动命名,避免文件名冲突。 一、环境准备 确保已安装Python和Flask pip install flask项目结构 flask_upload/ ├── app.py ├── upload/ # 上传文…

多级缓存入门:Caffeine、Lua、OpenResty、Canal

之前写过——Google Guava Cache简介 本文系统学习一下多级缓存 目录 0.什么是多级缓存商品查询业务案例导入1.JVM进程缓存初识Caffeine实现JVM进程缓存2.Lua语法入门HelloWorld数据类型、变量和循环函数、条件控制3.Nginx业务编码实现多级缓存安装OpenRestyOpenResty快速入门…

Python + Playwright:如何在Docker 容器运行测试?

Python + Playwright:如何在Docker 容器运行测试? 前言一、简介二、环境准备1. 安装 DockerWindows 用户macOS 用户Linux 用户(以 Ubuntu 为例)2. 启动 browserless 服务拉取 browserless 镜像启动 browserless 容器验证 browserless 是否启动成功三、创建自动化测试项目1.…

语音合成之四大语言模型(LLM)与TTS的深度融合

基于LLM的语音合成 1.技术架构1.1 LlaSA1.2 CosyVoice (和 CosyVoice2)1.3 SparkTTS 2 特性对比2.1 零样本语音克隆2.2 多语种支持2.3 可控语音生成2.4 计算效率和模型大小 总结 当前&#xff0c;在大型语言模型&#xff08;Large Language Models&#xff0c;LLMs&#xff09;…

使用 Conda 创建新环境

使用 Conda 创建新环境 在使用 Conda 进行包管理和环境隔离时&#xff0c;创建新环境是一个非常常见的操作。通过创建独立的环境&#xff0c;可以避免不同项目之间的依赖冲突&#xff0c;并且能够灵活地管理各个项目的运行环境。 以下是使用 Conda 创建和管理新环境的详细步骤…

Unity AssetBundle (AB) 打包详解

AssetBundle 是 Unity 提供的一种资源打包机制&#xff0c;允许开发者将游戏资源&#xff08;如模型、纹理、预制体等&#xff09;打包成独立的文件&#xff0c;便于动态加载和热更新。 一、AssetBundle 基础概念 1. 什么是 AssetBundle 资源压缩包&#xff0c;包含序列化资源…

Python flask入门

Python flask入门 一、路由1.1 常规路由1.2 动态路由1.3 路由的其他高级用法 二、变量规则2.1 示例1&#xff1a;字符串类型&#xff08;默认&#xff09;2.2 示例2&#xff1a;整数类型2.3 示例3&#xff1a;路径类型 三、自定义转换器3.1 核心组件详解3.2 工作流程详解 四、f…

AI赋能守护行车安全新防线,基于YOLOv5全系列【n/s/m/l/x】参数模型开发构建驾驶车辆场景下驾驶员疲劳分心驾驶行为智能检测预警系统

在当今社会&#xff0c;随着科技生产力的飞速发展&#xff0c;汽车早已成为人们日常出行不可或缺的交通工具。它不仅极大地提高了人们的出行效率&#xff0c;也为生活带来了诸多便利。然而&#xff0c;随着汽车保有量的不断增加&#xff0c;交通安全问题也日益凸显。疲劳驾驶和…

onloyoffice历史版本功能实现,版本恢复功能,编辑器功能实现 springboot+vue2

文章目录 onloyoffice历史版本功能实现&#xff0c;版本恢复功能&#xff0c;编辑器功能实现 springbootvue2前提 需要注意把这个 (改成自己服务器的ip或者域名) 改成 自己服务器的域名或者地址我使用的onloyoffice版本 8.1.3.41. onloyoffice服务器部署 搜索其他文章2. 前段代…

概率论与统计(不确定性分析)主要应用在什么方面?涉及到具体知识是什么?

用户问的是概率论与统计&#xff08;不确定性分析&#xff09;的主要应用方面&#xff0c;涉及的具体知识以及具体公式。首先&#xff0c;我需要确定概率论与统计在哪些领域有应用&#xff0c;比如工程、金融、医学、数据科学等等。然后&#xff0c;具体知识部分应该包括概率论…

如何利用快照与备份快速恢复服务器的数据

在服务器上利用**快照&#xff08;Snapshot&#xff09;**和**备份&#xff08;Backup&#xff09;**快速恢复数据&#xff0c;可显著减少停机时间并确保业务连续性。以下是具体操作步骤和最佳实践&#xff1a; --- ### **1. 快照&#xff08;Snapshot&#xff09;恢复** **适…

安卓APP开发项目源码

在移动互联网蓬勃发展的今天&#xff0c;安卓应用几乎覆盖了人们生活的方方面面。从社交、购物&#xff0c;到医疗、教育&#xff0c;APP 的需求呈指数级增长。然而&#xff0c;如何高效、低成本地开发一款质量可靠的安卓应用&#xff0c;仍是很多开发者和团队关注的核心问题。…

遨游三防|30200mAh、双露营灯三防平板,见证堆料天花板

在工业4.0与智能化转型的浪潮中&#xff0c;专业设备对性能、防护及场景适应性的要求日益严苛。遨游通讯作为国家级高新技术企业&#xff0c;依托“危、急、特”场景的深耕经验&#xff0c;推出的旗舰级产品AORO-P300三防平板&#xff0c;以30200mAh超大容量电池、双露营灯设计…