Alexandr Ershov
Alexandr Ershov

Reputation: 55

how xdp ebpf change checksum tcphdr after update dest port

how xdp ebpf change checksum tcphdr after update dest port ?

// 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;

this is the function code with which i am trying to change the checksum of a tcp packet

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;
}

I load an xdp program for the lo interface, and I want to proxy the packet to port 5555 in the same network interface.

Upvotes: 4

Views: 4099

Answers (4)

xyzzz
xyzzz

Reputation: 1

If you only want to change the destination port at localhost then it can work without updating checksum also. I tried it and it works for me.

Upvotes: 0

Alexandr Ershov
Alexandr Ershov

Reputation: 55

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;
}

Upvotes: -1

Alexandr Ershov
Alexandr Ershov

Reputation: 55

maybe I didn’t figure it out, but from the example you proposed, I only change the property of the structure I need and recount the ipv4 packet checksum:

    __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;

but the result is the same, the request does not get to the server running on port 5555.

Upvotes: -1

Qeole
Qeole

Reputation: 9174

Unless you're working with hardware offload, you probably want to use the relevant BPF helper bpf_l4_csum_replace() (or alternatively 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.

The kernel samples or Cilium display some example usage.

If you cannot use it, there is an eBPF implementation from Netronome available here that might help you.

Upvotes: 1

Related Questions