3

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.

Qeole
  • 8,284
  • 1
  • 24
  • 52

4 Answers4

1

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.

Qeole
  • 8,284
  • 1
  • 24
  • 52
0

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.

xyzzz
  • 1
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/30513319) – micke Dec 06 '21 at 10:10
-1

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.

  • `update_header_field()` does the checksum update already, so I don't see why you would attempt to do it again in your example? Also I'm not sure your checksum update algorithm is correct. Nevertheless, the packet should be sent back to the wire even with a wrong checksum. How do you check if your packet arrive? You have two machines A<->B, A sends a packet to B where BPF program runs and sent back to A, so tcpdump on A should show your modified packet? (Feel free to tag me with @Qeole or I may miss your answer) – Qeole May 28 '20 at 19:39
  • Ah but I see you're attaching to the `lo` interface. I'm not sure this is a good idea. I am not entirely sure how packets are processed in that case, but it looks to me they will bounce from `lo` to `lo` in a loop, possibly breaking services on your machine that rely on that interface. What are you trying to achieve, exactly? – Qeole May 29 '20 at 08:10
  • inside the docker container, I want to redirect all tcp traffic to another port, without using iptables or nftables. Also, I should be able to redirect traffic to another ip (nat) also without using iptables or nftables. – Alexandr Ershov May 29 '20 at 19:18
  • Ok so you process incoming traffic directed to a given port, you want to change the destination port, and then... you just let it reach the stack with that new port, right? So after port+checksum update it should return `XDP_PASS` and not `XDP_TX`? You should also be able to change the IP address in your program. You would use `XDP_TX` to send the packet back through the interface (but I do not believe this is what you want here), or `XDP_REDIRECT` with one of the `bpf_redirect*()` helpers to redirect through another interface. I hope this helps? – Qeole May 29 '20 at 22:21
  • can you tell me, if I change the ip packet to the address of another container, will it be possible to proxy this packet to this other container using XDP_REDIRECT in the eth0 interface? – Alexandr Ershov May 30 '20 at 11:40
  • I think so, you would use for example [`bpf_redirect()`](https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/include/uapi/linux/bpf.h?h=v5.6#n1034) as in [this example](https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/samples/bpf/xdp_redirect_kern.c?h=v5.6#n55). – Qeole May 30 '20 at 13:28
  • I encountered a problem when I output the xdp trace of the program to the console, I see some random ports for tcp-> dest there, so I can’t filter the traffic by port? if so, what is the alternative solution using skb? – Alexandr Ershov Jun 01 '20 at 13:01
  • Not easy to tell, I'd suggest opening a new question with code and detailed context. – Qeole Jun 02 '20 at 06:15
  • please help me figure it out, I change the destination port, as you recommended, using the update_header_field function, but when I look in trace_pipe, which port actually changed in the packet header, I see some kind of random port, and not the one I passed in the parameter, can you tell me what is wrong with this function or am I doing something wrong? – Alexandr Ershov Jun 02 '20 at 07:29
  • Could you at least update your answer above to the code you are using now? The current snippet does not seem to update the port at all. The `update_header_field()` function (name is misleading) is used to change the checksum, not the destination port itself, right? – Qeole Jun 02 '20 at 07:57
-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;
}
  • Please update or extend your original question when you want to add additional context, but I would not recommend writing it as an answer---much less as several ones. This is very confusing for future readers trying to find a solution to a similar problem. If you fixed the problem stated in the question but have a follow-up issue, opening a new question (as you did :) ) is generally the correct decision. – Qeole Jun 02 '20 at 20:51