Add forwarder logic

This commit is contained in:
Zoltan Papp
2023-08-14 11:29:49 +02:00
parent 6c2b364966
commit f185107268
14 changed files with 452 additions and 41 deletions
+119
View File
@@ -0,0 +1,119 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
// +build arm64be armbe mips mips64 mips64p32 ppc64 s390 s390x sparc sparc64
package forwarder
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
// loadBpf returns the embedded CollectionSpec for bpf.
func loadBpf() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_BpfBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load bpf: %w", err)
}
return spec, err
}
// loadBpfObjects loads bpf and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *bpfObjects
// *bpfPrograms
// *bpfMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadBpf()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// bpfSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfSpecs struct {
bpfProgramSpecs
bpfMapSpecs
}
// bpfSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfProgramSpecs struct {
XdpDnsPortFwd *ebpf.ProgramSpec `ebpf:"xdp_dns_port_fwd"`
}
// bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct {
XdpPortMap *ebpf.MapSpec `ebpf:"xdp_port_map"`
}
// bpfObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfObjects struct {
bpfPrograms
bpfMaps
}
func (o *bpfObjects) Close() error {
return _BpfClose(
&o.bpfPrograms,
&o.bpfMaps,
)
}
// bpfMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfMaps struct {
XdpPortMap *ebpf.Map `ebpf:"xdp_port_map"`
}
func (m *bpfMaps) Close() error {
return _BpfClose(
m.XdpPortMap,
)
}
// bpfPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct {
XdpDnsPortFwd *ebpf.Program `ebpf:"xdp_dns_port_fwd"`
}
func (p *bpfPrograms) Close() error {
return _BpfClose(
p.XdpDnsPortFwd,
)
}
func _BpfClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//go:embed bpf_bpfeb.o
var _BpfBytes []byte
Binary file not shown.
+119
View File
@@ -0,0 +1,119 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64
// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64
package forwarder
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
// loadBpf returns the embedded CollectionSpec for bpf.
func loadBpf() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_BpfBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load bpf: %w", err)
}
return spec, err
}
// loadBpfObjects loads bpf and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *bpfObjects
// *bpfPrograms
// *bpfMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadBpf()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// bpfSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfSpecs struct {
bpfProgramSpecs
bpfMapSpecs
}
// bpfSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfProgramSpecs struct {
XdpDnsPortFwd *ebpf.ProgramSpec `ebpf:"xdp_dns_port_fwd"`
}
// bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct {
XdpPortMap *ebpf.MapSpec `ebpf:"xdp_port_map"`
}
// bpfObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfObjects struct {
bpfPrograms
bpfMaps
}
func (o *bpfObjects) Close() error {
return _BpfClose(
&o.bpfPrograms,
&o.bpfMaps,
)
}
// bpfMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfMaps struct {
XdpPortMap *ebpf.Map `ebpf:"xdp_port_map"`
}
func (m *bpfMaps) Close() error {
return _BpfClose(
m.XdpPortMap,
)
}
// bpfPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct {
XdpDnsPortFwd *ebpf.Program `ebpf:"xdp_dns_port_fwd"`
}
func (p *bpfPrograms) Close() error {
return _BpfClose(
p.XdpDnsPortFwd,
)
}
func _BpfClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//go:embed bpf_bpfel.o
var _BpfBytes []byte
Binary file not shown.
+89
View File
@@ -0,0 +1,89 @@
#include <stdbool.h>
#include <linux/if_ether.h> // ETH_P_IP
#include <linux/udp.h>
#include <linux/ip.h>
#include <netinet/in.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#define bpf_printk(fmt, ...) \
({ \
char ____fmt[] = fmt; \
bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \
})
const __u32 map_key_dns_ip = 0;
const __u32 map_key_dns_port = 1;
struct bpf_map_def SEC("maps") xdp_port_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(__u16),
.max_entries = 10,
};
__be32 dns_ip = 0;
__be16 dns_port = 0;
bool read_port_settings() {
__u16 *value;
__be32 *ip_value;
value = bpf_map_lookup_elem(&xdp_port_map, &map_key_dns_port);
if(!value) {
return false;
}
dns_port = htons(*value);
ip_value = bpf_map_lookup_elem(&xdp_port_map, &map_key_dns_ip);
if(!ip_value) {
return false;
}
dns_ip = htonl(*ip_value);
return true;
}
SEC("xdp")
int xdp_dns_port_fwd(struct xdp_md *ctx) {
if(dns_port == 0) {
if(!read_port_settings()){
return XDP_PASS;
}
bpf_printk("dns port: %d", dns_port);
bpf_printk("dns ip: %d", dns_ip);
}
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
struct iphdr *ip = (data + sizeof(struct ethhdr));
struct udphdr *udp = (data + sizeof(struct ethhdr) + sizeof(struct iphdr));
// return early if not enough data
if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) > data_end){
return XDP_PASS;
}
// skip non IPv4 packages
if (eth->h_proto != htons(ETH_P_IP)) {
return XDP_PASS;
}
if (ip->protocol != IPPROTO_UDP) {
return XDP_PASS;
}
// 2130706433 = 127.0.0.1
if (ip->daddr != dns_ip) {
return XDP_PASS;
}
// skip non dns ports
if (udp->source != htons(53)){
return XDP_PASS;
}
udp->dest = dns_port;
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
@@ -0,0 +1,89 @@
package forwarder
import (
_ "embed"
"encoding/binary"
log "github.com/sirupsen/logrus"
"net"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/rlimit"
)
const (
mapKeyDNSIP uint32 = 0
mapKeyDNSPort uint32 = 1
)
// libbpf-dev, libc6-dev-i386-amd64-cross
//
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang-14 bpf port_fwd.c -- -I /usr/x86_64-linux-gnu/include
type TrafficForwarder struct {
link link.Link
iFaceName string
}
func NewTrafficForwarder(iFace string) *TrafficForwarder {
return &TrafficForwarder{
iFaceName: iFace,
}
}
func (tf *TrafficForwarder) Start(ip string, dnsPort int) error {
log.Debugf("start DNS port forwarder")
// it required for Docker
err := rlimit.RemoveMemlock()
if err != nil {
return err
}
iFace, err := net.InterfaceByName(tf.iFaceName)
if err != nil {
return err
}
// load pre-compiled programs into the kernel.
objs := bpfObjects{}
err = loadBpfObjects(&objs, nil)
if err != nil {
return err
}
defer func() {
_ = objs.Close()
}()
err = objs.XdpPortMap.Put(mapKeyDNSIP, tf.ip2int(ip))
if err != nil {
return err
}
err = objs.XdpPortMap.Put(mapKeyDNSPort, uint16(dnsPort))
if err != nil {
return err
}
defer func() {
_ = objs.XdpPortMap.Close()
}()
tf.link, err = link.AttachXDP(link.XDPOptions{
Program: objs.XdpDnsPortFwd,
Interface: iFace.Index,
})
return err
}
func (tf *TrafficForwarder) Free() error {
if tf.link == nil {
return nil
}
err := tf.link.Close()
tf.link = nil
return err
}
func (tf *TrafficForwarder) ip2int(ipString string) uint32 {
ip := net.ParseIP(ipString)
return binary.BigEndian.Uint32(ip.To4())
}