代码之家  ›  专栏  ›  技术社区  ›  Romeo

macOS NKE ipf_过滤器IP校验和为0

  •  0
  • Romeo  · 技术社区  · 7 年前

    Im通过IP过滤器拦截、修改和重新注入传出的IPv4 TCP数据包。

    以下是我遵循的程序,我希望有人能够发现错误或提出更好的处理方法:

        static int handle_packet(mbuf_t* data, int ip_len, int dir, ipf_pktopts_t options)
    {
        errno_t result = 0;
        unsigned char packet[1500];
        struct tcphdr *tcp;
        struct ip *ip;
        mbuf_t old_packet = *data, new_packet;
        uint32_t mbufs = 0, packet_bytes = 0;
    
        // zero packet
        bzero(packet, sizeof(packet));
    
        // "finalize" the packet so that it is safe to modify it
        mbuf_outbound_finalize(*data, AF_INET, 0);
    
        // get length of mbuf chain
        do
        {
            mbufs++;
            packet_bytes += mbuf_len(old_packet);
            old_packet = mbuf_next(old_packet);
        } while (old_packet != NULL);
    
        // copy data to local buffer
        if (0 != (result = mbuf_copydata(*data, 0, packet_bytes, packet))) {
            printf("mbuf_copydata returned %d", result);
            return 0;
        }
    
        // pointer to start IP header
        ip = (struct ip*)packet;
        tcp = (struct tcphdr*)((u_int32_t*)ip + ip->ip_hl);
    
        // only consider SYN packet
        if (!(tcp->th_flags & TH_SYN))
            return KERN_SUCCESS;
    
        if (0 != (result = mbuf_dup(*data, MBUF_DONTWAIT, &new_packet)))
        {
            printf("ERROR - mbuf_dup: unable to duplicate mbuf, %d", result);
            return 0;
        }
    
    
    /**
    … I’m modifying the packet and recalculating ip and tcp’s checksums here
    (by previously setting them to 0, so to avoid that the previous values
    are considered in the calculation) …
    */
    
    
        /*
         * Copy buffer back to mbuf
         */
        if (0 != (result = mbuf_copyback(new_packet, 0, ntohs(ip->ip_len), packet, MBUF_DONTWAIT)))
        {
             mbuf_freem(new_packet);
    
            switch (result) {
                case EINVAL:
                    printf("ERROR - handle_packet: mbuf_copyback returned EINVAL");
                    return 0;
                    break;
                case ENOBUFS:
                    printf("ERROR - handle_packet: mbuf_copyback returned ENOBUFS");
                    return 0;
                    break;
                default:
                    break;
            }
        }
    
        // recompute any checksums invalidated by data changes
    //    mbuf_outbound_finalize(new_packet, AF_INET, 0); // -> PANIC(m->m_flags & M_PKTHDR)
    
      // is this necessary?
        mbuf_set_csum_performed(new_packet, MBUF_CSUM_DID_IP | MBUF_CSUM_IP_GOOD | MBUF_CSUM_DID_DATA | MBUF_CSUM_PSEUDO_HDR, checksum_ip(ip));
    
        result = ipf_inject_output(new_packet, ip_filter_ref, options);
    
        return result == 0 ? EJUSTRETURN : result;
    }
    
    static errno_t ip_filter_output(void* cookie, mbuf_t *data, ipf_pktopts_t options)
    {
        struct ip *ip;
        char src[32], dst[32];
        int ip_len;
    
        // pointer to start IP header
        ip = (struct ip*)mbuf_data(*data);
        ip_len = ntohs(ip->ip_len);
    
        bzero(src, sizeof(src));
        bzero(dst, sizeof(dst));
    
        // converts the network address structure into a character string
        inet_ntop(AF_INET, &ip->ip_src, src, sizeof(src));
        inet_ntop(AF_INET, &ip->ip_dst, dst, sizeof(dst));
    
        // avoid congestion and filter only packets from/to tcpcrypt website
        if (ip->ip_p == IPPROTO_TCP
            && mbuf_flags(*data) == MBUF_PKTHDR) {
            return handle_packet(data, ip_len, DIRECTION_OUT /* 1 */, options);
        }
    
        // continue with normal processing of the packet
        return KERN_SUCCESS;
    }
    

    我怀疑得到零是在整个报头上重新计算校验和的预期结果,即它会导致计算出的和自身抵消。

    无论如何,我真的不明白为什么会这样。

    提前非常感谢,

    罗密欧

    2 回复  |  直到 7 年前
        1
  •  3
  •   Mecki    7 年前

    首先,这个

    do
    {
        mbufs++;
        packet_bytes += mbuf_len(old_packet);
        old_packet = mbuf_next(old_packet);
    } while (old_packet != NULL);
    

    size_t totalLength = (
        mbuf_flags(mbuf) & MBUF_PKTHDR ?
        mbuf_pkthdr_len(mbuf) : mbuf_len(mbuf)
    );
    

    因为要么它是一个mbuf链,但该链的第一个mbuf应该有一个paket头,这个paket头包含整个mbuf链的大小(没有这样的头的链根据定义是断开的),要么它只是一个单一的mbuf,但只需询问这一个mbuf的大小就足够了。在整个内核代码中,mbuf链的大小是由我上面的代码检索的。

    那么请理解什么 mbuf_outbound_finalize() 做根据该方法的文件:

    因此,校验和计算是否转移到硬件并不重要,一旦调用该函数,数据包就应该具有正确的校验和。如果您随后修改数据包,则由您再次修复校验和。您可以通过从头开始重新计算校验和来做到这一点,一旦您完成了修改,或者通过“修复”现有的校验和(例如,如果您知道哪些旧数据变成了哪些新数据,则可以“修复”旧校验和以匹配新数据,而不必从头开始计算所有内容,这有点复杂,但会使计算更快)。

    mbuf_outbound_finalize() mbuf_outbound_finalize() 只有在校验和计算尚未完成且已计划在硬件中完成时,才会执行某些操作。如果您的硬件不支持卸载,那么一旦您的过滤器捕捉到数据包,那么该数据包将已经具有正确的校验和,然后 mbuf_outbound_finalize() 什么也不做,因为已经没有什么事情可做了。

    mbuf_set_csum_performed() 。请参阅该功能的文档:

    驱动程序使用它向堆栈指示在硬件中执行了哪些校验和操作。

        2
  •  0
  •   dorian    7 年前

    如果没有,您的NIC可能会进行校验和卸载,这使得驱动程序实际上不会将任何校验和插入数据包(因为网络接口会处理它)。

    section 7.10.2. (Checksum offloading)