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.pswp.cn/bicheng/78748.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/78748.shtml
英文地址,請注明出處:http://en.pswp.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超大容量電池、雙露營燈設計…