Fabian
Fabian

Reputation: 34

Forward HTTP requests to another webserver (port) BPF/XDP

I am trying to route my GET requests to another port using XDP. I am aware of the StackOverflow of 3 years ago.

I created the following code:

        int xdp_program(struct xdp_md *ctx)
    {
        void *data_end = (void *)(long)ctx->data_end;
        void *data = (void *)(long)ctx->data;
    
        struct ethhdr *eth = data;
        if (eth + 1 > (struct ethhdr *)data_end)
        {
            bpf_printk("Invalid ETHERNET header");
            return XDP_DROP;
        }
    
        struct iphdr *iph = (data + sizeof(struct ethhdr));
        if (iph + 1 > (struct iphdr *)data_end)
        {
            bpf_printk("Invalid IP header");
            return XDP_DROP;
        }
    
        if(iph->protocol == IPPROTO_TCP) {
            struct tcphdr *tcph = (data + sizeof(struct ethhdr) + sizeof(struct iphdr));
            if (tcph + 1 > (struct tcphdr *)data_end)
            {
                bpf_printk("Invalid TCP header");
                return XDP_DROP;
            }
    
            if (tcph->dest == htons(8000))
            {
                if(GetPayload(ctx, eth, iph, tcph) == 1) {
                     tcp->dest = htons(289);
                }    
                return XDP_PASS;
            }
        }
}

I have another webserver running on port 289 which is working correctly when accessed directly, unfortunately, the page isn't showing that webserver. When logging port 289 in XDP, nothing shows up. When using TCPDump on the server, I got a couple of RST and PSH packets. What do I wrong/forget in order to show the webserver running on port 289?

Update 1: I updated my code with an valid TCP Checksum, when logging with TCPDump the traffic still comes in, but now as valid, I am starting to receive syn / syn+ack packets instead of resets.

Hereby is my new code

static __u16 csum_fold_helper(__u32 csum) {
    csum = (csum & 0xffff) + (csum >> 16);
        return ~((csum & 0xffff) + (csum >> 16));
}

static void update_iph_checksum(struct iphdr *iph) 
{
    uint16_t *next_iph_u16 = (uint16_t *)iph;
    uint32_t csum = 0;
    iph->check = 0;
    #pragma clang loop unroll(full)
    for (uint32_t i = 0; i < sizeof(*iph) >> 1; i++) {
        csum += *next_iph_u16++;
    }

    iph->check = ~((csum & 0xffff) + (csum >> 16));
}
    
int xdp_program(struct xdp_md *ctx)
{
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;

    struct ethhdr *eth = data;
    if (eth + 1 > (struct ethhdr *)data_end)
    {
        bpf_printk("Invalid ETHERNET header");
        return XDP_DROP;
    }

    struct iphdr *iph = (data + sizeof(struct ethhdr));
    if (iph + 1 > (struct iphdr *)data_end)
    {
        bpf_printk("Invalid IP header");
        return XDP_DROP;
    }

    if(iph->protocol == IPPROTO_TCP) {
        struct tcphdr *tcph = (data + sizeof(struct ethhdr) + sizeof(struct iphdr));
        if (tcph + 1 > (struct tcphdr *)data_end)
        {
            bpf_printk("Invalid TCP header");
            return XDP_DROP;
        }

        if (tcph->dest == htons(8000))
        {
            if(GetPayload(ctx, eth, iph, tcph) == 1) {
                // Recalculate checksum.
                __u32 csum;
                __u32 new_dest = ntohs(289);
                csum = bpf_csum_diff(&tcph->dest, sizeof(__u32),
                                &new_dest, sizeof(new_dest), ~tcph->check);
            
                tcph->dest = new_dest;
                tcph->check = csum_fold_helper(csum);
                tcp = tcph;
            }    
            return XDP_PASS;
        }
    }
}

Upvotes: 0

Views: 929

Answers (1)

Dylan Reimerink
Dylan Reimerink

Reputation: 7968

You are changing the packet but you are not recomputing the TCP checksum. So when the packet hits the TCP stack it is discarded, a RST packet is sent back to notify the client that the connection is refused.

After you have modified the TCP packet, you should set the checksum field to 0 . Then call bpf_csum_diff on the packet. This will return a checksum difference which you will have to fold to get the expected new checksum. Here is an example from the linux sample programs:

static __always_inline __u16 csum_fold_helper(__u32 csum)
{
    return ~((csum & 0xffff) + (csum >> 16));
}

static __always_inline void ipv4_csum(void *data_start, int data_size,
                      __u32 *csum)
{
    *csum = bpf_csum_diff(0, 0, data_start, data_size, *csum);
    *csum = csum_fold_helper(*csum);
}

static __always_inline int send_icmp4_too_big(struct xdp_md *xdp)
{
  [...]

  iph->check = 0;
  csum = 0;
  ipv4_csum(iph, sizeof(struct iphdr), &csum);
  iph->check = csum;
}

In the example it is used with IPv4, but the same functions work for TCP checksums as well since the same algorithm for calculation is used. Do keep in mind that for TCP, you need to include the header + data not just the header like with IPv4.

Upvotes: 1

Related Questions