clinuxlinux-kernelnetfilter

icmp_hdr is wrong on CentOS 6 kernels


I have a simple netfilter module to test icmp_hdr function:

unsigned int hook_func(
    unsigned int hooknum,
    struct sk_buff *skb,
    const struct net_device *in,
    const struct net_device *out,
    int (*okfn)(struct sk_buff *))
{    
    const struct iphdr *ip_header = ip_hdr(skb);
    if (ip_header && ip_header->protocol == IPPROTO_ICMP)
    {
        const struct icmphdr *icmp_header = icmp_hdr(skb);    
        printk(KERN_INFO "ICMP type %d", icmp_header->type);
    }

    return NF_ACCEPT;
}

static int __init startup(void)
{
    hook_ops.hook     = hook_func;
    hook_ops.hooknum  = NF_INET_PRE_ROUTING;
    hook_ops.pf       = PF_INET;
    hook_ops.priority = NF_IP_PRI_FIRST;

    nf_register_hook(&hook_ops);
    return 0;
}

Then I start to PING the HOST.

On CentOS 6 (2.6.32-754.12.1.el6.x86_64), he printed ICMP type is always 69 (INVALID).

On CentOS 7 (3.10) the result is ICMP_ECHO (8), which is correct.

Any ideas? Was there a bug in 2.6.32 kernel?


Solution

  • There is some difference between mentioned Linux kernels. In 3.10 kernel we can see the transport header setting in ip_rcv() such way:

    skb->transport_header = skb->network_header + iph->ihl*4;
    

    So the transport header is already set before NF_INET_PRE_ROUTING hooks.

    In 2.6 kernel I see nothing similar up to ip_local_deliver_finish():

    __skb_pull(skb, ip_hdrlen(skb));
    
    /* Points to the IP datagram, just past the header. */
    skb_reset_transport_header(skb);
    

    It's just right after NF_INET_LOCAL_IN hooks. So it seems you can't access ICMP-header such a way in mentioned hooks on 2.6.32 kernel. But you can easy make some workaround :)