《动手测试单机百万连接》实验记录(二)
上篇文章,虽然已经成功达到了实验的目标,但是碰到的两个问题,都是在网上搜到的答案,或者说是靠猜测和尝试解决的。能不能凭借内核的一些知识来解决呢?
现在重新看问题一,假设内核并没有任何错误信息,如何定位到具体原因呢?还是运行 bpftrace kfree_skb.bt,kfree_skb.bt 的源码如下:
kprobe:kfree_skb
{
$skb = (struct sk_buff*) arg0;
$ip_header = ((struct iphdr *) ($skb->head + $skb->network_header));
// 这里只捕获 daddr 是 192.168.2.120 的包
if ($ip_header->protocol == IPPROTO_TCP && $ip_header->daddr == 0x7802a8c0)
{
$tcp_header= ((struct tcphdr *) ($skb->head + $skb->network_header + ($ip_header->ihl << 2)));
$sport = $tcp_header->source;
$dport = $tcp_header->dest;
$dport = ($dport >> 8) | (($dport << 8) & 0xff00);
$sport = ($sport >> 8) | (($sport << 8) & 0xff00);
// 这里只捕获 dport 是 8100 的包
if ($dport == 8100) {
@[kstack(11)] = count();
time("%H:%M:%S ");
printf("%s:%d > %s:%d\n", ntop($ip_header->saddr), $sport, ntop($ip_header->daddr), $dport);
}
}
}
得到的结果如下
@[
kfree_skb_reason+1
nf_hook_slow_list+113
ip_sublist_rcv+505
ip_list_rcv+249
__netif_receive_skb_list_core+553
netif_receive_skb_list_internal+403
napi_complete_done+122
e1000e_poll+218
__napi_poll+48
net_rx_action+389
__softirqentry_text_start+213
]: 42
查看nf_hook_slow_list的源码,它调用了nf_hook_slow,接下来还是用./funcgraph -a ip_sublist_rcv
查看nf_hook_slow_list的下游调用链,结果抓取到了很多结果,究竟哪一个才是丢包时候的调用链呢?ftrace只支持进程id的过滤,而收包过程是软中断时内核线程来完成的,而且提前并不能知道。
于是我查询ftrace的手册,看到可以调用内核函数trace_printk往ftrace的结果中插入自定义的comment,而ebpf的辅助函数bpf_trace_printk正好可以做到这一点。我们可以利用这一点往调用nf_hook_slow(并且是处理丢包的那个skb的调用链)的地方插入comment,运行bpftrace nf_hook_slow.bt
(注意bpftrace的版本>=0.17),nf_hook_slow.bt的代码如下:
kprobe:nf_hook_slow
{
$skb = (struct sk_buff*) arg1;
$ip_header = ((struct iphdr *) ($skb->head + $skb->network_header));
if ($ip_header->protocol == IPPROTO_TCP && $ip_header->daddr == 0x7802a8c0)
{
$tcp_header= ((struct tcphdr *) ($skb->head + $skb->network_header + ($ip_header->ihl << 2)));
$sport = $tcp_header->source;
$dport = $tcp_header->dest;
$dport = ($dport >> 8) | (($dport << 8) & 0xff00);
$sport = ($sport >> 8) | (($sport << 8) & 0xff00);
if ($dport == 8100) {
time("%H:%M:%S ");
printf("%s:%d > %s:%d\n", ntop($ip_header->saddr), $sport, ntop($ip_header->daddr), $dport);
debugf("%d > %d", $sport, $dport);
}
}
}
kprobe:kfree_skb
{
$skb = (struct sk_buff*) arg0;
$ip_header = ((struct iphdr *) ($skb->head + $skb->network_header));
// 这里只捕获 daddr 是 192.168.2.120 的包
if ($ip_header->protocol == IPPROTO_TCP && $ip_header->daddr == 0x7802a8c0)
{
$tcp_header= ((struct tcphdr *) ($skb->head + $skb->network_header + ($ip_header->ihl << 2)));
$sport = $tcp_header->source;
$dport = $tcp_header->dest;
$dport = ($dport >> 8) | (($dport << 8) & 0xff00);
$sport = ($sport >> 8) | (($sport << 8) & 0xff00);
// 这里只捕获 dport 是 8100 的包
if ($dport == 8100) {
time("%H:%M:%S ");
printf("%s:%d > %s:%d\n", ntop($ip_header->saddr), $sport, ntop($ip_header->daddr), $dport);
}
}
}
再次运行./funcgraph -a ip_sublist_rcv
可以看到在丢包的调用链nf_hook_slow会出现一行comment,类似/* bpf_trace_printk: 48876 > 8100 */
,这样我们就可以精准的定位到丢包时刻的调用链了,接下来就是根据调用链来看源码了,先贴一下funcgraph抓到的调用链结果:
274972.252065 | 5) ksoftir-47 | | ip_sublist_rcv() {
274972.252066 | 5) ksoftir-47 | 0.961 us | __rcu_read_lock();
274972.252068 | 5) ksoftir-47 | | nf_hook_slow_list() {
274972.252070 | 5) ksoftir-47 | | nf_hook_slow() {
274972.252071 | 5) ksoftir-47 | | kprobe_ftrace_handler() {
274972.252424 | 5) ksoftir-47 | | /* bpf_trace_printk: 48876 > 8100 */
274972.252434 | 5) ksoftir-47 | ! 363.954 us | }
274972.252436 | 5) ksoftir-47 | 1.257 us | ip_sabotage_in [br_netfilter]();
274972.252439 | 5) ksoftir-47 | 1.213 us | ipv4_conntrack_defrag [nf_defrag_ipv4]();
274972.252441 | 5) ksoftir-47 | | ipv4_conntrack_in [nf_conntrack]() {
274972.252442 | 5) ksoftir-47 | | nf_conntrack_in [nf_conntrack]() {
274972.252443 | 5) ksoftir-47 | 1.046 us | get_l4proto [nf_conntrack]();
274972.252445 | 5) ksoftir-47 | | resolve_normal_ct [nf_conntrack]() {
274972.252446 | 5) ksoftir-47 | | nf_ct_get_tuple [nf_conntrack]() {
274972.252448 | 5) ksoftir-47 | 0.996 us | nf_ct_get_tuple_ports.constprop.0 [nf_conntrack]();
274972.252449 | 5) ksoftir-47 | 2.966 us | }
274972.252450 | 5) ksoftir-47 | 1.272 us | hash_conntrack_raw [nf_conntrack]();
274972.252453 | 5) ksoftir-47 | | __nf_conntrack_find_get [nf_conntrack]() {
274972.252454 | 5) ksoftir-47 | 0.987 us | __rcu_read_lock();
274972.252457 | 5) ksoftir-47 | 1.017 us | __rcu_read_unlock();
274972.252459 | 5) ksoftir-47 | 6.287 us | }
274972.252460 | 5) ksoftir-47 | | init_conntrack.constprop.0 [nf_conntrack]() {
274972.252461 | 5) ksoftir-47 | 1.261 us | nf_ct_invert_tuple [nf_conntrack]();
274972.252463 | 5) ksoftir-47 | | __nf_conntrack_alloc [nf_conntrack]() {
274972.252464 | 5) ksoftir-47 | 0.978 us | __rcu_read_lock();
274972.252467 | 5) ksoftir-47 | 1.002 us | __rcu_read_unlock();
274972.252468 | 5) ksoftir-47 | | early_drop [nf_conntrack]() {
274972.252469 | 5) ksoftir-47 | 0.965 us | __rcu_read_lock();
274972.252472 | 5) ksoftir-47 | 1.049 us | __rcu_read_unlock();
274972.252474 | 5) ksoftir-47 | 1.132 us | __rcu_read_lock();
274972.252476 | 5) ksoftir-47 | 0.994 us | __rcu_read_unlock();
274972.252478 | 5) ksoftir-47 | 0.987 us | __rcu_read_lock();
274972.252481 | 5) ksoftir-47 | 1.202 us | __rcu_read_unlock();
274972.252483 | 5) ksoftir-47 | 1.311 us | __rcu_read_lock();
274972.252486 | 5) ksoftir-47 | 0.977 us | __rcu_read_unlock();
274972.252488 | 5) ksoftir-47 | 1.038 us | __rcu_read_lock();
274972.252490 | 5) ksoftir-47 | 1.436 us | __rcu_read_unlock();
274972.252493 | 5) ksoftir-47 | 1.062 us | __rcu_read_lock();
274972.252495 | 5) ksoftir-47 | 0.840 us | __rcu_read_unlock();
274972.252497 | 5) ksoftir-47 | 0.963 us | __rcu_read_lock();
274972.252499 | 5) ksoftir-47 | 0.949 us | __rcu_read_unlock();
274972.252500 | 5) ksoftir-47 | 1.009 us | __rcu_read_lock();
274972.252503 | 5) ksoftir-47 | 1.009 us | __rcu_read_unlock();
274972.252504 | 5) ksoftir-47 | + 36.030 us | }
274972.252506 | 5) ksoftir-47 | | net_ratelimit() {
274972.252507 | 5) ksoftir-47 | 1.163 us | _raw_spin_trylock();
274972.252510 | 5) ksoftir-47 | 1.137 us | _raw_spin_unlock_irqrestore();
274972.252512 | 5) ksoftir-47 | 6.035 us | }
274972.252513 | 5) ksoftir-47 | | _printk() {
274972.252624 | 5) ksoftir-47 | ! 111.979 us | }
274972.252625 | 5) ksoftir-47 | ! 162.001 us | }
274972.252626 | 5) ksoftir-47 | ! 166.517 us | }
274972.252627 | 5) ksoftir-47 | ! 181.756 us | } /* resolve_normal_ct [nf_conntrack] */
274972.252628 | 5) ksoftir-47 | ! 186.205 us | }
274972.252629 | 5) ksoftir-47 | ! 188.247 us | }
274972.252630 | 5) ksoftir-47 | | kfree_skb_reason() {
274972.252632 | 5) ksoftir-47 | | kprobe_ftrace_handler() {
274972.253227 | 5) ksoftir-47 | ! 595.718 us | }
274972.253228 | 5) ksoftir-47 | 1.375 us | skb_release_head_state();
274972.253231 | 5) ksoftir-47 | | skb_release_data() {
274972.253232 | 5) ksoftir-47 | | skb_free_head() {
274972.253233 | 5) ksoftir-47 | 1.419 us | kfree();
274972.253235 | 5) ksoftir-47 | 3.270 us | }
274972.253236 | 5) ksoftir-47 | 5.350 us | }
274972.253237 | 5) ksoftir-47 | | kfree_skbmem() {
274972.253238 | 5) ksoftir-47 | | kmem_cache_free() {
274972.253240 | 5) ksoftir-47 | 1.368 us | __slab_free();
274972.253242 | 5) ksoftir-47 | 3.944 us | }
274972.253243 | 5) ksoftir-47 | 5.910 us | }
274972.253244 | 5) ksoftir-47 | ! 613.834 us | }
274972.253245 | 5) ksoftir-47 | # 1175.182 us | }
274972.253245 | 5) ksoftir-47 | # 1177.527 us | }
274972.253247 | 5) ksoftir-47 | 1.182 us | __rcu_read_unlock();
274972.253249 | 5) ksoftir-47 | 1.148 us | ip_sublist_rcv_finish();
274972.253251 | 5) ksoftir-47 | # 1186.144 us | }
最终可以看到是在调用__nf_conntrack_alloc
时出了问题,源码如下:
static struct nf_conn *
__nf_conntrack_alloc(struct net *net,
const struct nf_conntrack_zone *zone,
const struct nf_conntrack_tuple *orig,
const struct nf_conntrack_tuple *repl,
gfp_t gfp, u32 hash)
{
struct nf_conntrack_net *cnet = nf_ct_pernet(net);
unsigned int ct_count;
struct nf_conn *ct;
if (nf_conntrack_max && unlikely(ct_count > nf_conntrack_max)) {
if (!early_drop(net, hash)) {
if (!conntrack_gc_work.early_drop)
conntrack_gc_work.early_drop = true;
atomic_dec(&cnet->count);
net_warn_ratelimited("nf_conntrack: table full, dropping packet\n");
return ERR_PTR(-ENOMEM);
}
}
然后可以定位到是内核的nf_conntrack_max配置小了,至此,问题一已经定位到了,修改内核即可解决。
本作品采用《CC 协议》,转载必须注明作者和本文链接