marcfranc
marcfranc

Reputation: 1

eBPF TCP checksum too big

I am writing simple eBPF program that can change IP and TCP PORT of a packet. When updating the checksums, each time using the helper functions the result is 1 too many. tcpdump output:

00:28:00.743620 IP (tos 0x0, ttl 64, id 20245, offset 0, flags [DF], proto TCP (6), length 60, bad cksum 67f0 (->67ef)!)
    192.168.1.1.60758 > 192.168.1.102.5002: Flags [S], cksum 0xb5de (incorrect -> 0xb5dd), seq 1988705508, win 64240, options [mss 1460,sackOK,TS val 4170540693 ecr 0,nop,wscale 7], length 0
00:28:01.776693 IP (tos 0x0, ttl 64, id 20246, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.1.1.60758 > 192.168.1.102.5002: Flags [S], cksum 0xb1d5 (incorrect -> 0xb1d4), seq 1988705508, win 64240, options [mss 1460,sackOK,TS val 4170541726 ecr 0,nop,wscale 7], length 0
00:28:01.776900 IP (tos 0xc0, ttl 64, id 27418, offset 0, flags [none], proto ICMP (1), length 88)
    192.168.1.101 > 192.168.1.1: ICMP redirect 192.168.1.102 to host 192.168.1.102, length 68
    IP (tos 0x0, ttl 63, id 20246, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.1.1.60758 > 192.168.1.102.5002: Flags [S], cksum 0xb1d5 (incorrect -> 0xb1d4), seq 1988705508, win 64240, options [mss 1460,sackOK,TS val 4170541726 ecr 0,nop,wscale 7], length 0
00:28:02.800554 IP (tos 0x0, ttl 64, id 20247, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.1.1.60758 > 192.168.1.102.5002: Flags [S], cksum 0xadd5 (incorrect -> 0xadd4), seq 1988705508, win 64240, options [mss 1460,sackOK,TS val 4170542750 ecr 0,nop,wscale 7], length 0

My script (may be a little bit messy):

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/in.h>          // For IPPROTO_TCP and IPPROTO_UDP
#include <linux/pkt_cls.h>     // For TC_ACT_OK and other TC actions
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

#define OLD_IP 0xC0A80165 /* 192.168.1.101 in hex */
#define NEW_IP 0xC0A80166 /* 192.168.1.102 in hex */
#define NEW_PORT 5002     /* Destination port in decimal */

SEC("tc")
int modify_packet(struct __sk_buff *skb) {
    void *data = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;

    // Check Ethernet header
    struct ethhdr *eth = data;

    if ((void *)(eth + 1) > data_end)
        return TC_ACT_OK; // Pass the packet if bounds are exceeded

    // Only handle IPv4 packets
    if (eth->h_proto != bpf_htons(ETH_P_IP))
        return TC_ACT_OK;

    // Check IP header
    struct iphdr *ip = (struct iphdr *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return TC_ACT_OK;

    // Only handle TCP packets
    if (ip->protocol != IPPROTO_TCP)
        return TC_ACT_OK;

    // Check if destination IP is 192.168.1.101
    if (ip->daddr != bpf_htonl(OLD_IP))
        return TC_ACT_OK;

    // Save old destination IP for checksum calculation
    __u32 old_ip = ip->daddr;




    __u32 new_dst_ip = bpf_htonl(NEW_IP);
    ip->daddr = new_dst_ip; // Update to the new IP

    //!!!!!!!!!!!!!! IP CHECKSUM - IDK IF VALID BUT WORKS
    __u32 csum_diff = bpf_csum_diff(&old_ip, sizeof(old_ip), &new_dst_ip, sizeof(new_dst_ip),0);
    __u32 new = (~((~ip->check) + csum_diff));
    __u32 second = (new>>8) & 0xF;
    if(second > 0)
    {
        second -= 1;
    }
    new &= ~(0xF << 8);
    new |= (second << 8);
    ip->check = new;
    ////////////////////////////////////////////////////////////






    // Handle TCP header
    struct tcphdr *tcp = (void *)ip + (ip->ihl * 4);
    if ((void *)(tcp + 1) > data_end)
        return TC_ACT_OK;

    // Save old port for checksum calculation
    __u16 old_port = tcp->dest;
    __u16 new_port = bpf_htons(NEW_PORT);
    
    tcp->dest = new_port;

    // Calculate TCP pseudo-header checksum
    bpf_l4_csum_replace(skb, offsetof(struct tcphdr, check), old_ip, new, BPF_F_PSEUDO_HDR);
    bpf_l4_csum_replace(skb, offsetof(struct tcphdr, check), old_port, new_port, 0);

    // AGAIN -1 TO DO
    __u16 hej = tcp->check;
    ////////////////////////////////////
    return TC_ACT_OK; // Pass the modified packet
}

char LICENSE[] SEC("license") = "GPL";

As you can see, in the IP checksum, I manually subtracted 1 bit. Somehow I managed to skim that i had to remove it from the center (not a perfect solution but it works). But when i comes to TCP checksum i dont really know how can i remove this bit from the end. But the real question is - why after using the helper function checksums still dont match?

Upvotes: 0

Views: 69

Answers (1)

pchaigno
pchaigno

Reputation: 13133

According to the bpf_l4_csum_replace description in the BPF helpers manpage, you are not correctly using this helper.

As described, there are two ways of calling that helper:

  1. to compute and set a checksum diff.
  2. to set a pre-computed checksum diff.

You're trying to implement the first case since you're passing the old and new values of the updated fields. In that case, you also need to indicate the size of those fields in the lowest four bits of flags, as follows:

bpf_l4_csum_replace(skb, offsetof(struct tcphdr, check), old_ip, new, BPF_F_PSEUDO_HDR | 4); // For the 4 bytes of the IPv4 address.
bpf_l4_csum_replace(skb, offsetof(struct tcphdr, check), old_port, new_port, BPF_F_PSEUDO_HDR | 2); // For the 2 bytes of the port.

Upvotes: 1

Related Questions