libbpf-bootstrap开发指南:socket 监测与过滤 - sockfilter

目录

代码分析

comm 数据部分

BPF 代码部分

功能说明

rb 结构分析

ip_is_fragment 函数分析

bpf_skb_load_bytes函数分析

GRE协议说明

用户代码部分

功能说明

open_raw_sock& 原始套接字

setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd))

执行效果

分片与不分片的处理难度说明


代码分析

comm 数据部分

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2022 Jacky Yin */
#ifndef __SOCKFILTER_H
#define __SOCKFILTER_Hstruct so_event {__be32 src_addr;__be32 dst_addr;union {__be32 ports;__be16 port16[2];};__u32 ip_proto;__u32 pkt_type;__u32 ifindex;
};#endif /* __SOCKFILTER_H */

BPF 代码部分

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2022 Jacky Yin */
#include <stddef.h>
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/in.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include "sockfilter.h"#define IP_MF	  0x2000
#define IP_OFFSET 0x1FFFchar LICENSE[] SEC("license") = "Dual BSD/GPL";struct {__uint(type, BPF_MAP_TYPE_RINGBUF);__uint(max_entries, 256 * 1024);
} rb SEC(".maps");static inline int ip_is_fragment(struct __sk_buff *skb, __u32 nhoff)
{__u16 frag_off;bpf_skb_load_bytes(skb, nhoff + offsetof(struct iphdr, frag_off), &frag_off, 2);frag_off = __bpf_ntohs(frag_off);return frag_off & (IP_MF | IP_OFFSET);
}SEC("socket")
int socket_handler(struct __sk_buff *skb)
{struct so_event *e;__u8 verlen;__u16 proto;__u32 nhoff = ETH_HLEN;bpf_skb_load_bytes(skb, 12, &proto, 2);proto = __bpf_ntohs(proto);if (proto != ETH_P_IP)return 0;if (ip_is_fragment(skb, nhoff))return 0;/* reserve sample from BPF ringbuf */e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);if (!e)return 0;bpf_skb_load_bytes(skb, nhoff + offsetof(struct iphdr, protocol), &e->ip_proto, 1);if (e->ip_proto != IPPROTO_GRE) {bpf_skb_load_bytes(skb, nhoff + offsetof(struct iphdr, saddr), &(e->src_addr), 4);bpf_skb_load_bytes(skb, nhoff + offsetof(struct iphdr, daddr), &(e->dst_addr), 4);}bpf_skb_load_bytes(skb, nhoff + 0, &verlen, 1);bpf_skb_load_bytes(skb, nhoff + ((verlen & 0xF) << 2), &(e->ports), 4);e->pkt_type = skb->pkt_type;e->ifindex = skb->ifindex;bpf_ringbuf_submit(e, 0);return skb->len;
}
功能说明

从接收到的数据包中提取一些元数据,并将这些元数据保存到一个ring buffer中。具体来说,它提取的元数据包括:

  • IP协议类型 (protocol)
  • 如果IP协议不是GRE (Generic Routing Encapsulation),则提取源IP地址 (saddr) 和目标IP地址 (daddr)
  • IP头中的版本和头长度字段 (verlen)
  • TCP或UDP的源端口和目标端口 (ports)
  • 数据包类型 (pkt_type)
  • 接收数据包的网络接口索引号 (ifindex)

这个BPF程序只处理非分片的IP数据包,并且只处理以太网协议类型为IP的数据包。

rb 结构分析

rb是一个ring buffer类型的map,最大容量为256 * 1024项。ring buffer是一种先进先出(FIFO)的数据结构,用于保存从数据包中提取的元数据。

在之前的案例中也提到过这个结构,就是用于和用户空间之间通信的

ip_is_fragment 函数分析

ip_is_fragment函数的主要作用是检查给定的IP数据包是否是一个分片。在IP协议中,如果一个数据包过大,超过了最大传输单元(MTU),那么这个数据包会被分片(fragmented)成多个更小的数据包以进行传输。这个过程可以在发送端进行,也可以在路由过程中由路由器进行。每个分片都是一个完整的IP数据包,包含了完整的IP头,但只包含了原始数据包的一部分数据。

