实现Linux平台自定义协议族

一 简介

我们常常在Linux系统中编写socket接收TCP/UDP协议数据,大家有没有想过它怎么实现的,如果我们要实现socket接收自定义的协议数据又该怎么做呢?带着这个疑问,我们一起往下看吧~~

二 Linux内核函数简介

在Linux系统中要想实现通过socket接收网络协议栈送过来的数据,首先要对这两个内核函数实现注册, 先来看看这两个函数原型:

//ops: 指向一个描述套接字协议族的 net_proto_family 结构体。这个结构体定义了:
//协议族编号(family),比如 AF_INET(IPv4)或 AF_INET6(IPv6)。
//与此协议族关联的 create 函数,用于创建套接字。
int sock_register(const struct net_proto_family *ops);

//prot: 指向一个描述传输层协议的 proto 结构体,该结构体定义了协议的具体实现,包括各种操作和资源管理逻辑,例如发送、接收、内存分配等。
//alloc_slab: 一个布尔值,指定是否为该协议分配内存缓存(slab 缓存),通常用于控制协议的缓冲区管理。
int proto_register(struct proto *prot, int alloc_slab);

1.sock_register 将用户自定义的协议族挂载到内核的协议族表(net_families),注册成功后,用户态程序可以通过指定协议族(如 socket(AF_INET, ..))来创建对应的套接字。

2.proto_register 函数将传输层协议的实现注册到内核的协议栈中,使其可以在对应的套接字类型下运行(如 SOCK_STREAM 对应 TCP),注册的协议通常与 sock_register 中的协议族关联,用于实现更高层次的功能。

两者的关系

  1. sock_register: 是更高层的接口,用于注册协议族(如 AF_INET)。

  2. proto_register: 是传输层的具体实现,用于注册实际的协议逻辑(如 TCP、UDP、或自定义协议)。

通常,一个协议族可能对应多种协议,比如AF_INET(IPv4)对的具体协议又TCP/UDP等,proto_register 是为了支持某个协议族的实际功能,而 sock_register 提供的是更外层的入口。二者可以配合使用,以实现从协议族到协议具体实现的完整功能。

自定义协议族:内核模块实现

下面这个代码是实现自定义一个协议族AF_MYPROTO(28)的内核模块。

测试平台:CentOS7, Linux内核版本: 3.10.0,编译工具:gcc 4.8.5

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <linux/skbuff.h>
#include <net/sock.h>
#include <linux/init.h>

/* 自定义协议族号 (通常选择未使用的值,sock.h有定义) */
#define AF_MYPROTO 28 

/* 定义 proto_ops,用于实现 socket 操作 */
static int myproto_sock_create(struct net *net, struct socket *sock, int protocol, int kern);

/* 应用层调用close会调用sock_my_release */
static int sock_my_release (struct socket *sock)
{
    pr_info("sock_my_release............\n");
 return 0;
}

/* 应用层调用recv函数会触发调用下面这个sock_my_recvmsg函数 */
static int sock_my_recvmsg (struct kiocb *iocb, struct socket *sock,
          struct msghdr *msg, size_t total_len,
          int flags)
{
    char buf[32];
    int i=0;
    for(i=0; i<32; ++i) buf[i] = (total_len+i)&0xFF;

    memcpy_toiovec(msg->msg_iov, buf, 32); /* 拷贝数据到应用层buffer */

    pr_info("total_len=%ld flags=%d\n", total_len, flags);

 return 0;
}

/* 应用层调用send函数会触发调用该sock_my_sendmsg函数 */
static int sock_my_sendmsg (struct kiocb *iocb, struct socket *sock,
          struct msghdr *m, size_t total_len)
{
    pr_info("sock_my_sendmsg............\n");
 
 return 0;
}

