首页
相册
统计
留言
更多
网安工具
CTF工具
关于
Search
1
椭圆曲线加密算法原理(ECC)
5,324 阅读
2
SMBGhost(CVE-2020-0796)漏洞利用
4,302 阅读
3
关于gdb调试
4,065 阅读
4
Arduino使用CubeCell开发板进行LORA无线通信
3,852 阅读
5
Diffie-Hellman密钥协商算法
3,542 阅读
深度学习
技术随笔
应急响应
漏洞复现
流量分析
溯源
入侵检测
Linux
eBPF
服务配置
渗透测试
信息收集
横向攻击
密码学
web安全
CTF
登录
Search
标签搜索
单片机
密码学
Windows
BPF
Python
Linux
Mysql
APP开发
软考
Cobalt Strike
flutter
入侵检测
HSM's Blog
累计撰写
53
篇文章
累计收到
11
条评论
首页
栏目
深度学习
技术随笔
应急响应
漏洞复现
流量分析
溯源
入侵检测
Linux
eBPF
服务配置
渗透测试
信息收集
横向攻击
密码学
web安全
CTF
页面
相册
统计
留言
网安工具
CTF工具
关于
搜索到
2
篇与
的结果
2023-02-23
基于eBPF的Linux主机行为监测工具
使用Go语言编写了一个基于eBPF的Linux僵尸内核探测工具,这个探测工具可以实现根据恶意域名溯源到主机上发起恶意请求的用户和线程等信息。eBPF是一种可以在 Linux 内核中运行用户编写的程序,不需要修改内核代码的技术,eBPF的流程主要为:编译 eBPF 源代码,eBPF 用户态程序加载eBPF字节码加载到内核,内核验证并运行 eBPF 程序,监控主机上运行的进程和触发eBPF程序执行。通过将用于监控的BPF代码attach到BPF的hook点,可以实现探测到主机的DNS、TCP,UDP和线程等的行为eBPF介绍eBPF(extended Berkeley Packet Filter)是一种可以在 Linux 内核中运行用户编写的程序,而不需要修改内核代码或加载内核模块的技术,简单说,eBPF 让 Linux 内核变得可编程化了。eBPF 是一个用 RISC 指令集设计的 VM,他可以通过运行 BPF 程序来跟踪内核函数、内存等。eBPF 程序直接在 Linux 内核中运行。这使他们可以轻松地跟踪操作系统子系统的几乎任何方面,包括 CPU 调度程序、网络、系统调用等。您可以通过 eBPF 程序查看和跟踪操作系统中发生的几乎所有事情。这使其成为在基于 Linux 的部署中设置全局和根深蒂固的监控的可行竞争者。使用BPF系统调用BPF程序流程图:工作流程:编译 eBPF 源代码(把C代码编译成eBPF字节码,本项目通过 go-bindata 库将 bpf 字节码文件内嵌到go文件中)eBPF 用户态程序加载eBPF字节码加载到内核(决定哪些内核区域(代码、内存)可以被这个程序访问)内核验证并运行 eBPF 程序(代码在 kernel/bpf/verifier.c)监控主机上运行的进程(通过 kprobe、tracepoint 来把这些代码插入到对应的位置)触发eBPF程序执行(触发程序后 bpf 程序代码会把数据写到他们自己的 ringbuffers 或者 key-value maps ,用户态读取这些 ringbuffers 或者 maps 来获取想要的数据。)基于eBPF的Linux僵尸主机行为监测工具具备DNS、TCP,UDP,Thread的Linux内核级僵尸主机监控工具项目结果:输出:请求DNS解析的域名、解析到的IP、用户ID、进程ID、执行的命令等eBPF DNS监控原理1. 在eBPF中创建三个map,分别为start,currres和eventsstruct addrinfo { int ai_flags; /* Input flags. */ int ai_family; /* Protocol family for socket. */ int ai_socktype; /* Socket type. */ int ai_protocol; /* Protocol for socket. */ u32 ai_addrlen; /* Length of socket address. */ // CHANGED from socklen_t struct sockaddr *ai_addr; /* Socket address for socket. */ char *ai_canonname; /* Canonical name for service location. */ struct addrinfo *ai_next; /* Pointer to next in list. */ }; struct val_t { u32 pid; char host[80]; } __attribute__((packed)); struct data_t { u32 pid; u32 uid; u32 af; u32 ip4addr; __int128 ip6addr; char host[80]; } __attribute__((packed)); struct { __uint(type, BPF_MAP_TYPE_HASH); __type(key, u32); __type(value, struct val_t); __uint(max_entries, 1024); } start SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __type(key, u32); __type(value, struct addrinfo **); __uint(max_entries, 1024); } currres SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); } events SEC(".maps");2. hook uprobe/getaddrinfoSEC("uprobe/getaddrinfo") int getaddrinfo_entry(struct pt_regs *ctx) { if (!(ctx)->di) return 0; struct val_t val = {}; bpf_probe_read(&val.host, sizeof(val.host), (void *)PT_REGS_PARM1(ctx)); u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; val.pid = pid; struct addrinfo **res = (struct addrinfo **)(ctx)->cx; // 更新这两个map bpf_map_update_elem(&start, &pid, &val, BPF_ANY); bpf_map_update_elem(&currres, &pid, &res, BPF_ANY); return 0; }3. hook uretprobe/getaddrinfoSEC("uretprobe/getaddrinfo") int getaddrinfo_return(struct pt_regs *ctx) { struct val_t *valp; u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; valp = bpf_map_lookup_elem(&start, &pid); if (valp == 0) { return 0; // missed start } struct addrinfo ***res; res = bpf_map_lookup_elem(&currres, &pid); if (!res || !(*res)) { return 0; // missed entry } u32 uid = bpf_get_current_uid_gid(); struct addrinfo **resx; bpf_probe_read(&resx, sizeof(resx), (struct addrinfo **)res); struct addrinfo *resxx; bpf_probe_read(&resxx, sizeof(resxx), (struct addrinfo **)resx); for (int i = 0; i < 9; i++) // Limit max entries that are considered { struct data_t data = {}; bpf_probe_read(&data.host, sizeof(data.host), (void *)valp->host); bpf_probe_read(&data.af, sizeof(data.af), &resxx->ai_family); if (data.af == AF_INET) { struct sockaddr_in *daddr; bpf_probe_read(&daddr, sizeof(daddr), &resxx->ai_addr); bpf_probe_read(&data.ip4addr, sizeof(data.ip4addr), &daddr->sin_addr.s_addr); } else if (data.af == AF_INET6) { struct sockaddr_in6 *daddr6; bpf_probe_read(&daddr6, sizeof(daddr6), &resxx->ai_addr); bpf_probe_read(&data.ip6addr, sizeof(data.ip6addr), &daddr6->sin6_addr.in6_u.u6_addr32); } data.pid = valp->pid; data.uid = uid; bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data)); break; } bpf_map_delete_elem(&start, &pid); bpf_map_delete_elem(&currres, &pid); return 0; }
2023年02月23日
926 阅读
0 评论
1 点赞
2023-02-23
eBPF入门与实现
eBPF介绍eBPF: extended Berkeley Packet Filter是对BPF(现在称为cBPF: classic BPF)的扩展, 现在尽管还叫做BPF, 但应用场景已经远远超过了它的名称的范畴。它的应用范围的扩大主要得益于这几方面:内核中BPF字节码虚拟机扩展为一个通用的执行引擎执行可节码的安全校验JIT支持,可以直接将字节码指令转成内核可执行的原生指令运行这样在安全性、可编程性和性能方面的提升都使得eBPF在包过滤以外的其他领域获取巨大的应用空间。eBPF的整体架构图如下:eBPF程序分为两部分:内核态部分: 内核中的eBPF字节码程序负责在内核中处理特定事件,并可以将处理的结果通过maps或者perf-event发送到用户空间用户态部分: 用户态部分主要有两方面作用:加载eBPF字节码程序到内核中与内核态eBPF程序之写读写信息eBPF本身是事件驱动触发的机制,因而需要将特定的内核事件与eBPF字节码程序进行关联。eBPF程序的开发及运行的一个典型过程如下:编写eBPF程序,并编译成字节码,目前只能使用CLANG和LLVM编译成eBPF字节码将eBPF程序加载到内核中,内核会校验字节码避免内核崩溃将内核事件与eBPF程序进行关联内核事件发生时,eBPF程序执行,发送信息给用户态程序用户态程序读取相关信息由于要使用CLANG和LLVM来编译eBPF程序,而CentOS7上默认yum安装的CLANG和LLVM的版本比较老,不支持eBPF的编译。可以从这个repo中安装, 在/etc/yum.repos.d/下创建文件c7-llvm.repo文件, 内容如下:[c7-llvm-toolset-9] name=c7-llvm-toolset-9 baseurl=https://buildlogs.centos.org/c7-llvm-toolset-9.0.x86_64/ gpgcheck=0 enabled=1然后执行以下命令进行安装并启用:Kprobe & Uprobe - Linux Tracinguprobe和kprobe经过长期的发展, kprobes/uprobes 机制在事件(events)的基础上分别为内核态和用户态提供了追踪调试的功能, 这也构成了 tracepoint 机制的基础, 后期的很多工具, 比如 perf_events, ftrace 等都是在其基础上演化而来. 参考由 Brendan Gregg 提供的资料来看, kprobes/uprobes 在 Linux 动态追踪层面起到了基石的作用, 如下所示:引用: https://blog.arstercz.com/introduction_to_linux_dynamic_tracing/Kprobe 和 Uprobe 均可以通过 ftrace 的 /sys/debug/tracing interface (基于 debugfs 的用户空间层面的 API) 执行各种跟踪和分析,虽然 ftrace 的内部是复杂的, 不过输出的信息却以简单明了为主,更详细的使用示例可以参考 , 如下图所示, 大致为 ftrace 的原理:Krpobe 介绍kprobe是linux内核的一个重要特性,是其他的内核调试工具(perf, systemtap)的“基础设施”,同时内核 BPF 也依赖 kprobe它利用指令桩原理,截获指令流,并在指令执行前后插入hook函数:如果需要知道内核函数是否被调用、被调用上下文、入参以及返回值,比较简单的方法是加printk,但是效率低。利用kprobe技术,用户可以自定义自己的回调函数,可以再几乎所有的函数中动态插入探测点。当内核执行流程执行到指定的探测函数时,会调用该回调函数,用户即可收集所需的信息了,同时内核最后还会回到原本的正常执行流程。如果用户已经收集足够的信息,不再需要继续探测,则同样可以动态的移除探测点。kprobes技术包括的2种探测手段分别时kprobe 和 kretprobe:kprobe是最基本的探测方式,是实现后两种的基础,它可以在任意的位置放置探测点(就连函数内部的某条指令处也可以),它提供了探测点的调用前、调用后和内存访问出错3种回调方式,分别是pre_handler、post_handler和fault_handler,其中pre_handler函数将在被探测指令被执行前回调,post_handler会在被探测指令执行完毕后回调(注意不是被探测函数),fault_handler会在内存访问出错时被调用。retprobe从名字种就可以看出其用途了,它同样基于kprobe实现,用于获取被探测函数的返回值。查看kprobe 可hook的表:/sys/kernel/debug/tracing基本使用指南编译镜像的时候配置开启内核:Symbol: FTRACE [=y] Type : boolean Prompt: Tracers Location: (5) -> Kernel hacking Defined at kernel/trace/Kconfig:132 Depends on: TRACING_SUPPORT [=y] Symbol: KPROBE_EVENT [=y] Type : boolean Prompt: Enable kprobes-based dynamic events Location: -> Kernel hacking (1) -> Tracers (FTRACE [=y]) Defined at kernel/trace/Kconfig:405 Depends on: TRACING_SUPPORT [=y] && FTRACE [=y] && KPROBES [=y] && HAVE_REGS_AND_STACK_ACCESS_API [=y] Selects: TRACING [=y] && PROBE_EVENTS [=y] Symbol: HAVE_KPROBES_ON_FTRACE [=y] Type : boolean Defined at arch/Kconfig:183 Selected by: csky [=y] Symbol: KPROBES_ON_FTRACE [=y] Type : boolean Defined at arch/Kconfig:79 Depends on: KPROBES [=y] && HAVE_KPROBES_ON_FTRACE [=y] && DYNAMIC_FTRACE_WITH_REGS [=y] 终端运行: 首先通过 mount 获得 ftrace debug 接口,然后通过 kprobe_evets 注册你需要 probe 的内核函数,在 tracing/events/kprobes// 下可以控制该 kprobe 函数的 开启和关闭 # mount -t debugfs nodev /sys/kernel/debug/ # echo 'p:myprobe _do_fork dfd=%a0 filename=%a1 flags=%a2 mode=+4($stack)' > /sys/kernel/debug/tracing/kprobe_events # echo 'r:myretprobe _do_fork $retval' >> /sys/kernel/debug/tracing/kprobe_events # echo 1 > /sys/kernel/debug/tracing/events/kprobes/myprobe/enable # echo 1 > /sys/kernel/debug/tracing/events/kprobes/myretprobe/enable # cat /sys/kernel/debug/tracing/trace # tracer: nop # # entries-in-buffer/entries-written: 8/8 #P:1 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | swapper/0-1 [000] dn.. 2.488544: Unknown type 599 swapper/0-1 [000] dn.. 2.489270: Unknown type 600 sh-121 [000] d... 408.113780: myprobe: (_do_fork+0x0/0x3ac) dfd=0xbe485f20 filename=0x0 flags=0x0 mode=0x0 sh-121 [000] d... 408.117058: myretprobe: (sys_clone+0xa6/0xac <- _do_fork) arg1=0x82 sh-121 [000] d... 409.816850: myprobe: (_do_fork+0x0/0x3ac) dfd=0xbe485f20 filename=0x0 flags=0x0 mode=0x0 sh-121 [000] dn.. 409.817539: myretprobe: (sys_clone+0xa6/0xac <- _do_fork) arg1=0x83 sh-121 [000] d... 411.202079: myprobe: (_do_fork+0x0/0x3ac) dfd=0xbe485f20 filename=0x0 flags=0x0 mode=0x0 sh-121 [000] d... 411.202750: myretprobe: (sys_clone+0xa6/0xac <- _do_fork) arg1=0x84内核态只能在/sys/kernel/debug/tracing规定的位置设置 kprobe 桩点Uprobe使用ftrace 基础使用:先准备一个应用程序,测试代码:#include <stdio.h> #include <unistd.h> static void print_curr_state_one(void) { printf("This is the print current state one function\n"); } static void print_curr_state_two(void) { printf("This is the print current state two function\n"); } int main() { while(1) { print_curr_state_one(); sleep(1); print_curr_state_two(); } }编译并获取可执行文件和反汇编 (9 系列请使用 riscv-linux-gcc):➜ csky-linux-gcc test.c -o test ➜ linux-next git:(linux-next-ftrace-kprobe-uprobe-simlutate-insn) ✗ ../artifacts/output_860_next/images/host/bin/csky-linux-objdump -S test 00008518 <print_curr_state_one>: 8518: 1422 subi sp, sp, 8 851a: dd0e2000 st.w r8, (sp, 0) 851e: ddee2001 st.w r15, (sp, 0x4) 8522: 6e3b mov r8, sp 8524: 1006 lrw r0, 0x8698 // 853c <print_curr_state_one+0x24> 8526: eae00007 jsri 0x0 // from address pool at 0x8540 852a: 6c00 or r0, r0 852c: 6fa3 mov sp, r8 852e: d9ee2001 ld.w r15, (sp, 0x4) 8532: d90e2000 ld.w r8, (sp, 0) 8536: 1402 addi sp, sp, 8 8538: 783c rts 853a: 0000 .short 0x0000 853c: 00008698 .long 0x00008698 8540: 00000000 .long 0x00000000 00008544 <print_curr_state_two>: 8544: 1422 subi sp, sp, 8 8546: dd0e2000 st.w r8, (sp, 0) 854a: ddee2001 st.w r15, (sp, 0x4) 854e: 6e3b mov r8, sp 8550: 100d lrw r0, 0x86c8 // 8584 <main+0x1c> 8552: eae0000e jsri 0x0 // from address pool at 0x8588 8556: 6c00 or r0, r0 8558: 6fa3 mov sp, r8 855a: d9ee2001 ld.w r15, (sp, 0x4) 855e: d90e2000 ld.w r8, (sp, 0) 8562: 1402 addi sp, sp, 8 8564: 783c rts ... 00008568 <main>: 8568: 1422 subi sp, sp, 8 856a: dd0e2000 st.w r8, (sp, 0) 856e: ddee2001 st.w r15, (sp, 0x4) 8572: 6e3b mov r8, sp 8574: e3ffffd2 bsr 0x8518 // 8518 <print_curr_state_one> 8578: 3001 movi r0, 1 857a: eae00006 jsri 0x0 // from address pool at 0x8590 857e: e3ffffe3 bsr 0x8544 // 8544 <print_curr_state_two> 8582: 07f9 br 0x8574 // 8574 <main+0xc> ➜ linux-next git:(linux-next-ftrace-kprobe-uprobe-simlutate-insn) ✗ ../artifacts/output_860_next/images/host/bin/csky-linux-readelf -S test There are 28 section headers, starting at offset 0x1cd8: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 00008134 000134 00000d 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 00008144 000144 000020 00 A 0 0 4 [ 3] .hash HASH 00008164 000164 00003c 04 A 4 0 4 [ 4] .dynsym DYNSYM 000081a0 0001a0 0000a0 10 A 5 1 4 [ 5] .dynstr STRTAB 00008240 000240 000098 00 A 0 0 1 [ 6] .gnu.version VERSYM 000082d8 0002d8 000014 02 A 4 0 2 [ 7] .gnu.version_r VERNEED 000082ec 0002ec 000020 00 A 5 1 4 [ 8] .rela.dyn RELA 0000830c 00030c 00009c 0c A 4 0 4 [ 9] .init PROGBITS 000083b0 0003b0 000030 00 AX 0 0 16 [10] .text PROGBITS 000083e0 0003e0 000282 00 AX 0 0 16根据程序反汇编编插 uprobe 桩echo 'p:enter_current_state_one /root/test:0x518 arg0=%a0 lr=%lr' >> /sys/kernel/debug/tracing/uprobe_events echo 'r:exit_current_state_one /root/test:0x518 arg0=%a0' >> /sys/kernel/debug/tracing/uprobe_events echo 'p:enter_current_state_two /root/test:0x544 arg0=%a0 lr=%lr' >> /sys/kernel/debug/tracing/uprobe_events echo 'r:exit_current_state_two /root/test:0x544 arg0=%a0' >> /sys/kernel/debug/tracing/uprobe_events echo 1 > /sys/kernel/debug/tracing/events/uprobes/enable cat /sys/kernel/debug/tracing/trace用户态任意位置都可以设置 uprobe 桩点Perf probe 使用:除了 ftrace,我们还可以使用 Perf probe -x 动态跟踪点是一种可观察性功能,可实现以下功能:甚至不需要重新编译就可以插装源代码的任意行。 有了程序源代码的副本,跟踪点可以在运行时放置在任何地方,并且每个变量的值都可以转储时间执行通过跟踪点。 这是一项非常强大的技术,用于检测其他人编写的复杂系统或代码。perf probe -x /lib/libc.so.6 memcpy perf record -e probe_libc:memcpy -aR ls perf reportBPF学习BPF maps的相关操作eBPF是一个用RISC指令集设计的VM,他可以通过运行BPF程序来跟踪内核函数、内存等。使用BPF系统调用操作BPF:eBPF hook 原理ebpf参数:bpf_tracing.h#define PT_REGS_PARM1(x) ((x)->di) #define PT_REGS_PARM2(x) ((x)->si) #define PT_REGS_PARM3(x) ((x)->dx) #define PT_REGS_PARM4(x) ((x)->cx) #define PT_REGS_PARM5(x) ((x)->r8) #define PT_REGS_RET(x) ((x)->sp) #define PT_REGS_FP(x) ((x)->bp) #define PT_REGS_RC(x) ((x)->ax) #define PT_REGS_SP(x) ((x)->sp) #define PT_REGS_IP(x) ((x)->ip) #define PT_REGS_PARM1_CORE(x) BPF_CORE_READ((x), di) #define PT_REGS_PARM2_CORE(x) BPF_CORE_READ((x), si) #define PT_REGS_PARM3_CORE(x) BPF_CORE_READ((x), dx) #define PT_REGS_PARM4_CORE(x) BPF_CORE_READ((x), cx) #define PT_REGS_PARM5_CORE(x) BPF_CORE_READ((x), r8) #define PT_REGS_RET_CORE(x) BPF_CORE_READ((x), sp) #define PT_REGS_FP_CORE(x) BPF_CORE_READ((x), bp) #define PT_REGS_RC_CORE(x) BPF_CORE_READ((x), ax) #define PT_REGS_SP_CORE(x) BPF_CORE_READ((x), sp) #define PT_REGS_IP_CORE(x) BPF_CORE_READ((x), ip)2.2.1 寄存器寄存器如下 (这里对比 x86_64 做说明):R0: RAX, 存放函数返回值或程序退出状态码 R1: RDI,第一个实参 R2: RSI,第二个实参 R3: RDX,第三个实参 R4: RCX,第四个实参 R5: R8,第五个实参 (别问为啥没有第六个实参) R6: RBX, callee saved R7: R13, callee saved (别问为啥没有R12) R8: R14, callee saved R9: R15, callee saved R10: RBP, 只读栈帧通过寄存器的设计,我们可以看到,每个函数调用允许5个参数,这些参数只允许立即数或者指向自己的ebpf栈(通用内核栈是不被允许的)上的指针,所有的内存访问必须先把数据放到ebpf自己的栈上(512字节的栈),才能被ebpf程序进一步操作。getaddrinfogethostbyname 和 gethostbyaddr 这两个函数仅支持IPv4。getaddrinfo 支持名字到地址以及服务到端口的转换,同时支持IPv4和IPv6。getaddrinfo 使用通用套接字结构 sockaddr,隐藏了具体协议的实现。getaddrinfo 函数原型如下:#include <netdb.h> int getaddrinfo(const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result); - hostname : 主机名或字符串形式地址("1.1.1.1", "1111::9999") - service : 服务名或字符串形式端口号 - hints : 期望返回哪些内容,可以为NULL - results : 查询结果数组指针 - return : 成功返回0,失败返回非0hints 表示希望返回什么结果,比如,只想返回TCP结果,可以让 hints 指向的ai_socktype等于SOCK_DGRAM。返回值中的sockaddr可以直接用于sock函数的调用。struct addrinfo的定义其中,struct addrinfo 的定义如下:struct addrinfo { int ai_flags; /* 标志位 */ int ai_family; /* 协议族: AF_INET|AF_INET6|AF_UNSPEC... */ int ai_socktype; /* sock类型: SOCK_DGRAM|SOCK_STREAM */ int ai_protocol; /* 协议类型: IPPROTO_IP|IPPROTO_IPV6... */ socklen_t ai_addrlen; /* 地址结构长度 */ char *ai_canonname; /* host的正式名称 */ struct sockaddr *ai_addr; /* 地址结构 */ struct addrinfo *ai_next; /* result是个链表结构,ai_next指向下一个addrinfo */ }hints详解hint中 ai_flags 标志及含义如下:表示 含义 AI_PASSIVE 套接字被动打开,服务端调用 AI_CANONNAME 返回主机的规范名字 AI_NUMERICHOST 输入的host必须是字符串表示的地址 AI_NUMERICSERV 输入的service必须是字符串表示的端口号 AI_V4MAPPED 如果ai_family等于AF_INET6,但是只有A记录,没有AAAA记录,那么把IPv4地址映射成IPv6地址返回 AI_ALL 即返回AAAA记录,又返回IPv4映射的IPv6地址 AI_ADDRCONFIG 根据主机地址返回结果,如主机是V4的,那么只返回A记录,如果主机是双栈的,那么也返回AAAA记录gai_strerror以getaddrinfo的返回值作为参数,gai_strerror可以获取错误返回值对应的错误信息。#include <netdb.h> const char *gai_strerror(error); - return : 返回指向错误信息的指针freeaddrinfogetaddrinfo返回的addrinfo中,所有的字段都是动态分配的,调用freeaddrinfo释放分配的内存。#include <netdb.h> void freeaddrinfo(struct addrinfo *ai);
2023年02月23日
976 阅读
0 评论
0 点赞