c-将HTTP请求转发到另一个Web服务器(端口)BPF/XDP



我正在尝试使用XDP将GET请求路由到另一个端口。我知道3年前的StackOverflow。

我创建了以下代码:

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

我有另一个运行在289端口上的Web服务器,当直接访问时,它可以正常工作,不幸的是,页面没有显示该Web服务器。在XDP中记录端口289时,不会显示任何内容。当在服务器上使用TCPDump时,我得到了几个RST和PSH数据包。为了显示在端口289上运行的Web服务器,我做错了什么/忘记了什么?

更新1:我用有效的TCP校验和更新了我的代码,当用TCPDump登录时,流量仍然会进来,但现在有效了,我开始接收syn/syn+ack数据包,而不是重置。

这是我的新代码

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

您正在更改数据包,但没有重新计算TCP校验和。因此,当数据包到达TCP堆栈时,它会被丢弃,RST数据包会被发回,通知客户端连接被拒绝。

修改TCP数据包后,应将校验和字段设置为0。然后在数据包上调用bpf_csum_diff。这将返回一个校验和差异,您必须将其折叠以获得预期的新校验和。以下是linux示例程序中的一个示例:

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

在本例中,它与IPv4一起使用,但由于使用了相同的计算算法,因此相同的函数也适用于TCP校验和。请记住,对于TCP,您需要包括标头+数据,而不仅仅是IPv4中的标头。

最新更新