用户态协议栈02-arp reply实现

news2024/12/23 6:53:02

在上一节DODK的UDP收发中发送udp包的时候,需要向物理机的arp表中添加一个静态的arp记录。这在生产环境中显然是不可以的。在内核的协议栈中,会将自己的ipmac在局域网中进行广播,并且记录其他电脑的ipmac。在需要发送数据包的时候,查询arp表来获取目标的地址构建发送数据包。在实现arp reply之后,dpdk可以回复收到的arp数据包,让对方在arp表中添加一条动态的记录,这样就不需要添加静态的arp记录了。

arp协议

地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到局域网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。地址解析协议是建立在网络中各个主机互相信任的基础上的,局域网络上的主机可以自主发送ARP应答消息,其他主机收到应答报文时不会检测该报文的真实性就会将其记入本机ARP缓存;由此攻击者就可以向某一主机发送伪ARP应答报文,使其发送的信息无法到达预期的主机或到达错误的主机,这就构成了一个ARP欺骗。ARP命令可用于查询本机ARP缓存中IP地址和MAC地址的对应关系、添加或删除静态对应关系等。相关协议有RARP、代理ARP。NDP用于在IPv6中代替地址解析协议。

arp报文结构

协议解析

  • 硬件类型:指明了发送方想知道的硬件接口类型,以太网的值为1

  • 协议类型:指明了发送方提供的高层协议类型,IP为0800(16进制)

  • 硬件地址长度和协议长度:指明了硬件地址和高层协议地址的长度,这样ARP报文就可以在任意硬件和任意协议的网络中使用

  • 操作类型:用来表示这个报文的类型,ARP请求为1,ARP响应为2,RARP请求为3,RARP响应为4

  • 发送方硬件地址(0-3字节):源主机硬件地址的前3个字节

  • 发送方硬件地址(4-5字节):源主机硬件地址的后3个字节

  • 发送方IP地址(0-1字节):源主机硬件地址的前2个字节

  • 发送方IP地址(2-3字节):源主机硬件地址的后2个字节

  • 目标硬件地址(0-1字节):目的主机硬件地址的前2个字节

  • 目标硬件地址(2-5字节):目的主机硬件地址的后4个字节

  • 目标IP地址(0-3字节):目的主机的IP地址

DPDK实现

在UDP的基础上加上ARP的实现,和UDP一样,都分为报文解析和报文组织两个部分。

报文解析

if(ethhdr->ether_type == rte_cpu_to_be_16(RTE_ETHER_TYPE_ARP)) {
            struct rte_arp_hdr* arphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_arp_hdr*, sizeof(struct rte_ether_hdr));
            struct in_addr addr;
            addr.s_addr = arphdr->arp_data.arp_tip;
            printf("arp --> src: %s ", inet_ntoa(addr));
            addr.s_addr = gLocalIp;
            printf("local: %s\n", inet_ntoa(addr));
            if(arphdr->arp_data.arp_tip == gLocalIp) {
                struct rte_mbuf* arpbuf = ng_send_arp(mbuf_pool, arphdr->arp_data.arp_sha.addr_bytes, gLocalIp, arphdr->arp_data.arp_sip);
                rte_eth_tx_burst(gDpdkPortId, 0, &arpbuf, 1);
                rte_pktmbuf_free(arpbuf);
                rte_pktmbuf_free(mbufs[i]);
                arpbuf = NULL;
                mbufs[i] = NULL;
            }
            continue;
        }

ARP协议工作在数据链路层,主要传递的信息是IP和MAC。在解析完以太网之后,如果他的数据包是ARP数据包,那就对数据包进行解析。针对数据包内容,组织一个新的ARP包进行回发。

报文组织

