默安逐日实验室:XDP的应用实践

1. 网络数据包是如何进入进计算机的

众所周知,网络数据包通常需要在TCP/IP协议栈中进行处理,但网络数据包并不直接进入TCP/IP协议栈;相反,他们直接进入网络接口。因此,在数据包进入 TCP/IP 堆栈之前,它们已经到达计算机内部。到目前为止,大多数应用程序都是在 TCP/IP 堆栈之后处理的。

image-20240118150012600

2. 什么是XDP

XDP,全称eXpress Data Path,是Linux内核提供的一个高可用性和可编程性的网络数据包处理框架,XDP允许在网络数据包到达Linux网络栈之前,在网络驱动程序级别对数据包进行处理。 XDP 使内核有能力在数据包到达网络层时快速处理数据包,它有高性能、高灵活度、低开销等优点。

3. 开始一个简单的XDP项目

挂载XDP程序请谨慎,一条错误的xdp规则是极有可能导致服务器失联的!

以下是一个很简单的XDP示例程序

#include <linux/bpf.h>

SEC("xdp")
//XDP 处理逻辑
int xdp_main(struct xdp_md *ctx)
{
    static int count = 1;
    count++;
    if (count%2)
    {
        return XDP_DROP;
    }
    else
    {
        return XDP_PASS;
    }
}

char __license[] __section("license") = "GPL";

工作时,当数据包进入接口时,计数将加1。当计数为偶数时,数据包被丢弃。

我这里采用了clang来编译XDP程序,不过clang有一定的版本限制,需要10.0以上,同样对于内核版本来说xdp需要4.15以上。

编译命令

clang -O2 -g -Wall -target bpf -c main.c -o xdp.o

当命令执行时,它会生成一个名为 xdp.o 的文件。

image-20240119104600652

当所有准备工作完成后,在网络接口上挂载xdp.o。

ip link set dev ens33 xdp obj xdp.o

如果您不需要此xdp程序,可以将其卸载。

ip link set dev ens33 xdp off

当这个XDP程序处于运行状态时,如果去ping该主机,每两组数据包中,就会有一组无响应,就像如下这样。image-20240119111654161

4. 逐步升级XDP应用功能

4.1 Level 1 使用XDP记录源地址IP

由于XDP是内核应用,而将源地址IP记录到本地又是一个用户态行为,那么我们就需要设法让内核态跟用户态进行交互,在这里我们使用了自带的bpf系统当中的MAP来进行数据交换。Map的本质是结构,它允许用户在内核空间和用户空间之间存储和共享数据。

4.1.1 XDP
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>

#define MAX_ENTRIES 1024

// 定义一个xdp_map结构,其中要包含map的类型、kv的数据类型以及map的最大条数
struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __type(key, __u32);
    __type(value, __u32);
    __uint(max_entries, MAX_ENTRIES);
} xdp_map SEC(".maps");

struct ip_event {
    __u32 src_ip;
};

