实现透明网桥的主要思路是在内核空间使用 eBPF 程序对网络数据包进行拦截和修改,从而实现对数据包转发的控制。下面是一个基于 eBPF 的简单透明网桥实现,使用 Golang 编写。

  1. 安装依赖项

首先需要安装 libbpf 和 bpftool,这两个工具是 eBPF 程序编写和调试的必备工具。

sudo apt-get install libbpf-dev
sudo apt-get install linux-tools-common linux-tools-generic linux-tools-`uname -r`
  1. 编写 eBPF 程序

eBPF 程序的主要作用是拦截和修改网络数据包,下面是一个简单的 eBPF 程序,它将接收到的数据包转发到指定的端口。

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/in.h>

#define ETH_ALEN    6

struct bpf_map_def SEC("maps") port_map = {
    .type = BPF_MAP_TYPE_HASH,
    .key_size = sizeof(u32),
    .value_size = sizeof(u32),
    .max_entries = 256,
};

SEC("prog")
int forward(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    struct ethhdr *eth = data;
    u32 *port = bpf_map_lookup_elem(&port_map, &eth->h_dest[ETH_ALEN-2]);

    if (!port) {
        return XDP_DROP;
    }

    bpf_clone_redirect(ctx, *port, 0);

    return XDP_PASS;
}

上述 eBPF 程序使用了一个哈希表(port_map)来存储 MAC 地址和端口号的映射关系,当收到一个数据包时,首先从哈希表中查找目的 MAC 地址对应的端口号,如果找到则将数据包转发到该端口,否则丢弃该数据包。

  1. 加载 eBPF 程序

在 Golang 中使用 libbpf 库加载 eBPF 程序,代码如下:

package main

/*
#cgo CFLAGS: -I/usr/include/bpf
#cgo LDFLAGS: -lelf -lz -lbpf

#include <linux/bpf.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>

struct bpf_object *load_bpf_program(char *filename) {
    struct bpf_object *obj;
    int err;

    err = bpf_prog_load(filename, BPF_PROG_TYPE_XDP, &obj, &err);
    if (err) {
        return NULL;
    }

    return obj;
}
*/
import "C"

import (
    "fmt"
    "syscall"
    "unsafe"
)

func main() {
    // 加载 eBPF 程序
    obj := C.load_bpf_program(C.CString("./forward.o"))
    if obj == nil {
        fmt.Println("Failed to load eBPF program")
        return
    }
    defer C.bpf_object__close(obj)

    // 获取 XDP 程序 fd
    prog := C.bpf_object__find_program_by_name(obj, C.CString("forward"))
    if prog == nil {
        fmt.Println("Failed to find XDP program")
        return
    }

    fd := C.bpf_program__fd(prog)
    if fd < 0 {
        fmt.Println("Failed to get XDP program fd")
        return
    }

    // 配置网络接口
    iface := "eth0"
    err := setInterfaceXdpFd(iface, int(fd))
    if err != nil {
        fmt.Println("Failed to set XDP program for interface", iface, err)
        return
    }

    fmt.Println("eBPF program loaded and attached to interface", iface)
}

func setInterfaceXdpFd(iface string, fd int) error {
    sockFd, err := syscall.Socket(syscall.AF_XDP, syscall.SOCK_RAW, 0)
    if err != nil {
        return err
    }

    defer syscall.Close(sockFd)

    ifIndex, err := getInterfaceIndex(iface)
    if err != nil {
        return err
    }

    xdpFlags := syscall.XDP_FLAGS_UPDATE_IF_NOEXIST

    err = syscall.SetsockoptInt(sockFd, syscall.SOL_XDP, syscall.XDP_ATTACH, ifIndex)
    if err != nil {
        return err
    }

    err = syscall.SetsockoptInt(sockFd, syscall.SOL_XDP, syscall.XDP_FLAGS, xdpFlags)
    if err != nil {
        return err
    }

    err = syscall.SetsockoptInt(sockFd, syscall.SOL_XDP, syscall.XDP_FD, fd)
    if err != nil {
        return err
    }

    return nil
}

func getInterfaceIndex(iface string) (int, error) {
    ifReq := syscall.Ifreq{}
    copy(ifReq.IfrnName[:], iface)

    sockFd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
    if err != nil {
        return 0, err
    }

    defer syscall.Close(sockFd)

    err = syscall.Ioctl(sockFd, syscall.SIOCGIFINDEX, uintptr(unsafe.Pointer(&ifReq)))
    if err != nil {
        return 0, err
    }

    return int(ifReq.IfruIndex), nil
}

上述代码首先使用 Cgo 调用 libbpf 库中的函数加载 eBPF 程序,然后使用系统调用将 eBPF 程序绑定到指定的网络接口上。

  1. 测试透明网桥

在测试透明网桥时,需要将两个网络接口(比如 eth0 和 eth1)连接起来,并将它们的 IP 地址、MAC 地址等参数配置成相同的值。这样可以模拟出一个简单的局域网环境。

测试代码如下:

package main

import (
    "fmt"
    "net"
)

func main() {
    conn, err := net.Dial("udp", "192.168.1.100:8888")
    if err != nil {
        fmt.Println("Failed to dial UDP server", err)
        return
    }
    defer conn.Close()

    data := []byte("Hello, world")
    _, err = conn.Write(data)
    if err != nil {
        fmt.Println("Failed to send UDP packet", err)
        return
    }

    fmt.Println("UDP packet sent successfully")
}

上述代码向 192.168.1.100:8888 发送一个 UDP 数据包,由于该 IP 地址不在本机的子网范围内,因此数据包需要通过透明网桥转发到另一个网络接口上。如果一切正常,就应该能够收到该数据包。

完整代码见下:

使用 eBPF 和 Golang 实现透明网桥

原文地址: https://www.cveoy.top/t/topic/luQi 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录