static int ng_encode_arp_pkt(uint8_t *msg, uint8_t *dst_mac, uint32_t sip, uint32_t dip) {

	// 1 ethhdr
	struct rte_ether_hdr *eth = (struct rte_ether_hdr *)msg;
	rte_memcpy(eth->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN);
	rte_memcpy(eth->d_addr.addr_bytes, dst_mac, RTE_ETHER_ADDR_LEN);
	eth->ether_type = htons(RTE_ETHER_TYPE_ARP);

	// 2 arp 
	struct rte_arp_hdr *arp = (struct rte_arp_hdr *)(eth + 1);
	arp->arp_hardware = htons(1);
	arp->arp_protocol = htons(RTE_ETHER_TYPE_IPV4);
	arp->arp_hlen = RTE_ETHER_ADDR_LEN;
	arp->arp_plen = sizeof(uint32_t);
	arp->arp_opcode = htons(2);

	rte_memcpy(arp->arp_data.arp_sha.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN);
	rte_memcpy( arp->arp_data.arp_tha.addr_bytes, dst_mac, RTE_ETHER_ADDR_LEN);

	arp->arp_data.arp_sip = sip;
	arp->arp_data.arp_tip = dip;
	
	return 0;

}

static struct rte_mbuf *ng_send_arp(struct rte_mempool *mbuf_pool, uint8_t *dst_mac, uint32_t sip, uint32_t dip) {

	const unsigned total_length = sizeof(struct rte_ether_hdr) + sizeof(struct rte_arp_hdr);

	struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool);
	if (!mbuf) {
		rte_exit(EXIT_FAILURE, "rte_pktmbuf_alloc\n");
	}

	mbuf->pkt_len = total_length;
	mbuf->data_len = total_length;

	uint8_t *pkt_data = rte_pktmbuf_mtod(mbuf, uint8_t *);
	ng_encode_arp_pkt(pkt_data, dst_mac, sip, dip);

	return mbuf;
}

这里的步骤和UDP一样,作为对ARP协议的实现,后面的arp_table会有详细的实现,这里是回复arp request使得不需要手动添加静态ARP

完整代码

#if 1
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <stdio.h>
#include <arpa/inet.h>

static int gDpdkPortId = 0;

#define MAKE_IPV4_ADDR(a, b, c, d) (a + (b<<8) + (c<<16) + (d<<24))
static uint32_t gLocalIp = MAKE_IPV4_ADDR(192, 168, 1, 111);
uint32_t gSrcIp;
uint32_t gDstIp;
uint16_t gSrcPort;
uint16_t gDstPort;
uint8_t gSrcMac[RTE_ETHER_ADDR_LEN];
uint8_t gDstMac[RTE_ETHER_ADDR_LEN];

#define MBUF_LEN    (4096-1)
#define BURST_LEN   64

static const struct rte_eth_conf port_conf_default = {
        .rxmode = {.max_rx_pkt_len = RTE_ETHER_MAX_LEN}
};

static void ng_encode_udp_pkt(uint8_t* msg, uint8_t* data, unsigned total_len) {
    struct rte_ether_hdr* ethhdr = (struct rte_ether_hdr*)(msg);
    rte_memcpy(ethhdr->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN);
    rte_memcpy(ethhdr->d_addr.addr_bytes, gDstMac, RTE_ETHER_ADDR_LEN);
    ethhdr->ether_type = htons(RTE_ETHER_TYPE_IPV4);

    struct rte_ipv4_hdr* iphdr = (struct rte_ipv4_hdr*)(ethhdr + 1);
    iphdr->version_ihl = 0x45;
    iphdr->type_of_service = 0;
    iphdr->total_length = htons(total_len - sizeof(struct rte_ether_hdr));
    iphdr->packet_id = 0;
    iphdr->fragment_offset = 0;
    iphdr->time_to_live = 64;
    iphdr->next_proto_id = IPPROTO_UDP;
    iphdr->src_addr = gSrcIp;
    iphdr->dst_addr = gDstIp;
    iphdr->hdr_checksum = 0;
    iphdr->hdr_checksum = rte_ipv4_cksum(iphdr);

    struct rte_udp_hdr* udphdr = (struct rte_udp_hdr*)(iphdr + 1);
    udphdr->src_port = gSrcPort;
    udphdr->dst_port = gDstPort;
    uint16_t len = total_len - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr);
    udphdr->dgram_len = htons(len);
    rte_memcpy((uint8_t*)(udphdr + 1), data, len);
    udphdr->dgram_cksum = 0;
    udphdr->dgram_cksum = rte_ipv4_udptcp_cksum(iphdr, udphdr);
    struct in_addr addr;
    addr.s_addr = gSrcIp;
    printf("-->Src: %s:%d ", inet_ntoa(addr), ntohs(gSrcPort));
    addr.s_addr = gDstIp;
    printf("Dst:%s:%d %s\n", inet_ntoa(addr), ntohs(gDstPort), data);

}