ip_is_fragment函数通过检查IP头中的"fragment offset"字段来判断一个数据包是否是分片。这个字段的结构如下:

  • 最高位是保留位,总是0。
  • 下一个位是"More Fragments"(MF)位,如果这个位是1,那么表示后面还有更多的分片。
  • 剩下的13位是"Fragment Offset",表示这个分片在原始数据包中的偏移量。

所以,如果一个数据包是分片,那么它的"More Fragments"位是1,或者它的"Fragment Offset"不是0。在ip_is_fragment函数中,这两个条件被合并在一起,所以只要这个字段的值和IP_MF或IP_OFFSET进行位与操作的结果不是0,那么这个函数就会返回1,表示这个数据包是分片。否则,这个函数就会返回0,表示这个数据包不是分片。

注意,这个函数只能判断一个数据包是否是分片,但不能判断这个分片是原始数据包的哪一部分。为了完整地重组一个分片的数据包,需要收集所有的分片,然后按照它们的"Fragment Offset"字段的值将它们按正确的顺序组合在一起。

	if (ip_is_fragment(skb, nhoff))return 0;

如果ip_is_fragment函数返回true(或者非零),那么这段代码就会执行return 0;,意味着该函数在这个点上结束,且返回值为0。这是因为该BPF程序只处理非分片的IP数据包。

bpf_skb_load_bytes函数分析
static inline void bpf_skb_load_bytes(const struct __sk_buff *skb, u32 off, void *to, u32 len);

这个函数的参数包括:

  • skb:这个参数是一个指向数据包的指针。在BPF程序中,数据包通常被表示为struct __sk_buff类型的对象。
  • off:这个参数是要读取的数据在数据包中的偏移量,以字节为单位。
  • to:这个参数是一个指向缓冲区的指针,这个缓冲区用于保存从数据包中读取的数据。
  • len:这个参数是要读取的数据的长度,以字节为单位。

bpf_skb_load_bytes函数的功能是从数据包的指定偏移量开始,读取指定长度的数据,并将这些数据复制到指定的缓冲区。这个函数可以用于读取数据包的任何部分,包括数据包头(例如IP头,TCP头等)和数据包的数据部分。

注意,bpf_skb_load_bytes函数只能在BPF程序中使用,不能在普通的C程序中使用。这是因为这个函数在运行时将被BPF虚拟机转换为对底层数据结构的直接操作。

bpf_skb_load_bytes(skb, 12, &proto, 2);

这行代码是在使用bpf_skb_load_bytes函数从数据包中读取两个字节的数据,这两个字节位于数据包的偏移量为12的位置。这两个字节被解释为网络层协议类型(proto),并且它们被复制到变量proto中。

在以太网帧的标准结构中,偏移量为12的位置是帧类型字段的开始位置。该字段用于表示以太网帧的有效载荷(Payload)中的协议类型。例如,如果这个字段的值是0x0800,那么表示有效载荷中的数据是一个IP数据包;如果这个字段的值是0x86DD,那么表示有效载荷中的数据是一个IPv6数据包。

因此,这行代码的目的是读取以太网帧的类型字段,然后检查这个字段的值。如果这个字段的值表示有效载荷中的数据是一个IP数据包,那么socket_handler函数将继续处理这个数据包;否则,socket_handler函数将忽略这个数据包。

GRE协议说明

在这个代码中是不处理GRE协议的

GRE,全称为Generic Routing Encapsulation(通用路由封装),是一个网络层协议,用于在两个网络节点之间封装任何网络层协议的数据包,使其能够经过不同的网络传输。

GRE协议可用于在两个网络之间建立直接的、点对点的连接,即使这两个网络在地理上相隔很远。这使得GRE协议成为建立VPN(虚拟专用网络)的一种常用技术。

GRE协议的一大优点是它能够封装几乎所有的网络层协议,包括IPv4、IPv6、IPX等。这使得GRE协议非常灵活,能够应用于各种网络环境中。