SEC("xdp")
int xdp_main(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    struct ethhdr *eth = data;
    struct iphdr *ip;
    // 对一些畸形包以及非ipv4的包放行
    if (eth + 1 > data_end) {
        return XDP_PASS;
    }
    if (eth->h_proto != __constant_htons(ETH_P_IP)) {
        return XDP_PASS;
    }

    ip = data + sizeof(*eth);
    if (ip + 1 > data_end) {
        return XDP_PASS;
    }
    struct ip_event evt = {
        .src_ip = ip->saddr,
    };
    __u32 key = 0; 
    __u32 value = evt.src_ip;
    bpf_map_update_elem(&xdp_map, &key, &value, BPF_ANY);
    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

在上述代码当中使用了bpf_map_update_elem接口,这个接口是bpf系统提供用来更新映射Map的。

4.1.2 用户态
package main

import (
	"fmt"
	"log"
	"net"
	"time"

	"github.com/cilium/ebpf"
	"github.com/cilium/ebpf/link"
	"github.com/cilium/ebpf/rlimit"
)

var (
	XDP_PATH = "xdp.o"
	IF       = "ens33"
	XDP_Func = "xdp_main"
	XDP_Map  = "xdp_map"
)

func main() {
	if err := rlimit.RemoveMemlock(); err != nil {
		log.Fatalf("关闭内存锁失败: %v", err)
	}
	// 加载xdp程序规则
	spec, err := ebpf.LoadCollectionSpec(XDP_PATH)
	if err != nil {
		log.Fatalf("XDP 程序加载失败: %v", err)
	}
	coll, err := ebpf.NewCollection(spec)
	if err != nil {
		log.Fatalf("创建新的程序集失败: %v", err)
	}
	defer coll.Close()
	// 在当前的程序集当中寻找名为 xdp_map 的bpfmap对象
	cmdMap := coll.DetachMap(XDP_Map)
	if cmdMap == nil {
		log.Fatalf("在%s当中未找到%s对象", XDP_PATH, XDP_Map)
	}
	// 将网卡名称转换为网卡index
	ifIndex, err := getInterfaceIndex(IF)
	if err != nil {
		log.Fatalf("获取%s索引失败: %v", IF, err)
	}
	// 寻找
	prog := coll.Programs[XDP_Func]
	if prog == nil {
		log.Fatalf("未找到%s方法",XDP_Func)
	}
	link, err := link.AttachXDP(link.XDPOptions{
		Program:   prog,
		Interface: ifIndex,
	})
	defer link.Close()
	// 读取map当中内容
	var key uint32 = 0
	value := make([]byte, 512)

	for {
		err = cmdMap.Lookup(&key, &value)
		if err != nil {
			log.Printf("查询map失败: %v", err)
			time.Sleep(1 * time.Second)
			continue
		}
		fmt.Println(value)
		time.Sleep(1 * time.Second)
	}
}
func getInterfaceIndex(name string) (int, error) {
	ifIndex, err := net.InterfaceByName(name)
	if err != nil {
		return 0, err
	}
	return ifIndex.Index, nil
}

在一通折腾下,达成效果如下,这同时也意味着,成功的将网卡收到的数据经过xdp程序传递到了用户态。

image-20240623174709784

当然对于想要进行使用某一个特定源ip的访问来触发某个事件的场景,也可以将打印的逻辑改掉,改成自己想要的样子。

4.2 Level 2 使用XDP做一个简易的防火墙

第二阶段开始尝试着使用XDP的行为指令来处理数据包,XDP 定义了数据包的五种处理行为

enum xdp_action {
	XDP_ABORTED = 0, // 将数据包丢弃,并抛出异常
	XDP_DROP, // 丢弃数据包
	XDP_PASS, // 放行至内核协议栈
	XDP_TX, // 从收到的这张往卡上再将包发出
	XDP_REDIRECT, // 从其他网卡将包发出,
};
4.2.1 XDP程序

在这个xdp程序当中,使用了bpf_map_lookup_elem来获取被禁止的ip,如果被禁止的ip存在在map当中,那么该ip就会被XDP_DROP行为丢掉

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>

#define MAX_ENTRIES 1024

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __type(key, __u32);
    __type(value, __u32);
    __uint(max_entries, MAX_ENTRIES);
} xdp_map SEC(".maps");