static struct rte_mbuf* ng_udp_send(struct rte_mempool* mbuf_pool, uint8_t* data, uint16_t length) {
    struct rte_mbuf* mbuf = rte_pktmbuf_alloc(mbuf_pool);
    if(!mbuf) {
        rte_exit(EXIT_FAILURE, "rte_pktmbuf_alloc\n");
    }
    const unsigned total_len = length + 42;
    mbuf->pkt_len = total_len;
    mbuf->data_len = total_len;
    uint8_t* pkt = rte_pktmbuf_mtod(mbuf, uint8_t*);
    ng_encode_udp_pkt(pkt, data, total_len);
    return mbuf;
}

static int ng_encode_arp_pkt(uint8_t *msg, uint8_t *dst_mac, uint32_t sip, uint32_t dip) {

	// 1 ethhdr
	struct rte_ether_hdr *eth = (struct rte_ether_hdr *)msg;
	rte_memcpy(eth->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN);
	rte_memcpy(eth->d_addr.addr_bytes, dst_mac, RTE_ETHER_ADDR_LEN);
	eth->ether_type = htons(RTE_ETHER_TYPE_ARP);

	// 2 arp 
	struct rte_arp_hdr *arp = (struct rte_arp_hdr *)(eth + 1);
	arp->arp_hardware = htons(1);
	arp->arp_protocol = htons(RTE_ETHER_TYPE_IPV4);
	arp->arp_hlen = RTE_ETHER_ADDR_LEN;
	arp->arp_plen = sizeof(uint32_t);
	arp->arp_opcode = htons(2);

	rte_memcpy(arp->arp_data.arp_sha.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN);
	rte_memcpy( arp->arp_data.arp_tha.addr_bytes, dst_mac, RTE_ETHER_ADDR_LEN);

	arp->arp_data.arp_sip = sip;
	arp->arp_data.arp_tip = dip;
	
	return 0;

}

static struct rte_mbuf *ng_send_arp(struct rte_mempool *mbuf_pool, uint8_t *dst_mac, uint32_t sip, uint32_t dip) {

	const unsigned total_length = sizeof(struct rte_ether_hdr) + sizeof(struct rte_arp_hdr);

	struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool);
	if (!mbuf) {
		rte_exit(EXIT_FAILURE, "rte_pktmbuf_alloc\n");
	}

	mbuf->pkt_len = total_length;
	mbuf->data_len = total_length;

	uint8_t *pkt_data = rte_pktmbuf_mtod(mbuf, uint8_t *);
	ng_encode_arp_pkt(pkt_data, dst_mac, sip, dip);

	return mbuf;
}
static void ng_init_port(struct rte_mempool* mbuf_pool) {
    int dev_sys_ports = rte_eth_dev_count_avail();
    if(dev_sys_ports == 0) {
        rte_exit(EXIT_FAILURE, "Could not support dpdk\n");
    }

    struct rte_eth_dev_info dev_info;
    rte_eth_dev_info_get(gDpdkPortId, &dev_info);
    const int nb_rx_queue = 1;
    const int nb_tx_queue = 1;
    struct rte_eth_conf port_conf = port_conf_default;
    rte_eth_dev_configure(gDpdkPortId, nb_rx_queue, nb_tx_queue, &port_conf);
    if(rte_eth_rx_queue_setup(gDpdkPortId, 0, 1024, rte_eth_dev_socket_id(gDpdkPortId), NULL, mbuf_pool) < 0) {
        rte_exit(EXIT_FAILURE, "Could not setup RX queue\n");
    }
    struct rte_eth_txconf txconf = dev_info.default_txconf;
    txconf.offloads = port_conf_default.rxmode.offloads;
    if(rte_eth_tx_queue_setup(gDpdkPortId, 0, 1024, rte_eth_dev_socket_id(gDpdkPortId), &txconf) < 0) {
        rte_exit(EXIT_FAILURE, "Could not setup TX queue\n");
    }

    if(rte_eth_dev_start(gDpdkPortId) < 0) {
        rte_exit(EXIT_FAILURE, "Could not start\n");
    }
}

