2

更新目标端口后xdp ebpf如何更改校验和tcphdr?

// Check tcp header size
struct tcphdr *tcph = data + nh_off;
nh_off += sizeof(struct tcphdr);
if (data + nh_off > data_end) {
    return XDP_PASS;
}
tcph->dest = bpf_ntohs(5555);
// ... i'm trying change checksum of tcphdr, it's not work for me. 
tcph->check = 0;
tcph->check = checksum((unsigned short *)tcph, sizeof(struct tcphdr));

return XDP_TX;

这是我试图更改 tcp 数据包校验和的功能代码

static inline unsigned short checksum(unsigned short *buf, int bufsz) {
    unsigned long sum = 0;

    while (bufsz > 1) {
        sum += *buf;
        buf++;
        bufsz -= 2;
    }

    if (bufsz == 1) {
        sum += *(unsigned char *)buf;
    }

    sum = (sum & 0xffff) + (sum >> 16);
    sum = (sum & 0xffff) + (sum >> 16);

    return ~sum;
}

我为接口加载了一个 xdp 程序lo,我想将数据包代理到同一网络接口中的端口 5555。

4

4 回答 4

1

除非您正在使用硬件卸载,否则您可能希望使用相关的 BPF 帮助程序bpf_l4_csum_replace()(或替代方法bpf_csum_diff())。

 * int bpf_l4_csum_replace(struct sk_buff *skb, u32 offset, u64 from, u64 to, u64 flags)
 *  Description
 *      Recompute the layer 4 (e.g. TCP, UDP or ICMP) checksum for the
 *      packet associated to *skb*. Computation is incremental, so the
 *      helper must know the former value of the header field that was
 *      modified (*from*), the new value of this field (*to*), and the
 *      number of bytes (2 or 4) for this field, stored on the lowest
 *      four bits of *flags*. Alternatively, it is possible to store
 *      the difference between the previous and the new values of the
 *      header field in *to*, by setting *from* and the four lowest
 *      bits of *flags* to 0. For both methods, *offset* indicates the
 *      location of the IP checksum within the packet. In addition to
 *      the size of the field, *flags* can be added (bitwise OR) actual
 *      flags. With **BPF_F_MARK_MANGLED_0**, a null checksum is left
 *      untouched (unless **BPF_F_MARK_ENFORCE** is added as well), and
 *      for updates resulting in a null checksum the value is set to
 *      **CSUM_MANGLED_0** instead. Flag **BPF_F_PSEUDO_HDR** indicates
 *      the checksum is to be computed against a pseudo-header.
 *
 *      This helper works in combination with **bpf_csum_diff**\ (),
 *      which does not update the checksum in-place, but offers more
 *      flexibility and can handle sizes larger than 2 or 4 for the
 *      checksum to update.
 *
 *      A call to this helper is susceptible to change the underlying
 *      packet buffer. Therefore, at load time, all checks on pointers
 *      previously done by the verifier are invalidated and must be
 *      performed again, if the helper is used in combination with
 *      direct packet access.
 *  Return
 *      0 on success, or a negative error in case of failure.

内核示例或 Cilium 显示了一些示例用法。

如果你不能使用它,这里有一个来自 Netronome 的 eBPF 实现,可能会对你有所帮助。

于 2020-05-28T14:26:21.450 回答
0

如果您只想更改 localhost 的目标端口,那么它也可以在不更新校验和的情况下工作。我试过了,它对我有用。

于 2021-12-05T19:22:02.080 回答
-1

也许我没有弄明白,但是从你提出的例子中,我只改变了我需要的结构的属性并重新计算了 ipv4 数据包校验和:

    __u16 new_port = bpf_ntohs(5555);
    __u32 csum = 0;
    update_header_field(&tcph->check, &tcph->dest, &new_port);
    /* Update IPv4 header checksum */
    iph->check = 0;
    __u16 *p_iph_16 = (__u16 *)iph;
    #pragma clang loop unroll(full)
    for (int i = 0; i < (int)sizeof(*iph) >> 1; i++)
        csum += *p_iph_16++;
    iph->check = ~((csum & 0xffff) + (csum >> 16));

    return XDP_TX;

但结果是一样的,请求没有到达在端口 5555 上运行的服务器。

于 2020-05-28T16:11:38.727 回答
-1
INTERNAL void update_tcp_header_port(struct tcphdr* tcp, __u16 *new_val)
{
    __u16 old_check = tcp->check;
    __u32 new_csum_value;
    __u32 new_csum_comp;
    __u32 undo;

    /* Get old sum of headers by getting one's compliment and adding
     * one's compliment of old header value (effectively subtracking)
     */
    undo = ~((__u32) tcp->check) + ~((__u32) tcp->dest);

    /* Check for old header overflow and compensate
     * Add new header value
     */
    new_csum_value = undo + (undo < ~((__u32) tcp->dest)) + (__u32) *new_val;

    /* Check for new header overflow and compensate */
    new_csum_comp = new_csum_value + (new_csum_value < ((__u32) *new_val));

    /* Add any overflow of the 16 bit value to itself */
    new_csum_comp = (new_csum_comp & 0xFFFF) + (new_csum_comp >> 16);

    /* Check that overflow added above did not cause another overflow */
    new_csum_comp = (new_csum_comp & 0xFFFF) + (new_csum_comp >> 16);

    /* Cast to 16 bit one's compliment of sum of headers */
    // tcp->check = (__u16) ~new_csum_comp;
    tcp->check = (__u16)10494;

    printt("old check: %d, old dest: %d, new port: %d\n", old_check, (__u16)bpf_ntohs(tcp->dest), *new_val);
    /* Update header to new value */
    tcp->dest = (__u16)bpf_ntohs(*new_val);
    return;
}
于 2020-06-02T09:59:08.373 回答