SEC("xdp")
int xdp_main(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    struct ethhdr *eth = data;
    struct iphdr *ip;

    if (eth + 1 > data_end) {
        return XDP_PASS;
    }

    if (eth->h_proto != __constant_htons(ETH_P_IP)) {
        return XDP_PASS;
    }

    ip = data + sizeof(*eth);
    if (ip + 1 > data_end) {
        return XDP_PASS;
    }

    __u32 src_ip = ip->saddr;
    __u32 *value;

    // 检查源 IP 是否在 BPF map 中
    value = bpf_map_lookup_elem(&xdp_map, &src_ip);
    if (value) {
        // 如果存在,则丢弃包
        return XDP_DROP;
    }

    // 否则放行包
    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";
4.2.2 用户态程序

在研究过程中,使用golang来写用户态程序会在将ip的uint32作为key出现问题,可能是遇到了C跟go之间的奇妙羁绊了,所以无奈使用c来写这个用户态程序,值得注意的是在C当中挂载函数bpf_set_link_xdp_fd在较高内核版本当中变为了bpf_xdp_attach

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <linux/bpf.h>

static int xdp_map_fd;
static int ifindex;

// cleanup用来清除xdp程序,若不写善后程序则在用户态程序关闭之后xdp程序还挂载在网卡上
static void cleanup(int sig) {
    if (ifindex) {
        bpf_set_link_xdp_fd(ifindex, -1, 0);
    }
    exit(0);
}

int main(int argc, char **argv) {
    struct bpf_object *obj;
    int prog_fd;
    __u32 key, value;


    signal(SIGINT, cleanup);
    signal(SIGTERM, cleanup);

    obj = bpf_object__open_file(argv[1], NULL);
    if (libbpf_get_error(obj)) {
        fprintf(stderr, "XDP 程序打开失败\n");
        return 1;
    }

    if (bpf_object__load(obj)) {
        fprintf(stderr, "XDP 程序加载失败\n");
        return 1;
    }

    prog_fd = bpf_program__fd(bpf_object__find_program_by_title(obj, "xdp"));
    if (prog_fd < 0) {
        fprintf(stderr, "未查询到xdp程序的fd\n");
        return 1;
    }

    xdp_map_fd = bpf_object__find_map_fd_by_name(obj, "xdp_map");
    if (xdp_ctrl_map_fd < 0) {
        fprintf(stderr, "未查询到map的fd\n");
        return 1;
    }

    ifindex = if_nametoindex(argv[2]);
    if (ifindex == 0) {
        return 1;
    }

    if (bpf_set_link_xdp_fd(ifindex, prog_fd, 0) < 0) {
        return 1;
    }

    char command[256];
    char ip_str[INET_ADDRSTRLEN];
    char action[10];

    while (1) {
        printf("Enter command (e.g., '192.168.11.102 block' or '10.102.11.192 accept'): ");
        fgets(command, sizeof(command), stdin);

        if (sscanf(command, "%s %s", ip_str, action) != 2) {
            fprintf(stderr, "非法输入\n");
            continue;
        }

        key = inet_addr(ip_str);

        if (strcmp(action, "block") == 0) {
            value = 1;
            if (bpf_map_update_elem(xdp_ctrl_map_fd, &key, &value, BPF_ANY) != 0) {
                perror("数据压入失败");
            } else {
                printf("Blocked IP address: %s\n", ip_str);
            }
        } else if (strcmp(action, "accept") == 0) {
            if (bpf_map_delete_elem(xdp_map_fd, &key) != 0) {
                perror("数据删除失败");
            } else {
                printf("Accepted IP address: %s\n", ip_str);
            }
        } else {
            fprintf(stderr, "未知操作: %s\n", action);
        }
    }

    return 0;
}

// 编译命令gcc -o user user.c -l:libbpf.a -lelf -lz

下面则是正常情况下golang对map进行更新的操作

// 使用golang对map进行更新
xdpMap := coll.Maps[XDP_Map]
xdpMap.Update(unsafe.Pointer(&key), unsafe.Pointer(&value), ebpf.UpdateAny)

在这个测试当中,我对11.1这个ip先进行了封禁,又开了放行,这样用户态和xdp程序联动的防火墙就做好了

image-20240620153349576

image-20240620153333031

4.3 Level 3 使用 XDP 拦截特定的请求包,并发送给用户态

第三阶段就是设法将XDP的数据包传递给用户态程序,并让用户态根据收到的数据进行一些操作。这里参考leveryd师傅的项目,使用连接起来最简单的udp进行信息的传递,使用map将获取到的信息储存在value当中。

4.3.1 XDP
#include <arpa/inet.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>

#define SIZE1 200
#define SIZE2 180

typedef char PAYLOAD[SIZE1];

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __type(key, __u32);
    __type(value, PAYLOAD);
    __uint(max_entries, 1);
} xdp_map SEC(".maps");