int main(int argc, char* argv[]) {
    if(rte_eal_init(argc, argv) < 0) {
        rte_exit(EXIT_FAILURE, "Error with eal init\n");
    }
    
    struct rte_mempool* mbuf_pool = rte_pktmbuf_pool_create("mbuf_pool", MBUF_LEN, 0, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
    if(!mbuf_pool) {
        rte_exit(EXIT_FAILURE, "Error with mempool create\n");
    }

    ng_init_port(mbuf_pool);
    rte_eth_macaddr_get(gDpdkPortId, (struct rte_ether_addr*)gSrcMac);
    //printf("local mac: %s\n", gSrcMac);
    while (1)
    {
        struct rte_mbuf* mbufs[BURST_LEN];
        unsigned nb_pkt = rte_eth_rx_burst(gDpdkPortId, 0, mbufs, BURST_LEN);
        if (nb_pkt > BURST_LEN) {
			rte_exit(EXIT_FAILURE, "Error receiving from eth\n");
		}

        for(size_t i = 0; i < nb_pkt; i++) {
            struct rte_ether_hdr* ethhdr = rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr*);
            if(ethhdr->ether_type == rte_cpu_to_be_16(RTE_ETHER_TYPE_ARP)) {
                struct rte_arp_hdr* arphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_arp_hdr*, sizeof(struct rte_ether_hdr));
                struct in_addr addr;
                addr.s_addr = arphdr->arp_data.arp_tip;
                printf("arp --> src: %s ", inet_ntoa(addr));
                addr.s_addr = gLocalIp;
                printf("local: %s\n", inet_ntoa(addr));
                if(arphdr->arp_data.arp_tip == gLocalIp) {
                    struct rte_mbuf* arpbuf = ng_send_arp(mbuf_pool, arphdr->arp_data.arp_sha.addr_bytes, gLocalIp, arphdr->arp_data.arp_sip);
                    rte_eth_tx_burst(gDpdkPortId, 0, &arpbuf, 1);
                    rte_pktmbuf_free(arpbuf);
                    rte_pktmbuf_free(mbufs[i]);
                    arpbuf = NULL;
                    mbufs[i] = NULL;
                }
                continue;
            }
            if(ethhdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
                rte_pktmbuf_free(mbufs[i]);
                mbufs[i] = NULL;
                continue;
            }
            struct rte_ipv4_hdr* iphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr*, sizeof(struct rte_ether_hdr));
            if(iphdr->next_proto_id == IPPROTO_UDP) {
                struct rte_udp_hdr* udphdr = (struct rte_udp_hdr*)(iphdr + 1);
               // rte_memcpy(gSrcMac, ethhdr->d_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
                rte_memcpy(gDstMac, ethhdr->s_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
                rte_memcpy(&gDstIp, &iphdr->src_addr, sizeof(uint32_t));
                rte_memcpy(&gSrcIp, &iphdr->dst_addr, sizeof(uint32_t));
                rte_memcpy(&gSrcPort, &udphdr->dst_port, sizeof(uint16_t));
                rte_memcpy(&gDstPort, &udphdr->src_port, sizeof(uint16_t));
                uint16_t data_len = ntohs(udphdr->dgram_len);
                *((char*)udphdr + data_len) = '\0';
                struct in_addr addr;
                addr.s_addr = iphdr->src_addr;
                printf("<--Src: %s:%d ", inet_ntoa(addr), htons(udphdr->src_port));
                addr.s_addr = iphdr->dst_addr;
                printf("Dst: %s:%d %s\n", inet_ntoa(addr), htons(udphdr->dst_port), (char*)(udphdr+1));
                struct rte_mbuf* txbuf = ng_udp_send(mbuf_pool, (uint8_t*)(udphdr + 1), data_len);
                rte_eth_tx_burst(gDpdkPortId, 0, &txbuf, 1);

                rte_pktmbuf_free(txbuf);
                rte_pktmbuf_free(mbufs[i]);
                mbufs[i] = NULL;
                txbuf = NULL;
            }
        }
    }
    
}
#endif