然而,GRE协议也有一些缺点。例如,GRE协议没有内置的加密功能,因此,如果需要保证数据的安全性,那么就需要额外实现加密功能。此外,由于GRE协议需要额外的头部信息来封装原始的数据包,因此,它会增加网络的带宽使用和处理开销。

GRE是由IETF(互联网工程任务组)在RFC 2784中定义的。

但是因为如下原因,一般不会处理GRE协议。

  1. 复杂性:GRE协议是一种封装协议,它可以封装各种不同的网络层协议。处理GRE协议的数据包需要解封装操作,这增加了处理的复杂性。可能为了简化BPF程序,开发者决定忽略GRE协议的数据包。
  2. 特定用途:这个BPF程序可能是为了处理特定协议的数据包,例如TCP或UDP,而不是GRE。因此,它只关心非GRE协议的数据包。
  3. 性能:解封装GRE数据包会消耗更多的CPU资源,如果这个BPF程序需要处理大量的数据包,那么处理GRE数据包可能会影响性能。
  4. 安全性:由于GRE协议可以封装任意的网络层协议,因此它可能会被用来进行某些类型的网络攻击。例如,攻击者可以使用GRE协议来封装恶意的数据包,以绕过防火墙或IDS系统。因此,一些安全设备或系统可能会选择忽略GRE协议的数据包。