SEC("xdp")
int xdp_main(struct xdp_md *ctx)
{
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    char match_pattern[] = "xdp";
    unsigned int payload_size, i;
    struct ethhdr *eth = data;
    unsigned char *payload;
    struct udphdr *udp;
    struct iphdr *ip;

    __u32 key = 0;
    PAYLOAD value;

    if ((void *)eth + sizeof(*eth) > data_end) {
        return XDP_PASS;
    }

    ip = data + sizeof(*eth);
    if ((void *)ip + sizeof(*ip) > data_end) {
        return XDP_PASS;
    }

    if (ip->protocol != IPPROTO_UDP){
        return XDP_PASS;
    }

    udp = (void *)ip + sizeof(*ip);
    if ((void *)udp + sizeof(*udp) > data_end){
        return XDP_PASS;
    }

    payload_size = ntohs(udp->len) - sizeof(*udp);
    if (payload_size != SIZE1) {
        return XDP_PASS;
    }

    payload = (unsigned char *)udp + sizeof(*udp);
    if ((void *)payload + payload_size > data_end) {
        return XDP_PASS;
    }

    for (i = 0; i < payload_size && payload_size <= SIZE1; i++){
        if (i == sizeof(match_pattern) - 1) {
            break;
        }
        if (payload[i] != match_pattern[i]){
            return XDP_PASS;
        }
    }
        bpf_map_update_elem(&xdp_map, &key, (char *)payload, BPF_ANY);
    return XDP_DROP;
}

char _license[] SEC("license") = "GPL";
4.3.2 用户态
package main

import (
	"fmt"
	"log"
	"net"
	"time"

	"github.com/cilium/ebpf"
	"github.com/cilium/ebpf/link"
	"github.com/cilium/ebpf/rlimit"
)

const PayloadSize = 200

var (	XDP_PATH = "xdp.o"
	IF       = "ens33"
	XDP_Func = "xdp_main"
	XDP_Map  = "xdp_map") 

func main() {
	if err := rlimit.RemoveMemlock(); err != nil {
		log.Fatalf("移除内存锁失败: %v", err)
	}
	spec, err := ebpf.LoadCollectionSpec(XDP_PATH)
	if err != nil {
		log.Fatalf("加载 XDP 程序失败: %v", err)
	}
	
	coll, err := ebpf.NewCollection(spec)
	if err != nil {
		log.Fatalf("创建新的集合失败: %v", err)
	}
	defer coll.Close()
	xdpMap := coll.DetachMap(XDP_Map)

	if xdpMap == nil {
		log.Fatalf("在 XDP 程序中找不到 map")
	}
	fmt.Println(xdpMap.FD())
	
	ifIndex, err := getInterfaceIndex(IF)
	if err != nil {
		log.Fatalf("获取接口索引失败: %v", err)
	}
	prog := coll.Programs[XDP_Func]
	if prog == nil {
		log.Fatalf("找不到 % 程序")
	}
	link.AttachXDP(link.XDPOptions{
		Program:   prog,
		Interface: ifIndex,
	})
	
	var key uint32 = 0
	value := make([]byte, 180)
	for {
		value, err = xdpMap.LookupBytes(&key)
		fmt.Println(xdpMap.String())
		if err != nil {
			log.Printf("查找 map 失败: %v", err)
			time.Sleep(1 * time.Second)
			continue
		}
		fmt.Println(value)
		if value[0] != '\x00' {
			fmt.Printf("接收到的值: %s\n", string(value))
			// 重置 map 中的值
			err = xdpMap.Update(key, make([]byte, PayloadSize), ebpf.UpdateAny)
			if err != nil {
				log.Printf("更新 map 失败: %v", err)
			}
		}
		time.Sleep(1 * time.Second)
	}
}
func getInterfaceIndex(name string) (int, error) {
	ifIndex, err := net.InterfaceByName(name)
	if err != nil {
		return 0, err
	}
	return ifIndex.Index, nil
}