本人站点欢迎友好交流!

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

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

相关文章

6月15号作业

使用手动连接&#xff0c;将登录框中的取消按钮使用第二中连接方式&#xff0c;右击转到槽&#xff0c;在该槽函数中&#xff0c;调用关闭函数 将登录按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0…

18. 《C语言》——【Nice2016年校招笔试题引发的思考】

亲爱的读者&#xff0c;大家好&#xff01;我是一名正在学习编程的高校生。在这个博客里&#xff0c;我将和大家一起探讨编程技巧、分享实用工具&#xff0c;并交流学习心得。希望通过我的博客&#xff0c;你能学到有用的知识&#xff0c;提高自己的技能&#xff0c;成为一名优…

证明 泊松分布 的期望和方差

泊松分布 泊松分布&#xff08;Poisson Distribution&#xff09;是描述在固定时间间隔内某事件发生次数的概率分布&#xff0c;特别适用于稀有事件的统计。假设随机变量 ( X ) 表示在时间间隔 ( t ) 内某事件发生的次数&#xff0c;并且该事件在单位时间内发生的平均次数为 (…

牛客练习题打卡(06-15)

run方法线程执行体 .start方法开启多线程 在java中 &#xff0c; 整数类型默认int,带小数默认double ; 如果要指定长整型加L&#xff1b;如果要指定为单精度加F ; 在java中&#xff0c;重载要求方法名相同&#xff0c; 参数列表必须不同&#xff08;个数不同、或类型不同、参数…

Oracle数据库Day03-单行函数

1. 单行函数 处理数据项接受函数并返回一个值对返回的每一行采取行动每行返回一个结果可能会修改数据类型可以嵌套接受可以是列或表达式的参数 function_name[(arg1, arg2,…)]

【面试干货】Class.forName()与ClassLoader.loadClass()在Java反射中的区别

【面试干货】Class.forName&#xff08;&#xff09;与ClassLoader.loadClass&#xff08;&#xff09; 在Java反射中的区别 1、Class.forName()1.1 示例代码1.2 关键点 2、ClassLoader.loadClass()2.1 示例代码2.2 关键点 3、两者之间的区别 &#x1f496;The Begin&#x1f…

主流框架选择:React、Angular、Vue的详细比较

目前前端小伙伴经常使用三种广泛使用的开发框架&#xff1a;React、Angular、Vue - 来设计网站 Reactjs&#xff1a;效率和多功能性而闻名 Angularjs&#xff1a;创建复杂的应用程序提供了完整的解决方案&#xff0c;紧凑且易于使用的框架 Vuejs&#xff1a;注重灵活性和可重用…

解决老毛子路由器自带微信提示功能无法触发问题

新买了一个二手的RM AC2100&#xff0c;刷了老毛子后&#xff0c;发现自带的上下线微信提示无法使用(方糖公众号无信息) 经我开启SSH&#xff0c;将上下线部分代码拿出来调试发现&#xff0c;发不出来的原因是原版信息发送长度过长&#xff0c;需要截取一部分才能发送成功。 …

查看npm版本异常,更新nvm版本解决问题

首先说说遇见的问题&#xff0c;基本上把nvm&#xff0c;npm的坑都排了一遍 nvm版本导致npm install报错 Unexpected token ‘.‘install和查看node版本都正确&#xff0c;结果查看npm版本时候报错 首先就是降低node版本… 可以说基本没用&#xff0c;如果要降低版本的话&…

比亚迪智驾技术震撼登场!L3级自动驾驶领跑全国,无图导航、夜间挑战轻松应对!