static const struct proto_ops myproto_ops = {
    .family     = AF_MYPROTO,
    .owner      = THIS_MODULE,
    .release    = sock_my_release, 
    .bind       = sock_no_bind, /* 默认空函数 xxx_no_xxx(),下同 */
    .connect    = sock_no_connect,
    .socketpair = sock_no_socketpair,
    .accept     = sock_no_accept,
    .getname    = sock_no_getname,
    .poll       = sock_no_poll,
    .ioctl      = sock_no_ioctl,
    .listen     = sock_no_listen,
    .shutdown   = sock_no_shutdown,
    .setsockopt = sock_no_setsockopt,
    .getsockopt = sock_no_getsockopt,
    .sendmsg    = sock_my_sendmsg, /* 指定上面实现的函数 */
    .recvmsg    = sock_my_recvmsg, /* 指定上面实现的函数 */
    .mmap       = sock_no_mmap,
    .sendpage   = sock_no_sendpage,
};

/* 定义传输协议 proto 结构体 */
static struct proto myproto_proto = {
    .name = "MYPROTO",
    .owner = THIS_MODULE,
    .obj_size = sizeof(struct sock),
};

// 定义协议族 net_proto_family
static const struct net_proto_family myproto_family = {
    .family = AF_MYPROTO,
    .create = myproto_sock_create,
    .owner = THIS_MODULE,
};

/* 创建 socket 函数 */
static int myproto_sock_create(struct net *net, struct socket *sock, int protocol, int kern) {
    struct sock *sk;

    pr_info("MYPROTO: Creating socket\n");

    if (!protocol)
        protocol = IPPROTO_IP;

    /* 分配 socket 的底层数据结构:PF_INET: Internet IP Protocol */
    sk = sk_alloc(net, PF_INET, GFP_KERNEL, &myproto_proto);
    if (!sk) return -ENOMEM;

    sock->ops = &myproto_ops;
    sock_init_data(sock, sk);

    return 0;
}

/* 模块加载和卸载,模块入口 */
static int __init myproto_init(void) {
    int ret;

    pr_info("MYPROTO: Initializing module\n");

    /* 注册传输层协议,具体协议 */
    ret = proto_register(&myproto_proto, 1);
    if (ret) {
        pr_err("MYPROTO: Failed to register proto\n");
        return ret;
    }

    ret = sock_register(&myproto_family); /* 注册协议族 */
    if (ret) {
        pr_err("MYPROTO: Failed to register protocol family\n");
        proto_unregister(&myproto_proto);
        return ret;
    }

    pr_info("MYPROTO: Module loaded\n");
    return 0;
}

/* 模块卸载时候调用 */
static void __exit myproto_exit(void) {
    pr_info("MYPROTO: Cleaning up module\n");

    // 注销协议族和传输层协议
    sock_unregister(AF_MYPROTO);
    proto_unregister(&myproto_proto);

    pr_info("MYPROTO: Module unloaded\n");
}