由于xdp是在二层处理数据,而端口是四层才有的概念,所以对发往任意端口的包都会被接收(前提是不超过65535),因为记录包内容以后采取的是XDP_DROP行为,所以在流量上看并不能发现这个包未到达,

image-20240624111925272

image-20240624021100965

4.4 Level 4 使用XDP将处理过的请求包发回

4.4.1 XDP_TX

在这个demo当中,我们尝试了对udp包的源地址目的地址进行了对调,并通过xdp挂载的网卡发出,其中用到了XDP_TX行为,XDP_TX默认会将包原封不动的从自己的网卡丢出。

 if (ip->protocol != IPPROTO_UDP)
        return XDP_PASS;

    struct udphdr *udp = (void *)(ip + 1);
    if ((void *)(udp + 1) > data_end)
        return XDP_PASS;
    // 对换mac
    unsigned char tmp_mac[ETH_ALEN];
    __builtin_memcpy(tmp_mac, eth->h_source, ETH_ALEN);
    __builtin_memcpy(eth->h_source, eth->h_dest, ETH_ALEN);
    __builtin_memcpy(eth->h_dest, tmp_mac, ETH_ALEN);
    
    // 对换地址
    __u32 tmp_ip = ip->saddr;
    ip->saddr = ip->daddr;
    ip->daddr = tmp_ip;

    // 对换端口
    __u16 tmp_port = udp->source;
    udp->source = udp->dest;
    udp->dest = tmp_port;
    

    return XDP_TX;

下面是使用起来的效果

image-20240623184432678

4.4.2 修改发出的包

那么利用level2当中的向手段与XDP_TX行为结合,就可以修改包内数据并发出了,以下是实现所用到的三个代码块。

// 定义一个map
struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __type(key, __u32);
    __uint(value_size, 128);
    __uint(max_entries, 1);
} xdp_map SEC(".maps");

// 从map当中获取收到的包
__u32 key = 0;
char *payload = bpf_map_lookup_elem(&xdp_map, &key);  

//将数据包进行修改
char *udp_payload = (char *)(udp + 1);
    if ((void *)udp_payload + 128 > data_end)  
        return XDP_PASS;
 __builtin_memcpy(udp_payload, payload, 128);

在之前golang加载器的基础上加入向map当中压数据的操作,新的加载器就做好了

// golang loader
	key := uint32(0)
	payload := [128]byte{69, 120, 112, 101, 108, 108, 105, 97, 114, 109, 117, 115}
	err = xdpMap.Update(unsafe.Pointer(&key), unsafe.Pointer(&payload), ebpf.UpdateAny)
	if err != nil {
		log.Fatalf("写入map失败: %v", err)
	}

用起来的效果是这个样子的

image-20240623193707516

利用xdp将发包收包的功能进行结合,就可以达到不通过协议栈来进行数据传递了,需要注意的是,如过想要使发回的包正常进入协议栈,还需要对增加ip包与udp包的校验和重新计算,只有校验和正确的包才能进入到传输层端口上。

5. 恶意XDP如何处置

当一台主机发生一些邪门的行为的时候,尤其是网络行为,有几率是被下了恶意的XDP程序,恶意的xdp可以使用bpftool工具进行排查

bpftool prog

image-20240623204808594

prog参数会将所有的prog以及挂载时间都列出来,根据prog可以寻找可疑的xdp运行程序,再使用ip命令可以找出是哪张网卡有xdp的挂载

ip link show

image-20240623205223795

如果发现了恶意程序再使用ip命令进行卸载

ip link set dev <interface> xdp off

参考文献

https://developers.redhat.com/blog/2021/04/01/get-started-with-xdp

https://www.cnblogs.com/bakari/p/10966303.html

https://rexrock.github.io/post/xdp1/