作为新能源汽车领域的翘楚&#xff0c;比亚迪在电池技术与智能驾驶方面都有着卓越的表现。近日&#xff0c;比亚迪凭借其领先的智驾技术&#xff0c;成功入选全国首批L3级自动驾驶上路及行驶试点名单&#xff0c;这无疑将推动智驾技术的普及速度。 你知道吗&#xff1f;比亚迪智…

Unity中的伽马(Gamma)空间和线性(Linear)空间

伽马空间定义&#xff1a;通常用于描述图像在存储和显示时的颜色空间。在伽马空间中&#xff0c;图像的保存通常经过伽马转换&#xff0c;使图片看起来更亮。 gamma并不是色彩空间&#xff0c;它其实只是如何对色彩进行采样的一种方式 为什么需要Gamma&#xff1a; 在游戏业…

并发容器(二):Concurrent类下的ConcurrentHashMap源码级解析

并发容器-ConcurrentHashMap 前言数据结构JDK1.7版本HashEntrySegment 初始化 重要方法Put方法扩容rehash方法 前言 之前我们在集合篇里聊完了HashMap和HashTable&#xff0c;我们又学习了并发编程的基本内容&#xff0c;现在我们来聊一聊Concurrent类下的重要容器&#xff0c…

程序设计实践--字符串

字符串 回文串 “回文”是指正读反读都能一样的句子,它是古今中外都有的一种修辞方式和文字游戏。你的任务是从键盘输入一个字符串(最大长度<100),判断这个字符串是不是回文,如果是回文,则输出“Yes”, 如果不是, 则输出“No”。 输入描述 输入若干个字符串(最大长…

[C++]使用yolov10的onnx模型结合onnxruntime和bytetrack实现目标追踪

【官方框架地址】 yolov10yolov10框架&#xff1a;https://github.com/THU-MIG/yolov10 bytetrack框架&#xff1a;https://github.com/ifzhang/ByteTrack 【算法介绍】 Yolov10与ByTetrack&#xff1a;目标追踪的强大组合 Yolov10和ByTetrack是两种在目标追踪领域具有显…

【计算机组成原理】指令系统考研真题详解之拓展操作码!

计算机组成原理&#xff1a;指令系统概述与深入解析 1. 指令系统概述 计算机软硬件界面的概念 在计算机组成原理中&#xff0c;指令系统扮演着至关重要的角色&#xff0c;它是计算机软硬件界面的核心。软件通过指令与硬件进行通信&#xff0c;硬件根据指令执行相应的操作。指…

韩顺平0基础学java——第24天

p484-508 System类 常见方法 System.arrycopy&#xff08;src&#xff0c;0&#xff0c;dest&#xff0c;1,2&#xff09;&#xff1b; 表示从scr的第0个位置拷贝2个&#xff0c;放到目标数组索引为1的地方。 BigInteger和BigDecimal类 保存大整数和高精度浮点数 BigInte…

Unity的三种Update方法

1、FixedUpdate 物理作用——处理物理引擎相关的计算和刚体的移动 (1) 调用时机&#xff1a;在固定的时间间隔内&#xff0c;而不是每一帧被调用 (2) 作用&#xff1a;用于处理物理引擎的计算&#xff0c;例如刚体的移动和碰撞检测 (3) 特点&#xff1a;能更准确地处理物理…

RIP路由附加度量值(华为)

#交换设备 RIP路由附加度量值 RIP&#xff08;Routing Information Protocol&#xff09;路由协议中的附加度量值是指在RIP路由原来度量值的基础上所增加的额外度量值&#xff0c;通常以跳数来表示。这个附加度量值可以是正值&#xff0c;也可以是负值&#xff0c;用于影响路…

嵌入式学习记录6.14(练习)

#include "mainwindow.h" #include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);this->resize(1028,783); //设置左侧背景QLabel *lab1new QLabel(this);lab1->…

慈善组织管理系统设计

一、用户角色与权限 慈善组织管理系统设计首先需要考虑的是用户角色与权限的划分。系统应明确区分不同的用户角色&#xff0c;如管理员、项目负责人、财务人员、捐赠者等&#xff0c;并为每个角色分配相应的权限。管理员应拥有最高的权限&#xff0c;能够管理系统全局&#xf…