用户代码部分

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2022 Jacky Yin */
#include <argp.h>
#include <arpa/inet.h>
#include <assert.h>
#include <bpf/libbpf.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <net/if.h>
#include <signal.h>
#include <stdio.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <unistd.h>
#include "sockfilter.h"
#include "sockfilter.skel.h"static struct env {const char *interface;
} env;const char argp_program_doc[] ="BPF socket filter demo application.\n""\n""This program watch network packet of specified interface and print out src/dst\n""information.\n""\n""Currently only IPv4 is supported.\n""\n""USAGE: ./sockfilter [-i <interface>]\n";static const struct argp_option opts[] = {{ "interface", 'i', "INTERFACE", 0, "Network interface to attach" },{},
};static error_t parse_arg(int key, char *arg, struct argp_state *state)
{switch (key) {case 'i':env.interface = arg;break;case ARGP_KEY_ARG:argp_usage(state);break;default:return ARGP_ERR_UNKNOWN;}return 0;
}static const struct argp argp = {.options = opts,.parser = parse_arg,.doc = argp_program_doc,
};static const char *ipproto_mapping[IPPROTO_MAX] = {[IPPROTO_IP] = "IP",	   [IPPROTO_ICMP] = "ICMP",	  [IPPROTO_IGMP] = "IGMP",[IPPROTO_IPIP] = "IPIP",   [IPPROTO_TCP] = "TCP",	  [IPPROTO_EGP] = "EGP",[IPPROTO_PUP] = "PUP",	   [IPPROTO_UDP] = "UDP",	  [IPPROTO_IDP] = "IDP",[IPPROTO_TP] = "TP",	   [IPPROTO_DCCP] = "DCCP",	  [IPPROTO_IPV6] = "IPV6",[IPPROTO_RSVP] = "RSVP",   [IPPROTO_GRE] = "GRE",	  [IPPROTO_ESP] = "ESP",[IPPROTO_AH] = "AH",	   [IPPROTO_MTP] = "MTP",	  [IPPROTO_BEETPH] = "BEETPH",[IPPROTO_ENCAP] = "ENCAP", [IPPROTO_PIM] = "PIM",	  [IPPROTO_COMP] = "COMP",[IPPROTO_SCTP] = "SCTP",   [IPPROTO_UDPLITE] = "UDPLITE", [IPPROTO_MPLS] = "MPLS",[IPPROTO_RAW] = "RAW"
};static int open_raw_sock(const char *name)
{struct sockaddr_ll sll;int sock;sock = socket(PF_PACKET, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, htons(ETH_P_ALL));if (sock < 0) {fprintf(stderr, "Failed to create raw socket\n");return -1;}memset(&sll, 0, sizeof(sll));sll.sll_family = AF_PACKET;sll.sll_ifindex = if_nametoindex(name);sll.sll_protocol = htons(ETH_P_ALL);if (bind(sock, (struct sockaddr *)&sll, sizeof(sll)) < 0) {fprintf(stderr, "Failed to bind to %s: %s\n", name, strerror(errno));close(sock);return -1;}return sock;
}static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{return vfprintf(stderr, format, args);
}static inline void ltoa(uint32_t addr, char *dst)
{snprintf(dst, 16, "%u.%u.%u.%u", (addr >> 24) & 0xFF, (addr >> 16) & 0xFF,(addr >> 8) & 0xFF, (addr & 0xFF));
}static int handle_event(void *ctx, void *data, size_t data_sz)
{const struct so_event *e = data;char ifname[IF_NAMESIZE];char sstr[16] = {}, dstr[16] = {};if (e->pkt_type != PACKET_HOST)return 0;if (e->ip_proto < 0 || e->ip_proto >= IPPROTO_MAX)return 0;if (!if_indextoname(e->ifindex, ifname))return 0;ltoa(ntohl(e->src_addr), sstr);ltoa(ntohl(e->dst_addr), dstr);printf("interface: %s\tprotocol: %s\t%s:%d(src) -> %s:%d(dst)\n", ifname,ipproto_mapping[e->ip_proto], sstr, ntohs(e->port16[0]), dstr, ntohs(e->port16[1]));return 0;
}static volatile bool exiting = false;static void sig_handler(int sig)
{exiting = true;
}int main(int argc, char **argv)
{struct ring_buffer *rb = NULL;struct sockfilter_bpf *skel;int err, prog_fd, sock;env.interface = "lo";/* Parse command line arguments */err = argp_parse(&argp, argc, argv, 0, NULL, NULL);if (err)return -err;/* Set up libbpf errors and debug info callback */libbpf_set_print(libbpf_print_fn);/* Cleaner handling of Ctrl-C */signal(SIGINT, sig_handler);signal(SIGTERM, sig_handler);/* Load and verify BPF programs*/skel = sockfilter_bpf__open_and_load();if (!skel) {fprintf(stderr, "Failed to open and load BPF skeleton\n");return 1;}/* Set up ring buffer polling */rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL);if (!rb) {err = -1;fprintf(stderr, "Failed to create ring buffer\n");goto cleanup;}/* Create raw socket for localhost interface */sock = open_raw_sock(env.interface);if (sock < 0) {err = -2;fprintf(stderr, "Failed to open raw socket\n");goto cleanup;}/* Attach BPF program to raw socket */prog_fd = bpf_program__fd(skel->progs.socket_handler);if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd))) {err = -3;fprintf(stderr, "Failed to attach to raw socket\n");goto cleanup;}/* Process events */while (!exiting) {err = ring_buffer__poll(rb, 100 /* timeout, ms */);/* Ctrl-C will cause -EINTR */if (err == -EINTR) {err = 0;break;}if (err < 0) {fprintf(stderr, "Error polling perf buffer: %d\n", err);break;}sleep(1);}cleanup:ring_buffer__free(rb);sockfilter_bpf__destroy(skel);return -err;
}
功能说明

用于分析网络数据包的 BPF (Berkeley Packet Filter) 程序。程序会侦听指定的网络接口,并打印出源和目标的信息。目前,只支持处理 IPv4 协议。

open_raw_sock& 原始套接字

open_raw_sock() 函数的主要功能是创建一个原始套接字,并将其绑定到指定的网络接口上。这个函数的主要步骤如下:

  1. 通过 socket() 系统调用创建一个原始套接字。其中,PF_PACKET 表示底层的包接口,SOCK_RAW 表示原始套接字,SOCK_NONBLOCK 和 SOCK_CLOEXEC 分别表示非阻塞和在 exec() 调用后关闭套接字,htons(ETH_P_ALL) 表示接收所有类型的数据包。
  2. 使用 if_nametoindex() 函数获取网络接口的索引。
  3. 使用 bind() 函数将套接字绑定到指定的网络接口上。bind() 调用需要一个 sockaddr_ll 结构体,其中包含了套接字、网络接口和协议的信息。