https://www.leveryd.top/2022-08-14-%E8%81%8A%E4%B8%80%E8%81%8A%E5%9F%BA%E4%BA%8E%22ebpf%20xdp%22%E7%9A%84rootkit/

https://developers.redhat.com/blog/2021/04/01/get-started-with-xdp

https://github.com/xdp-project/xdp-tutorial

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

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

相关文章

HTML如何在图片上添加文字

HTML如何在图片上添加文字 当我们开发一个页面&#xff0c;插入图片时&#xff0c;需要有一组文字对图片进行描述。那么HTML中如何在图片上添加文字呢&#xff1f;这篇文章告诉你。 先让我们来看下效果图&#xff1a; 句子“这是一张夜空图片”被放置在了图片的左下角。 那么…

谷粒商城学习-10-docker安装mysql

文章目录 一&#xff0c;拉取MySQL镜像1&#xff0c;搜索MySQL的Docker镜像2&#xff0c;拉取MySQL镜像3&#xff0c;查看已经拉取的镜像 二&#xff0c;创建、启动MySQL容器1&#xff0c;使用docker run创建启动容器2&#xff0c;使用docker ps查看运行状态的容器3&#xff0c…

属性描述符初探——Vue实现数据劫持的基础

目录 属性描述符——Vue实现数据劫持的基础 一、属性描述符是什么&#xff1f; ​编辑 1.1、属性描述符示例 1.2、用属性描述符定义属性及获取对象的属性描述符 1.3、带有读取器和设置器的属性描述符 二、使用属性描述符的情景 2.1、封装和数据隐藏 使用getter和setter…

论文写作全攻略:Kimi辅助下的高效学术写作技巧

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 完成论文写作是一个多阶段的过程&#xff0c;涉及到不同的任务和技能。以下是按不同分类总结的向Kimi提问的prompt&#xff0c;以帮助你在论文写作过程中取得成功&#xff1a; 1. 选题与…

使用kali Linux启动盘轻松破解Windows电脑密码

破解分析文章仅限用于学习和研究目的&#xff1b;不得将上述内容用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。谢谢&#xff01;&#xff01; 效果展示&#xff1a; 使用kali Linux可以轻松破解Windows用户及密码 准备阶段&#xff1a; &#xff08…

RedHat9 | kickstart无人值守批量安装

一、知识补充 kickstart Kickstart是一种用于Linux系统安装的自动化工具&#xff0c;它通过一个名为ks.cfg的配置文件来定义Linux安装过程中的各种参数和设置。 kickstart的工作原理 Kickstart的工作原理是通过记录典型的安装过程中所需人工干预填写的各种参数&#xff0c;…

配置基于用户认证的虚拟主机

添加账号abc [rootlocalhost conf.d]# htpasswd -c /etc/httpd/zhanghao abc New password: Re-type new password: Adding password for user abc添加账号tom [rootlocalhost conf.d]# htpasswd /etc/httpd/zhanghao tom New password: Re-type new password: Adding pa…

QT中QDomDocument读写XML文件

一、XML文件 <?xml version"1.0" encoding"UTF-8"?> <Begin><Type name"zhangsan"><sex>boy</sex><school>Chengdu</school><age>18</age><special>handsome</special>&l…

平安养老险蚌埠中心支公司开展“78奋力前行”健步走活动

7月4日&#xff0c;平安养老保险股份有限公司&#xff08;以下简称“平安养老险”&#xff09;蚌埠中心支公司组织员工在张公山公园开展“78奋力前行”健步走活动&#xff0c;传递保险行业的正能量&#xff0c;展现平安养老险的活力与风采。 平安养老险蚌埠中心支公司员工身着…

【瑞吉外卖 | day02】登录功能完善+员工信息的增改查

文章目录 瑞吉外卖 — day021. 完善登录功能1.1 问题分析1.2 实现 2. 新增员工功能2.1 分析2.2 程序执行过程2.3 程序执行中的异常处理 3. 员工信息分页查询3.1 程序执行过程 4. 启用/禁用员工账号4.1 需求分析4.2 程序执行过程4.3 代码修复 5. 编辑员工信息5.1 需求分析5.2 程…

