Linux가 netif_receive_skb에서 패킷을 삭제하는 이유는 무엇입니까?

Linux가 netif_receive_skb에서 패킷을 삭제하는 이유는 무엇입니까?

저는 Linux 상자를 가지고 있고 외부에서 파일을 수신(다운로드)할 때 tcpdump에서 많은 수(30%)의 TCP 재전송을 확인하고 있습니다. dropwatch 유틸리티를 사용하면 커널 함수 net_receive_skb()에서 많은 패킷 손실이 발생합니다. 이는 NIC가 데이터를 수신했지만 패킷이 처리될 때 일부가 커널에서 삭제되었음을 의미합니다. 삭제된 패킷이 많아 재전송이 필요한 이유를 설명할 수 있습니다.

dropwatch의 출력은 다음과 같습니다.

dropwatch -l kas 
Initalizing kallsyms db
dropwatch> start
Enabling monitoring...
Kernel monitoring activated.
Issue Ctrl-C to stop monitoring
1 drops at tcp_rcv_established+906 (0xffffffff814d0a66)
6 drops at unix_dgram_connect+4ac (0xffffffff8151890c)
6 drops at unix_dgram_connect+4ac (0xffffffff8151890c)
19 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
5 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
9 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
7 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
6 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
14 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
15 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
2 drops at __netif_receive_skb+49f (0xffffffff8147b4ef)
2 drops at inet_csk_reset_xmit_timer.clone.1+265 (0xffffffff814d9cb5) ^CGot a stop message
dropwatch> exit
Shutting down ...

시스템은 커널 2.6.32(centOS 패키지 이름 2.6.32-696.el6.x86_64)가 포함된 CentOS 6.2입니다. 그래서 커널 소스코드에 있는 netif_receive_skb 버전을 보고 패킷 손실의 원인을 찾아보았습니다. kfree_skb(함수 끝 부분)를 호출하면 손실된 패킷에 대한 흔적이 남는 곳을 한 곳만 찾았습니다. 코드는 다음과 같습니다

int netif_receive_skb(struct sk_buff *skb)
{
    struct packet_type *ptype, *pt_prev;
    struct net_device *orig_dev;
    struct net_device *null_or_orig;
    int ret = NET_RX_DROP;
    __be16 type;

    if (!skb->tstamp.tv64)
        net_timestamp(skb);

    if (skb->vlan_tci && vlan_hwaccel_do_receive(skb))
        return NET_RX_SUCCESS;

    /* if we've gotten here through NAPI, check netpoll */
    if (netpoll_receive_skb(skb))
        return NET_RX_DROP;

    if (!skb->iif)
        skb->iif = skb->dev->ifindex;

    null_or_orig = NULL;
    orig_dev = skb->dev;
    if (orig_dev->master) {
        if (skb_bond_should_drop(skb))
            null_or_orig = orig_dev; /* deliver only exact match */
        else
            skb->dev = orig_dev->master;
    }

    __get_cpu_var(netdev_rx_stat).total++;

    skb_reset_network_header(skb);
    skb_reset_transport_header(skb);
    skb->mac_len = skb->network_header - skb->mac_header;

    pt_prev = NULL;

    rcu_read_lock();

#ifdef CONFIG_NET_CLS_ACT
    if (skb->tc_verd & TC_NCLS) {
        skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
        goto ncls;
    }
#endif

    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (ptype->dev == null_or_orig || ptype->dev == skb->dev ||
            ptype->dev == orig_dev) {
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
    }

#ifdef CONFIG_NET_CLS_ACT
    skb = handle_ing(skb, &pt_prev, &ret, orig_dev);
    if (!skb)
        goto out;
ncls:
#endif

    skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
    if (!skb)
        goto out;
    skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
    if (!skb)
        goto out;

    type = skb->protocol;
    list_for_each_entry_rcu(ptype,
            &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
        if (ptype->type == type &&
            (ptype->dev == null_or_orig || ptype->dev == skb->dev ||
             ptype->dev == orig_dev)) {
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
    }

    if (pt_prev) {
        ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
    } else {
        kfree_skb(skb);
        /* Jamal, now you will not able to escape explaining
         * me how you were going to use this. :-)
         */
        ret = NET_RX_DROP;
    }

out:
    rcu_read_unlock();
    return ret;
}

kfree_skb에 대한 호출은 skb->dev가 어떤 프로토콜의 ptype 목록에도 등록되지 않은 경우에만 발생하므로 ptype 목록에 대해 2번의 루프 후에도 pt_prev는 NULL로 유지됩니다. 이는 시스템이 모든 패키지의 작은 하위 집합만 제거하기 때문에 의미가 없습니다. 즉, 장치는 "대부분의 경우 프로토콜 ptype 목록에 등록되지만 때로는 거기에 등록되지 않습니다"라는 의미입니다.

그래서 질문은 - 제가 드롭워치 결과와 netif_receive_skb 코드를 이해하면서 어떤 실수를 저질렀습니까? 이 함수에서 보고된 패킷 손실에 대한 더 합리적인 설명은 무엇입니까?

답변1

아마도 이것은 다음에서 발췌한 것 같습니다.https://access.redhat.com/solutions/657483왜 거기에 떨어졌는지 설명하는 데 도움이 됩니다.

RT 및 RHEL7 커널에는 오류가 아닌 다른 조건에 대해 rx_dropped 카운터를 업데이트하는 코드가 포함되어 있습니다.

  • 소프트 네트워크 백로그가 꽉 찼습니다.
  • 잘못된 VLAN 태그
  • 알 수 없거나 등록되지 않은 프로토콜을 사용하여 패킷을 수신했습니다.
  • 서버가 IPv4 전용으로 구성된 경우 IPv6 프레임

관련 정보