原始套接字(Raw Socket)允许在更低的网络层次(如 IP 或 Ethernet 层次)上进行通信。使用原始套接字,应用程序可以自行处理或生成协议头部,例如,IP 头或 TCP 头,而不是由操作系统的网络栈来处理。这使得原始套接字非常适合于实现网络监控工具,或者自定义协议。

相比之下,普通的套接字(如 TCP 或 UDP 套接字)是在更高的网络层次(如传输层)上进行通信。这些套接字由操作系统的网络栈自动处理协议头部,并为应用程序提供了一个简单的读写接口。

需要注意的是,由于原始套接字能够直接访问底层网络协议,使用原始套接字通常需要相应的权限(如 root 权限)。

setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd))

这段代码使用 setsockopt() 函数将 BPF(Berkeley Packet Filter)程序附加到原始套接字上。

下面是每个参数的详细解释:

  • sock:这是你要修改属性的 socket 文件描述符。
  • SOL_SOCKET:这是选项定义级别的参数,SOL_SOCKET 表示这个选项是 socket 层面的。
  • SO_ATTACH_BPF:这是你要修改的选项。SO_ATTACH_BPF 选项用于将 BPF 程序附加到 socket 上。
  • &prog_fd:这是你要设置的选项值。在这个例子中,它是一个指向 BPF 程序的文件描述符的指针。
  • sizeof(prog_fd):这是选项值的大小。

当这段代码执行后,所有通过 sock socket 接收的数据包将首先通过 prog_fd 指向的 BPF 程序进行处理。也就是说,BPF 程序能够在数据包被 socket 读取之前对其进行过滤或修改。

执行效果