基于DMAIC降低气缸体水套芯磕碰伤率

在制造业的激烈竞争中&#xff0c;产品质量的提升一直是企业追求的目标。气缸体作为汽车发动机的核心部件&#xff0c;其生产过程中的质量控制尤为重要。今天&#xff0c;深圳天行健企业管理咨询公司就来分享一下如何运用DMAIC&#xff08;定义、测量、分析、改进、控制&#x…

Logstash安装插件失败的问题

Logstash安装插件失败的问题 安装 logstash-output-jdbc 失败 报错为&#xff1a; Unable to download data from https://rubygems.org - Net::OpenTimeout: Failed to open TCP connection to rubygems.org:443 (execution expired) (https://rubygems.org/latest_specs.4.…

[GHCTF 2024 新生赛]UP+——入土为安的第一天

注意&#xff1a;这道题需要脱壳&#xff0c;而且需要改特征值&#xff0c;详细请看 [LitCTF 2024]hello_upx——入土为安的第一天-CSDN博客 脱完壳发现有256这个特殊的数&#xff0c;是rc4类型的题&#xff0c;最后有一个异或 a "9F041CEFA92386B6F56F27B96155FD42&qu…

鸿蒙应用开发之Column容器

这个容器已经使用了很多次,但是还是需要来简单地学习一下,它的主要作用是沿垂直方向布局的容器。 对于布局这种容器,需要看到显示的效果才会比较明白。 先来看下面这个界面: 先在最外面建立一个Column容器,space和方框显示等显示内容都是作为一行进行排列。现在这里设置为…

智能升级,监控无界——全新安全生产生态算法一体机上线

安全生产生态算法一体机 安全生产生态算法一体机是万物纵横推出的一款AI算法软硬一体生态产品&#xff0c;重点面向安全生产领域&#xff0c;采用BM1684强劲AI芯片&#xff0c;内置安全生产场景所需的多种专用AI算法&#xff0c;面向各场景的人员监控、规范作业、异常检测等应…

maven项目启动的时候,自动启动netty

1.写一个监听器,重开一个线程初始化,netty,一定要重开一个线程去启动,要不会阻塞的 public class StartupListener implements ServletContextListener {Overridepublic void contextInitialized(ServletContextEvent sce) {// 在Web应用启动时启动WebSocket服务器new Thread(…

2024 MWC上海:Infobip引领CPaaS新纪元

作为全球通信行业一年一度的顶级盛会&#xff0c;MWC上海2024于近日在上海盛大召开。Infobip以其前瞻性的CPaaS&#xff08;Communication Platform as a Service&#xff0c;通信平台即服务&#xff09;解决方案和广泛的合作伙伴网络&#xff0c;成为了众多参展嘉宾瞩目的焦点…

Leetcode.342 4的幂

给定一个整数&#xff0c;写一个函数来判断它是否是 4 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 整数 n 是 4 的幂次方需满足&#xff1a;存在整数 x 使得 n 4x 示例 1&#xff1a; 输入&#xff1a;n 16 输出&#xff1a;true示…

QT的编译过程(底层逻辑)

qmake -project 用于从源代码生成项目文件&#xff0c;qmake 用于从项目文件生成 Makefile&#xff0c;而 make 用于根据 Makefile 构建项目。 详细解释&#xff1a; qmake -project 这个命令用于从源代码目录生成一个初始的 Qt 项目文件&#xff08;.pro 文件&#xff09;。它…

通过营销本地化解锁全球市场

在一个日益互联的世界里&#xff0c;企业必须接触到全球各地的不同受众。营销本地化是打开这些全球市场的关键。它包括调整营销材料&#xff0c;使其与不同地区的文化和语言细微差别产生共鸣。以下是有效的营销本地化如何推动您的全球扩张&#xff0c;并用实际例子来说明每一点…