module_init(myproto_init);
module_exit(myproto_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux编程用C@Young");

编译内核模块的Makefile如下:注意内核代码名称为net_prot.c才能编译,

obj-m:=net_prot.o #名称对应起来,也可自己修改

KERNELDIR:=/lib/modules/$(shell uname -r)/build

PWD:=$(shell pwd)

default:       
  $(MAKE) -C $(KERNELDIR)  M=$(PWD) modules
clean:        
 rm -rf *.o *.mod.c *.mod.o *.koendif

编译完成后,使用insmod向内核插入模块:insmod net_prot.ko

使用dmesg查看模块加载信息:

三 自定义协议族:应用程序实现

这个应用程序创建AF_MYPROTO自定义协议的socket,读取数据,示例如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>

#define AF_MYPROTO 28 // 和内核协议族号保持一致

int main() {
    int sockfd, i=100;
    char buf[2048];
    // 创建套接字
    sockfd = socket(AF_MYPROTO, SOCK_RAW, 0);
    if (sockfd < 0) {
        perror("socket");
        return EXIT_FAILURE;
    }

    printf("Socket created successfully with AF_MYPROTO\n");
    
    while(--i){
        read(sockfd, buf, 32+i);

        for(int i=0; i<32; ++i){
            printf("0x%x ", buf[i]);
        }
        printf("\n");
           
    }

    // 关闭套接字
    close(sockfd);
    return EXIT_SUCCESS;
}

使用dmesgch

四 测试结果

收到来自内核返回数据:该数据是固定填充,做示例演示。

五 总结

通过这个例子,我们了解到了内核协议族的注册与使用流程,加深对Linux协议栈的了解。

我是小C,欢迎大家一起交流学习~

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

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

相关文章

数组和链表OJ题

leetcode用编译器调试的技巧 数组和链表练习题 leetcode/reverse_Link/main.c Hera_Yc/bit_C_学习 - 码云 - 开源中国 1、移除元素 ​​​​​​27. 移除元素 - 力扣&#xff08;LeetCode&#xff09; int removeElement(int* nums, int numsSize, int val) {int src 0, …

VSCode 使用教程:项目使用配置、使用哪些插件、Live Server使用问题及解决方案(你想要的,都在这里)

VSCode的配置&#xff1a; Ⅰ、VSCode 可能需要的项目配置&#xff1a;1、项目颜色主题的切换&#xff1a;其一、点击设置 -> 选择主题 -> 选择颜色主题&#xff1a;其二、通过上下键操作&#xff0c;选择想要的主题&#xff1a; 2、项目文件图标主题的切换&#xff1a;其…

28 基于51单片机的两路电压检测(ADC0808)

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;通过ADC0808获取两路电压&#xff0c;通过LCD1602显示 二、硬件资源 基于KEIL5编写C代码&#xff0c;PROTEUS8.15进行仿真&#xff0c;全部资源在页尾&#xff0c;提供…

宠物空气净化器推荐2024超详细测评 希喂VS霍尼韦尔谁能胜出

最近有粉丝一直在评论区和后台探讨宠物空气净化器是不是智商税的问题&#xff0c;有人认为宠物空气净化器肯定不是智商税&#xff0c;有些人认为将其购回家就是个没用的东西&#xff0c;还占地方&#xff0c;双方各有自己的观点。 其实宠物空气净化器和普通的空气净化器是有很大…

鸿蒙学习笔记:CheckboxGroup组件

本次鸿蒙CheckboxGroup组件实战&#xff0c;先创建CheckboxGroupDemoAbility与CheckboxGroupDemo.ets页面&#xff0c;在ets页面以Row、Column布局呈现界面。利用CheckboxGroup管理爱好相关Checkbox&#xff0c;通过状态记录及“确定”按钮实现选择展示。设置页面为首页后启动应…

[Java]微服务之分布式事务

介绍 下单业务&#xff0c;前端请求首先进入订单服务&#xff0c;创建订单并写入数据库。然后订单服务调用购物车服务和库存服务: 购物车服务负责清理购物车信息库存服务负责扣减商品库存 问题分析: 下单过程中, 订单服务创建订单, 插入自己的数据库, 执行成功购物车服务, 清…

如何在谷歌浏览器中使用开发者工具调试网页

在数字时代&#xff0c;网页开发和调试已成为每个前端开发人员必备的技能。谷歌浏览器&#xff08;Google Chrome&#xff09;提供了强大的开发者工具&#xff0c;帮助开发者快速定位和修复网页中的问题。本文将详细介绍如何使用Chrome开发者工具来调试网页&#xff0c;同时也会…

新增工作台模块,任务中心支持一键重跑,MeterSphere开源持续测试工具v3.5版本发布

2024年11月28日&#xff0c;MeterSphere开源持续测试工具正式发布v3.5版本。 在这一版本中&#xff0c;MeterSphere新增工作台模块&#xff0c;工作台可以统一汇总系统数据&#xff0c;提升测试数据的可视化程度并增强对数据的分析能力&#xff0c;为管理者提供测试工作的全局…

在Springboot项目中实现将文件上传至阿里云 OSS

oss介绍 阿里云对象存储服务&#xff08;OSS&#xff09;是一种高效、安全和成本低廉的数据存储服务&#xff0c;可以用来存储和管理海量的数据文件。本文将教你如何使用 Java 将文件上传到阿里云 OSS&#xff0c;并实现访问文件。 1. 准备工作 1.1 开通 OSS 服务 登录阿里云…

CrystalDiskInfo:硬盘健康监测工具简介和下载

原论坛给你更好的阅读体验&#xff1a;CrystalDiskInfo&#xff1a;硬盘健康监测工具简介和下载 | 波波论坛 引言 在日常使用电脑时&#xff0c;硬盘的健康状态对于系统的稳定性和数据的安全性至关重要。硬盘出现故障可能会导致数据丢失&#xff0c;严重时甚至会使整个系统无…

springboot339javaweb的新能源充电系统pf(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;新能源充电系统的设计与实现 摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解…

【第三讲】Spring Boot 3.4.0 新特性详解:增强的配置属性支持

Spring Boot 3.4.0 版本在配置属性的支持上进行了显著增强&#xff0c;使得开发者能够更灵活地管理和使用应用程序的配置。新的特性包括对配置属性的改进、类型安全增强、以及对环境变量的更好支持。这些改进旨在提升开发效率和代码可读性&#xff0c;同时简化配置过程。本文将…

龙迅#LT6912适用于HDMI2.0转HDMI+LVDS/MIPI,分辨率高达4K60HZ,支持音频和HDCP2.2

1. 描述 LT6912是一款高性能的HDMI2.0转HDMI和LVDS和MIPI转换器。 HDMI2.0 输入和输出均支持高达 6Gbps 的数据速率&#xff0c;为4k60Hz视频提供足够的带宽。此外&#xff0c;还支持 HDCP2.2 进行数据解密&#xff08;无数据 加密&#xff09;。 对于 LVDS 输出&#xff0c…

彻底理解微服务配置中心的作用

常见的配置中心有SpringCloudConfig、Apollo、Nacos等&#xff0c;理解它的作用&#xff0c;无非两点&#xff0c;一是配置中心能做什么&#xff0c;不使用配置中心会出现什么问题。 作用&#xff1a;配置中心是用来集中管理服务的配置&#xff0c;它是用来提高系统配置的维护…

MySQL数据库表的操作

1、总述 今天我跟大家分享MySQL数据库中表的创建&#xff0c;查看&#xff0c;修改&#xff0c;删除。 2、创建表 create table table_name ( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎; 说明&#xff1…

摄影相关常用名词

本文介绍与摄影相关的常用名词。 曝光 Exposure 感光元件接收光线的过程&#xff0c;决定图像的明暗程度和细节表现。 光圈 Aperture 控制镜头进光量的孔径大小&#xff0c;用 F 值&#xff08;f-stop&#xff09; 表示。 光圈越大&#xff08;F 值越小&#xff09;&#xff0c…

使用 VLC 在本地搭建流媒体服务器 (详细版)

提示&#xff1a;详细流程 避坑指南 Hi~&#xff01;欢迎来到碧波空间&#xff0c;平时喜欢用博客记录学习的点滴&#xff0c;欢迎大家前来指正&#xff0c;欢迎欢迎~~ ✨✨ 主页&#xff1a;碧波 &#x1f4da; &#x1f4da; 专栏&#xff1a;音视频 目录 借助VLC media pl…

C++之C++11新特性(三)--- 智能指针

目录 一、智能指针 1.1 为什么需要智能指针 1.2 内存泄漏 1.2.1 内存泄漏的基本概念 1.2.2 内存泄漏的分类 1.2.3 如何避免内存泄漏 1.3 智能指针的使用及其原理 1.3.1 RAII 1.3.2 智能指针的基本原理 1.3.3 auto_ptr 1.3.4 unique_ptr 1.3.5 shared_ptr 1.3.6 sha…

Elasticearch索引mapping写入、查看、修改

作者&#xff1a;京东物流 陈晓娟 一、ES Elasticsearch是一个流行的开源搜索引擎&#xff0c;它可以将大量数据快速存储和检索。Elasticsearch还提供了强大的实时分析和聚合查询功能&#xff0c;数据模式更加灵活。它不需要预先定义固定的数据结构&#xff0c;可以随时添加或修…

golang版本管理工具:scoop使用

安装 Scoophttps://scoop.sh/根据官方文档安装。 第一步&#xff1a;打开PowerShell。(注意不要使用管理员方式打开&#xff0c;否则在执行安装Scoop的过程中&#xff0c;会报错。) 第二步&#xff1a;切到C盘根目录下。 第三步&#xff1a; Set-ExecutionPolicy -Executi…