interface: lo	protocol: TCP	127.0.0.1:56738(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:37351(src) -> 127.0.0.1:57856(dst)
interface: lo	protocol: TCP	127.0.0.1:57856(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:37351(src) -> 127.0.0.1:57856(dst)
interface: lo	protocol: TCP	127.0.0.1:57856(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:37351(src) -> 127.0.0.1:57856(dst)
interface: lo	protocol: TCP	127.0.0.1:57856(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:56738(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:37351(src) -> 127.0.0.1:56738(dst)
interface: lo	protocol: TCP	127.0.0.1:56738(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:56738(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:52778(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:52762(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:37351(src) -> 127.0.0.1:56738(dst)
interface: lo	protocol: TCP	127.0.0.1:56738(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:37351(src) -> 127.0.0.1:52778(dst)
interface: lo	protocol: TCP	127.0.0.1:52778(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:37351(src) -> 127.0.0.1:52778(dst)
interface: lo	protocol: TCP	127.0.0.1:37351(src) -> 127.0.0.1:52778(dst)
interface: lo	protocol: TCP	127.0.0.1:37351(src) -> 127.0.0.1:52762(dst)
interface: lo	protocol: TCP	127.0.0.1:52762(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:37351(src) -> 127.0.0.1:52762(dst)
interface: lo	protocol: TCP	127.0.0.1:37351(src) -> 127.0.0.1:52762(dst)
interface: lo	protocol: TCP	127.0.0.1:52778(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:52762(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:37351(src) -> 127.0.0.1:57856(dst)
interface: lo	protocol: TCP	127.0.0.1:57856(src) -> 127.0.0.1:37351(dst)
interface: lo	protocol: TCP	127.0.0.1:37351(src) -> 127.0.0.1:57856(dst)
interface: lo	protocol: TCP	127.0.0.1:57856(src) -> 127.0.0.1:37351(dst)

分片与不分片的处理难度说明

  1. 完整性问题:在处理分片数据包时,你需要保证所有的分片都已经收到,并且能正确地组合在一起以恢复原始的数据包。如果某个分片丢失,那么整个数据包就无法正确地恢复。这需要在应用层实现额外的逻辑来处理分片的接收和重组。
  2. 资源使用:处理分片数据包通常需要更多的资源。例如,你需要在内存中保存已经收到的分片,等待剩下的分片到达。如果收到的分片数量很大,或者某个分片长时间无法到达,那么这可能会占用大量的内存和CPU资源。
  3. 安全问题:分片数据包可以被用来进行一些网络攻击。例如,攻击者可以故意发送大量的不完整的分片,导致你的应用消耗大量的资源去处理这些分片,从而实现拒绝服务(DoS)攻击。

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

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

相关文章

git 工具使用--分支管理

git 工具使用–分支管理 文章目录 git 工具使用--分支管理理解分支创建分支切换分支合并分支删除分支合并冲突分支管理策略分支策略bug分支删除临时分支总结 理解分支 分支管理是Git的杀手级功能之一。分支&#xff1a;就是科幻中的平行宇宙&#xff0c;当你正在电脑面前学习C…

设计模式-单例模式

面向对象语言讲究的是万物皆对象。通常流程是先定义可实例化类&#xff0c;然后再通过各种不同的方式创建对象&#xff0c;因此类一般可以实例化出多个对象。但是实际项目开发时&#xff0c;我们还是希望保证项目运行时有且仅包含一个实例对象。这个需求场景的出发点包括但不限…

变压器试验介质损耗

试验目的 介质损耗因数 tanδ (% ) 是判断变压器绝缘状态的一种较有效的手段, 主要用来检 查变压器整体受潮、 油质劣化及严重的局部缺陷等, 但不一定能发现变压器局部受潮 等集中性局部缺陷。 试验设备 异频介质损耗测试仪 厂家&#xff1a; 湖北众拓高试 试验接线 (1) 介…

搜索引擎elasticsearch :安装elasticsearch (包含安装组件kibana、IK分词器、部署es集群)

文章目录 安装elasticsearch1.部署单点es1.1.创建网络1.2.加载镜像1.3.运行 2.部署kibana2.1.部署2.2.DevTools2.3 分词问题(中文不友好) 3.安装IK分词器3.1.在线安装ik插件&#xff08;较慢&#xff09;3.2.离线安装ik插件&#xff08;推荐&#xff09;1&#xff09;查看数据卷…

数据结构--哈夫曼树

数据结构–哈夫曼树 带权路径长度 结点的 权 \color{red}权 权:有某种现实含义的数值&#xff08;如:表示结点的重要性等) 结点的带权路径长度 \color{red}结点的带权路径长度 结点的带权路径长度:从树的根到该结点的路径长度(经过的边数&#xff09;与该结点上权值的乘积 树的…

小程序MobX创建store并实现全局数据共享

查看小程序根目录中是否存在package.json文件 在项目根目录运行cmd 没有package.json文件输入npm init -y初始化一下,初始化一个包管理 安装MobX npm install --save mobx-miniprogram4.13.2 mobx-miniprogram-bindings1.2.1 小程序菜单栏工具–构建npm 根目录创建store文…

el-ment ui 表格组件table实现列的动态插入功能

在实际需求中我们经常遇到各种奇葩的需求&#xff0c;不足为奇。每个项目的需求各不相同&#xff0c;实现功能的思路大致是一样的。 本文来具体介绍怎么实现table表格动态插入几列。 首先实现思路有2种&#xff0c; 1. 插入的位置如果是已知的&#xff0c;我知道在哪个标题的…

设计模式之建造者设计模式

写在前面 不知道&#xff0c;你在工作中有没有使用过lombok&#xff0c;如果你使用过&#xff0c;不知道你有没有使用过其中的Builder注解&#xff0c;其就会帮我们生成建造者设计模式相关的代码&#xff0c;本文就一起来看下吧&#xff01; 1&#xff1a;介绍 1.1&#xff…

spark启动HA时workers为0,且为standby状态

今天学习一个spark视频&#xff0c;在启动StandAloneHa模式的时候&#xff0c;发现workers为0&#xff0c;而且spark两个master的状态都为standby&#xff0c;找了很久&#xff0c;才知道我用的spark3.2 最低支撑的zookeeper版本为3.5.x,而且zookeeper的安装包是需要带bin的那个…

Selenium 获取接口响应数据

目录 前言 seleniumwire简介 功能 兼容性 目录 安装 创建webdriver 获取请求 请求对象 限制请求捕获 前言 有时候需要知道UI界面操作的同时接口响应数据是否正常&#xff0c;这时就需要获取接口响应数据。Selenium本身没有获取接口响应的api&#xff0c;但是可以通过…

我的创作纪念日

机缘 实战项目中的经验分享&#xff1a;多看文档&#xff01;多看书&#xff01;团队中有这领域的前辈多去问&#xff0c;但是请带上自己的思考和间接&#xff0c;不要问一些文档中早已写过的内容。日常学习过程中的记录&#xff1a; 关注行业动态&#xff1a;我在日常学习过程…

1063 Set Similarity (PAT甲级)

大半个月没做题&#xff0c;手非常生... #include <cstdio> #include <vector> #include <map>int N, M, K, t, m, n; std::vector<std::map<int, int>> vec;void similarity(int a, int b){int cnt 0;for(auto c : vec[a]){if(vec[b].find(c.…

CSS3 动画 animation 入门学习笔记 之 属性详解

文章目录 简单介绍 CSS 动画CSS 动画的作用CSS 动画语法介绍CSS 动画属性animation-nameanimation-durationanimation-delayanimation-directionanimation-iteration-countanimation-play-stateanimation-timing-functionanimation-fill-modeanimation 简单介绍 CSS 动画 引用…

基于深度学习的高精度80类动物目标检测系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度80类动物目标检测识别系统可用于日常生活中或野外来检测与定位80类动物目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的80类动物目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YO…

Matplotlib是什么

Matplotlib 是一款用于数据可视化的 Python 软件包&#xff0c;支持跨平台运行&#xff0c;它能够根据 NumPy ndarray 数组来绘制 2D 图像&#xff0c;它使用简单、代码清晰易懂&#xff0c;深受广大技术爱好者喜爱。 NumPy 是 Python 科学计算的软件包&#xff0c;ndarray 则…

HTTP1.1 wireshark分析

目录 http1.1wireshark分析http 1.1 keep-alive的2次http请求wireshark分析http1.1 keep-alive过期的2次请求keep-alive报文 本地springboot启动一个简单的服务&#xff0c;然后请求测试 tcpdump -i lo0 -nnvv -w tmp.cap tcpdump 本地回环网卡 http1.1 HTTP/1.0 每进行一次…

蚂蚁集团开源可信隐私计算框架「隐语」:开放、通用

7 月 4 日,蚂蚁集团宣布面向全球开发者正式开源可信隐私计算框架 “隐语”。 隐语是蚂蚁集团历时 6 年自主研发,以安全、开放为核心设计理念打造的可信隐私计算技术框架,涵盖了当前几乎所有主流隐私计算技术。 据介绍,隐语内置 MPC、TEE、同态等多种密态计算虚拟设备,提…

操作系统练习:创建内核模块,并加载和卸载模块

说明 本文记录如何创建和编译一个内核模块&#xff0c;以及加载和卸载内核模块。为《操作系统概念(第九版)》第二章&#xff0c;关于“Linux内核模块”的练习题。 创建内核模块 注&#xff1a;我这里是基于阿里云的轻量应用服务器&#xff08;即当前博客服务器&#xff09; 首…

【Linux】分布式监控 Zabbix

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Zabbix 介绍zabbix 概述Zabbix 监控原理Zabbix 6.0 新特性Zabbix 6.0 功能组件 Zabbix 6.0 部署Zabbix 添加客户端主机Zabbix 自定义监控内容Zabbix 自动发现与自动…

Python+Requests+Excel接口测试实战

1、EXCEL文件接口保存方式&#xff0c;如图。 2、然后就是读取EXCEL文件中的数据方法&#xff0c;如下&#xff1a; 1 import xlrd2 3 4 class readExcel(object):5 def __init__(self, path):6 self.path path7 8 property9 def getSheet(self): 10 …