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

在上一节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

本人站点欢迎友好交流!

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

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

相关文章

AD使用快捷键

1、如何实现元器件旋转45放置 在Preferences >> PCB Editor >> General中将Rotation Step&#xff08;旋转的步进值&#xff09;由90改为45&#xff0c;这样以后每次按空格键旋转器件时旋转角度为45。 2、显示网络、隐藏网络 N 3、对齐 2、设置DRC检查选项&#xf…

clickhouse学习笔记(四)库、表、分区相关DDL操作

目录 一、数据库操作 1、创建数据库 2、查询及选择数据库 3、删除数据库 二、数据表操作 1、创建表 2、删除表 3、基本操作 ①追加新字段 ②修改字段类型或默认值 ③修改字段注释 ④删除已有字段 ⑤移动数据表&#xff08;重命名&#xff09; ⑥清空表 三、默认值…

【前端技巧】css篇

利用counter实现计数器 counter-reset&#xff1a;为计数器设置名称&#xff0c;语法如下&#xff1a; counter-rese: <idntifier><integer>第一个参数为变量名称&#xff0c;第二个参数为初始值&#xff0c;默认为0 counter-increment&#xff1a;设置计数器增…

OpenGL绘制Bezier曲面

Bezier的定义 贝塞尔曲面是贝塞尔曲线在二维上的扩展。它由一组控制点定义,通过这些控制点生成光滑的曲面。贝塞尔曲面通常用两个参数 u u u和 v v v来表示,这两个参数的取值范围都在 [0, 1] 之间。 数学表示 P ( u , v ) = ∑ i = 0 n ∑ j = 0 m p i j ⋅ B i , n ( u ) ⋅…

YOLOv10改进 | Conv篇 |YOLOv10引入SPD-Conv卷积

1. SPD-Conv介绍 1.1 摘要:卷积神经网络(CNN)在图像分类和目标检测等许多计算机视觉任务中取得了巨大的成功。 然而,在图像分辨率较低或物体较小的更艰巨的任务中,它们的性能会迅速下降。 在本文中,我们指出,这源于现有 CNN 架构中一个有缺陷但常见的设计,即使用跨步卷…

MicroPython+ESP32 C3+ST7735S LCD屏 WIFI联网显示实时时间

案例地址&#xff1a;https://gitee.com/whltaoin_admin/MP_ESP32_ST7735S- 展示效果 ESP32LCD屏 WIFI联网并显示实时时间 TFT LCD模块参数介绍 名称&#xff1a;1.8 128*160 RGB_TFT驱动芯片&#xff1a;ST7735S ESP32 C3 参数介绍&#xff08;经典款&#xff09; 外观及…

Kafka基础教程

Kafka基础教程 资料来源&#xff1a;Apache Kafka - Introduction (tutorialspoint.com) Apache Kafka起源于LinkedIn&#xff0c;后来在2011年成为一个开源Apache项目&#xff0c;然后在2012年成为一流的Apache项目。Kafka是用Scala和Java编写的。Apache Kafka是基于发布-订…

leetcode:557. 反转字符串中的单词 III(python3解法)

难度&#xff1a;简单 给定一个字符串 s &#xff0c;你需要反转字符串中每个单词的字符顺序&#xff0c;同时仍保留空格和单词的初始顺序。 示例 1&#xff1a; 输入&#xff1a;s "Lets take LeetCode contest" 输出&#xff1a;"steL ekat edoCteeL tsetnoc…

el-table表格变更前后根据数据值改变背景颜色

需求&#xff1a; 1.左侧变更前表格数据不可以编辑&#xff0c;并且背景色加灰 2.右侧变更后表格数据可被编辑&#xff0c;编辑后变更前与变更后行数据不一致&#xff0c;添加背景色区分 3.点击删除的时候&#xff0c;给变更后表格当前行&#xff0c;添加背景色和删除的中横…

jax.nn.initializers.glorot_normal()

import jax import jax.numpy as jnp from jax import random import jax.nn.initializers as init# 设置随机数种子 key random.PRNGKey(42)# 定义权重的形状 shape (in_dim, out_dim)# 获取 Glorot 正态初始化函数 glorot_normal_init init.glorot_normal()# 初始化权重 w…

