TGTGInsighttelegram intelligenceLIVE / telegram public index
Back to channels
Welcome to the Black Parade avatar

TGINSIGHT CHAT

Welcome to the Black Parade

@TheB1ackParade

Music

Death has many faces, I look forward to seeing this one.

Subscribers787Current channel subscribers
Tracked posts907Indexed post count
Recent reach2,918Sum of recent post views
Recent posts

Recent posts

Page 44 of 76 · 907 posts

Posted Nov 6

惊人发现,tcpdump -i any 看不到 bridge 的包,而直接 tcpdump -i br 可以抓到。类似的,br 上的 tc bpf 也看不到。 某种程度上我能理解,但是另一角度来看这又可能造成误导,比如 tcpdump -i any 没看到包在 br 上出现,然而 br_netfilter 依然在默默执行。不说 br nf,就连 ip nf 都跑了一遍。爱了!

269 views

Posted Nov 5

最近散步时的随机漫游: 1. 我想要什么样的 tcpdump。一开始的动机是 tcpdump 抓不到 bpf 网络里的包,比如在 ingress 方向有 tc-bpf/xdp 把流量重定向给了其他设备的时候 tcpdump 什么都看不到。当时我还很菜,思路是 tc-dump / xdp-dump,但是现在来看可以直接在 __netif_rx / netif_rx_internal 观测。而思路一旦从 tcpdump 的 “从设备上观测流量” 解放出来,我想要的 tcpdump 就呼之欲出了:在内核任何地址观测。 2. “所以在此表扬一下勇敢尝试第一问 exp 的同学,你们运气很好地发现了这个非预期解。” 没错,勇气+运气,还有想象力和好奇心,我不止一次地赞美这几个在我心中最重要的工程师品质。可惜加班会摧毁这一切,所以我如此仇恨加班,心想若非绝路,绝不向那些血汗巨兽低头。 3. 用 DRY 的思路切分函数真的好糟糕,很容易导致语义过度扩张、参数数量庞大、无数 if 嵌套。但是如果已经是屎山,打补丁而不是搬山的好处之一是,如果同时维护多版本,补丁可简单 backport。而 bpf c 的抽象能力也众所周知得烂,这时我目光看向了 bpf rust,这大概是我第一次觉得 rust 可能大显身手( 4. 在缺少实现细节的时候,调查一件怪事为什么发生比调查一件期望中的事为什么没发生要简单太多了。举个例子,如果陌生人用 nft add table 去做 DNAT,那么 iptables-nft-save 是看不到的,这时候流量莫名其妙发生了地址转换,我根本不知道有人动过 nft,怎么查?我可以列出所有以 skb 为参数的内核函数符号,在入口和出口 attach kprobe bpf 检查 dest ip,如果有变化则说明“就是这个函数干的”。但是如果是我自己加了一条 nft DNAT 规则却没有生效,场面会恶心许多。

298 views

Posted Nov 4

ustc-hackergame2023 又结束了,每次 zzh 和馒头都盛情邀请但我却只在最后一天看了一道题。。。 Komm, süsser Flagge 的第三小题其实比想象中细节更多。很容易想到是加 ipv4 option,但是怎么加,加什么内容,简直是和内核贴身肉搏。 我一开始直接加 'GET / HTTP',果然被内核扔了,pwru 能看到 kfree_skb_reason(SKB_DROP_REASON_NOT_SPECIFIED)。写一段 bpftrace 检查真·调用栈: bpftrace -e 'k:kfree_skb_reason {$skb=(struct sk_buff*)reg("di"); $data=$skb->network_header+$skb->head; if (*(uint8[4]*)((uint8*)$data+16)==pton("1.1.1.1")) {printf("%s %s\n", ksym(*(uint64*)reg("sp"), kstack)} } ' 看到是结果是 ip_rcv -> ip_rcv_finish_core 里扔掉的。看代码,搜 goto drop,定位 if (iph->ihl > 5 && ip_rcv_options(skb, dev)) goto drop; 继续检查,发现 ip_options_compile 返回非零。开始让 gpt 帮我构造一个合法 ip option。是的你们看 wiki 就知道要构造合法 option 我非要撞墙弹回来才落泪。 终于发现用 0x44 + len 可以塞任意字符串,然后遇到题解里所说的 GET 变成 GUT 的问题。这我就轻车熟路了,用 skbdump 在内核抓几个包: skbdump -i any -k tcf_classify,__ip_options_compile 'dst host 1.1.1.1 and tcp[tcpflags] = tcp-syn' 再用 wireshark 肉眼检查,确实是 __ip_options_compile 修改的。 检查代码,搜索 [3] 直接定位 } else if ((optptr[3]&0xF) != IPOPT_TS_PRESPEC) { unsigned int overflow = optptr[3]>>4; if (overflow == 15) { pp_ptr = optptr + 3; goto error; } if (skb) { optptr[3] = (optptr[3]&0xF)|((overflow+1)<<4); opt->is_changed = 1; } 修改逻辑是 >>> def change(c): return chr((ord(c) & 0xf) | (((ord(c)>>4)+1)<<4)) >>> change('E') 'U' 所以可以找一个字节,其修改后的字节是 E: >>> for i in range(100): ... if change(chr(i)) == 'E': print(i) ... 53 但是这又不满足修改的条件 `if ((optptr[3]&0xF) != IPOPT_TS_PRESPEC)`。所以还是像题解一样做就好。 内核因为提供 API 设置 ip option,所以题解应该是最速解法。我用 bpf 做这个属于蚊子轰大炮了。要点有两个: 1. 不能用 bpf_skb_change_head。这个 helper 改变的是 skb->mac_header 但是不改 skb->network_header。应该用 bpf_skb_adjust_room 在 L3 和 L4 之间加空。 2. 别忘了 bpf_l3_csum_replace 修改三层校验和。(tc bpf 修改校验和真愉快,隔壁 xdp bpf 还要手动 fold 真是有种中世纪的美) 赶紧打游戏。。。

243 views

Posted Nov 2

在回头看去年六月发现的 tcp checksum 问题的时候 ( https://t.me/c/1459082815/120 ),我发现 tcp checksum 居然是在 netdev 上做校正的,而 ipv4 checksum 在 skb 生成之时就是准确的。这意味着在 ingress 方向的 tc 和 xdp bpf 看到的 skb 无论如何都是错误 checksum,因为修正校验和的函数 skb_csum_hwoffload_help 在 tcf_classify 之后。 大家都从书本上学过 tcp checksum 不正确会被内核扔掉,我对此的理解是 tcp segment 在生成之时 checksum 就算好了,然而并不如此。错误的理解在仔细排查 bug 时会变成干扰项,因为你不知道这是异常与否。 我一直想搞一个 Linux TCP/IP 的观测系列,名字就叫“真的吗?我不信”。那些大家背得滚瓜烂熟的网络八股,以为自己理解了,其实并没有,至少我不理解。你真的见到过 checksum 异常时被内核扔掉吗?你亲眼观测过滑动窗口对用户态 IO 的影响吗?那些网络上的 iptables chains 执行顺序真的就像他们所说的那样吗?在现代 Linux 网络下,bpf 可以 redirect skb,一个包可以多次遍历内核栈,又有哪些情况和旧时代不同了?我唯一知道的是我一无所知。 以上只是口嗨,紧接着我就开始打游戏。。。

252 views

Posted Oct 30

xt_table tracing 顺利 poc,但是 nft_table 居然连个表名都拿不到 😭

254 views

Posted Oct 29

https://www.bilibili.com/video/BV1qh4y1B7TZ/ 塔防的尽头。。。

246 views

Posted Oct 26

为了追查为什么 nat POSTROUTING 不会处理同一 skb 两次,我今天写了以下 bpftrace 脚本 k:nf_nat_inet_fn { $skb=(struct sk_buff*)reg("si"); $data=$skb->network_header+$skb->head; if (*(uint8[4]*)((uint8*)$data+16)==pton("1.1.1.1") || *(uint8[4]*)((uint8*)$data+16)==pton("1.0.0.1")) { $state=(struct nf_hook_state*)reg("dx"); printf("%d@%s nfct=%llx ", $state->hook, $skb->dev->name, $skb->_nfct); $ct=(struct nf_conn*)($skb->_nfct & ~7); printf("ct->status=%llx nf_nat_initialized=%d do_nf_nat_manip_pkt?=%d\n", $ct->status, $ct->status & (1<<7), $ct->status & (1<<4)); @traced[tid]=true; @tid2ct[tid]=$ct; @cts[$ct]=true; } } kr:nf_nat_inet_fn{ if (@traced[tid]==true) { delete(@traced[tid]); $ct=@tid2ct[tid]; delete(@cts[$ct]); delete(@tid2ct[tid]); printf("retval %d\n", reg("ax")); } } k:nf_nat_manip_pkt { if (@traced[tid]==true) { printf("nf_nat_manip_pkt: skb=%llx\n", reg("di")); } } k:nf_nat_setup_info { $ct=(struct nf_conn*)reg("di"); if (@cts[$ct]==true) { printf("%s\n", ksym(*(uint64*)reg("sp"))); printf("%s\n", kstack); } } 虽然大家缺少上下文不太能看懂我在做什么,但是我还是总结了几点 takeaways: 1. bpftrace 使用熟练之后绝对比 bcc 生产力高得多,更别说 cilium/ebpf。因为在 debugging 场景下,写的脚本需要大量快速修改,看完这个参数的值要看另一个地方 kprobe 并把两处用 tid 关联起来,用 bpftrace 可以十秒钟用上 bpf map,一秒钟做内核符号转换,更别说对 deref 操作的转化,已经是我最爱的 bpftrace 抽象语法。deref 转化是指,*(uint64*)reg("sp") 这句实际上编译成了 bpf_probe_read_kernel 这种繁琐的 helper。我太爱这简化了。 2. 多个 kprobe 的事件关联。这是我非常非常常用的技巧。我们从简单的开始,假如想观测一个内核函数的返回值,输出 "入参->返回”,我们面临的问题是,在 kprobe 处没有返回值,在kretprobe 处没有入参值。所以我最常用的手段是在 kprobe 处把参数用 tid 存到 map 里,在 kretprobe 用 tid 取出来。实际上这甚至可以用在不同函数,我上面的代码里就使用多个不变量在多个 kprobe 之间关联事件。 3. 过滤。这是老大难了,ftrace graph 那么难用,一大原因就是支持的过滤太少。比如我想看 'dst host 1.1.1.1 and dst port 80' 的 skb 在 eth0 上执行这个内核函数时的第三入参 state 的值,没有已有工具能帮你一下子拿到,但是 bpftrace 去做这事并不麻烦。我上面代码里实际过滤的是 'dst host 1.1.1.1 or dst host 1.0.0.1',如果我愿意甚至可以过滤 tcp[tcpflags] = tcp-syn,也不难。 bpftrace 太灵活了,写起来还简单。 4. 畏难心理。就算我自己拥有大量 bpftrace 使用经验,知道各种问题怎么处理,理解底层实现,能读 verifier log 和 bpf opcode,但是逼迫自己用一个没有尝试过的方式去解决一个陌生的问题,依然心生恐惧。这种恐惧阻止人往前走,甚至让人如释重负、放弃思考:这太难了,我解决不了,就这样吧。我有过无数次这样的时候,我也有硬着头皮走下去也依然解决不了的时候,但是,(我就不灌鸡汤了)。 总之都快去用起来 bpftrace!让我们到达神奇的世界创造未知,创造未知的空间,熊熊火焰在胸中凝结~凝结!

277 views

Posted Oct 25

又是被 iptables 日翻股的一天,但是我对着内核代码翻来覆去地看,似乎找到了一种 trace iptables 的纯 bpf 方法,而且似乎和 legacy/nft 无关,ipv4/v6 也统一。对于非匹配的规则,也可以先输出“我们出、入 A 表 B 链啦”,来暗示接下来的空白输出是没有匹配。更重要的是它不修改内存,不添加观测性规则,不使用 ko,还可以很方便地用 pcap filter 过滤,实在是太棒了! 唯一的问题是时间,我的时间😭

230 views

Posted Oct 25

bpftrace 小贴士 比方说我想看 nf_log_buf_close 的调用参数,首先看参数类型 $ s bpftrace -lv kfunc:vmlinux:nf_log_buf_close kfunc:vmlinux:nf_log_buf_close struct nf_log_buf * m 然后检查检查类型定义 $ pahole -C nf_log_buf struct nf_log_buf { unsigned int count; /* 0 4 */ char buf[1020]; /* 4 1020 */ /* size: 1024, cachelines: 16, members: 2 */ }; 然后写 bpftrace bpftrace -e 'kfunc:vmlinux:nf_log_buf_close {printf("%s\n", str(args->m->buf));}' 可惜上面那个会报错,说 str() 要指针但是传入的是 char[]。 这就是大家经常遇到的 bpftrace 类型问题,非常弱智,但如果你不了解他的运作模式会卡住,放弃。 我的解法是直接从结构体取成员退化成手动偏移: s bpftrace -e 'kfunc:vmlinux:nf_log_buf_close {$m=(uint8*)args->m; printf("%s\n", str($m+4));}' 这一套方式虽然原始,失去了一些便捷性,但是扩展了边界,用熟练了之后也很快。 比如接下来我就可以稍加修改,只在 nat POSTROUTING 的时候打印栈 $ s bpftrace -e 'kfunc:vmlinux:nf_log_buf_close {$m=(uint8*)args->m; $buf=str($m+4); if (strcontains($buf, "nat/POSTROUTING")) {printf("%s\n", kstack)}}' 这不比 bcc 之流方便太多了。

241 views

Posted Oct 25

201 views

Posted Oct 24

我感觉这类 “bug” 根因是内核这些功能才一开始没考虑到现代 SDN 的玩法。 比如 skb_scrub 在 5.10 还无条件在 veth_xmit 的时候清空 mark,这是没考虑在 bpf 时代有人会把一对 veth 放在同一 netns。在史前做这种事毫无意义。 比如 iptables nat POSTROUTING 只对 skb 执行一次,在古代毫无问题,但是现在可能会出现一个 skb 多次 host forwarding,就坏掉了。 比如 tproxy server 生产的流量如果直接路由走了,万事大吉,但是 Z 世代居然把流量路由给本地 veth 然后再走一次 host forwarding,这在 sysctl_tcp_early_demux=1 的情况下会被设置上 skb->sk 导致通不过 ip_forward() 检查。 还一个比一个难查,吐了。

213 views

Posted Oct 24

在床上大叫:iptables 我 CNM! nat 表 POSTROUTING 链貌似(但有九成把握)只会对一个 skb 执行一次。如果我的数据面是 veth -> vhost -> vnet -> eth0 且在 veth -> vhost 这一次 host forwarding 的时候执行了 nat POSTROUTING,那么在 vnet -> eth0 的时候就不会执行!这个潜规则貌似(但)只有 nat POSTROUTING 才有,像 mangle POSTROUTING 就听话得像一只鸡。 这也是 iptables 的另一个观测难题:我们可以想办法 trace “哪些规则被命中了”,但是不能简单用 trace 来解释“为什么有些规则看起来应该命中却没有”。 实现上可能是通过 skb->nfct 来控制的,走过一次 nat POSTROUTING 可能被认为是 ct 处理过,如果再走一次害怕会 ct conflict,所以直接标记为不再执行。 但是文档呢?你吗的文档呢?

194 views
12•••5•••10•••15•••20•••25•••30•••35•••40•••4243444546•••50•••55•••60•••65•••70•••7576