C++初学者指南第一步---10.内存(基础)

C初学者指南第一步—10.内存&#xff08;基础&#xff09; 文章目录 C初学者指南第一步---10.内存&#xff08;基础&#xff09;1.内存模型1.1 纸上谈兵&#xff1a;C的抽象内存模型1.2 实践&#xff1a;内存的实际处理 2. 自动存储3.动态存储&#xff1a;std::vector3.1 动态内…

互联网技术基础-计算机人必看

目录 1.Internet的工作原理 1、Internet是一个分组交换系统 2、路由器是Internet实现互连的“标准件” 3、TCP/IP是Internet的核心协议 4、客户机/服务器的工作模式 2. IP地址 2.1 IP地址分类 2.2特殊IP地址 2.3路由器和IP编制原则 2.4子网的划分 2.5 IPV6 3.域名系…

Spatio-temporal Relation Modeling for Few-shot Action Recognition

标题&#xff1a;少样本动作识别的时空关系建模 源文链接&#xff1a;Thatipelli_Spatio-Temporal_Relation_Modeling_for_Few-Shot_Action_Recognition_CVPR_2022_paper.pdf (thecvf.com)https://openaccess.thecvf.com/content/CVPR2022/papers/Thatipelli_Spatio-Temporal_…

Sping源码(九)—— Bean的初始化(非懒加载)— Bean的创建方式(factoryMethod)

序言 前面文章介绍了在Spring中多种创建Bean实例的方式&#xff0c;包括采用FactoryBean的方式创建对象、使用反射创建对象、自定义BeanFactoryPostProcessor。 这篇文章继续介绍Spring中创建Bean的形式之一——factoryMethod。方法用的不多&#xff0c;感兴趣可以当扩展了解。…

Centos7.9安装kerberos

文章目录 一、背景二、Kerberos安装部署2.1kerberos服务端必要软件安装2.2配置krb5.conf2.3配置kdc.conf2.4配置kadm5.acl2.5创建Kerberos数据库2.6启动Kerberos服务2.7创建Kerberos管理员principal2.8客户端安装kerberos2.9Kerberos功能验证 本人其他相关文章链接 一、背景 亲…

Qemu虚拟机在线迁移到VMware

libvirt版本&#xff1a;libvirt-10.0.0qemu版本&#xff1a;qemu-8.2.0 在生产环境中&#xff0c;大多数的场景是 vmware 虚拟机迁移到 qemu 环境&#xff0c;一般是通过关机然后导出、导入磁盘镜像来实现。 如果要将 qemu 环境虚拟机迁移到 vmware 怎么办呢&#xff1f;要求…

112、路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 叶子节点 是指没有子节点…

【VMware】VMware虚拟机安装_配置_使用教程

一、准备工作 1、下载VMware软件&#xff1a;访问VMware官方网站&#xff0c;下载适合你操作系统的VMware Workstation Pro安装包。 下载地址&#xff1a;VMware Desktop Hypervisors for Windows, Linux, and Mac 2、准备操作系统镜像文件&#xff1a;根据你想要在虚拟机中安…

[Vulnhub] Sleepy JDWP+Tomcat+Reverse+Reverse-enginnering

信息收集 Server IP AddressPorts Opening192.168.8.100TCP:21,8009,9001 $ nmap -sV -sC 192.168.8.100 -p- --min-rate 1000 -Pn Starting Nmap 7.92 ( https://nmap.org ) at 2024-06-20 05:06 EDT Nmap scan report for 192.168.8.100 (192.168.8.100) Host is up (0.00…

前端入门篇(五十二)练习6:transition过渡小动画

所以应该先找到第n个li&#xff0c;找到li再找img&#xff0c;li没有找错&#xff0c;底下又各自只有一个img&#xff0c;解决 ul li:nth-child(1) img { } 描述文字从下往上&#xff1a; 一开始描述也在框框下面&#xff0c;当hover时&#xff0c;translateY(0)&#xff0…