From c9b2ce08eb4ac7d8e22aa46f7f8ce90b9f841c18 Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Tue, 5 Sep 2023 21:14:02 +0200 Subject: [PATCH] DNS forwarder and common ebpf loader (#1083) In case the 53 UDP port is not an option to bind then we hijack the DNS traffic with eBPF, and we forward the traffic to the listener on a custom port. With this implementation, we should be able to listen to DNS queries on any address and still set the local host system to send queries to the custom address on port 53. Because we tried to attach multiple XDP programs to the same interface, I did a refactor in the WG traffic forward code also. --- client/internal/dns/service_listener.go | 64 ++++++++-- .../{wgproxy => ebpf}/ebpf/bpf_bpfeb.go | 15 ++- client/internal/ebpf/ebpf/bpf_bpfeb.o | Bin 0 -> 15960 bytes .../{wgproxy => ebpf}/ebpf/bpf_bpfel.go | 15 ++- client/internal/ebpf/ebpf/bpf_bpfel.o | Bin 0 -> 15960 bytes client/internal/ebpf/ebpf/dns_fwd_linux.go | 51 ++++++++ client/internal/ebpf/ebpf/manager_linux.go | 116 ++++++++++++++++++ .../internal/ebpf/ebpf/manager_linux_test.go | 40 ++++++ client/internal/ebpf/ebpf/src/dns_fwd.c | 64 ++++++++++ client/internal/ebpf/ebpf/src/prog.c | 66 ++++++++++ client/internal/ebpf/ebpf/src/wg_proxy.c | 54 ++++++++ client/internal/ebpf/ebpf/wg_proxy_linux.go | 41 +++++++ client/internal/ebpf/instantiater_linux.go | 15 +++ client/internal/ebpf/instantiater_nonlinux.go | 10 ++ client/internal/ebpf/manager/manager.go | 9 ++ client/internal/wgproxy/ebpf/bpf_bpfeb.o | Bin 6264 -> 0 bytes client/internal/wgproxy/ebpf/bpf_bpfel.o | Bin 6264 -> 0 bytes client/internal/wgproxy/ebpf/loader.go | 84 ------------- client/internal/wgproxy/ebpf/loader_test.go | 18 --- .../internal/wgproxy/ebpf/src/portreplace.c | 90 -------------- client/internal/wgproxy/proxy_ebpf.go | 12 +- iface/iface.go | 2 +- 22 files changed, 553 insertions(+), 213 deletions(-) rename client/internal/{wgproxy => ebpf}/ebpf/bpf_bpfeb.go (84%) create mode 100644 client/internal/ebpf/ebpf/bpf_bpfeb.o rename client/internal/{wgproxy => ebpf}/ebpf/bpf_bpfel.go (84%) create mode 100644 client/internal/ebpf/ebpf/bpf_bpfel.o create mode 100644 client/internal/ebpf/ebpf/dns_fwd_linux.go create mode 100644 client/internal/ebpf/ebpf/manager_linux.go create mode 100644 client/internal/ebpf/ebpf/manager_linux_test.go create mode 100644 client/internal/ebpf/ebpf/src/dns_fwd.c create mode 100644 client/internal/ebpf/ebpf/src/prog.c create mode 100644 client/internal/ebpf/ebpf/src/wg_proxy.c create mode 100644 client/internal/ebpf/ebpf/wg_proxy_linux.go create mode 100644 client/internal/ebpf/instantiater_linux.go create mode 100644 client/internal/ebpf/instantiater_nonlinux.go create mode 100644 client/internal/ebpf/manager/manager.go delete mode 100644 client/internal/wgproxy/ebpf/bpf_bpfeb.o delete mode 100644 client/internal/wgproxy/ebpf/bpf_bpfel.o delete mode 100644 client/internal/wgproxy/ebpf/loader.go delete mode 100644 client/internal/wgproxy/ebpf/loader_test.go delete mode 100644 client/internal/wgproxy/ebpf/src/portreplace.c diff --git a/client/internal/dns/service_listener.go b/client/internal/dns/service_listener.go index 687ca2459..232f6ebc2 100644 --- a/client/internal/dns/service_listener.go +++ b/client/internal/dns/service_listener.go @@ -11,6 +11,9 @@ import ( "github.com/miekg/dns" log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/client/internal/ebpf" + ebpfMgr "github.com/netbirdio/netbird/client/internal/ebpf/manager" ) const ( @@ -24,10 +27,11 @@ type serviceViaListener struct { dnsMux *dns.ServeMux customAddr *netip.AddrPort server *dns.Server - runtimeIP string - runtimePort int + listenIP string + listenPort int listenerIsRunning bool listenerFlagLock sync.Mutex + ebpfService ebpfMgr.Manager } func newServiceViaListener(wgIface WGIface, customAddr *netip.AddrPort) *serviceViaListener { @@ -43,6 +47,7 @@ func newServiceViaListener(wgIface WGIface, customAddr *netip.AddrPort) *service UDPSize: 65535, }, } + return s } @@ -55,13 +60,21 @@ func (s *serviceViaListener) Listen() error { } var err error - s.runtimeIP, s.runtimePort, err = s.evalRuntimeAddress() + s.listenIP, s.listenPort, err = s.evalListenAddress() if err != nil { log.Errorf("failed to eval runtime address: %s", err) return err } - s.server.Addr = fmt.Sprintf("%s:%d", s.runtimeIP, s.runtimePort) + s.server.Addr = fmt.Sprintf("%s:%d", s.listenIP, s.listenPort) + if s.shouldApplyPortFwd() { + s.ebpfService = ebpf.GetEbpfManagerInstance() + err = s.ebpfService.LoadDNSFwd(s.listenIP, s.listenPort) + if err != nil { + log.Warnf("failed to load DNS port forwarder, custom port may not work well on some Linux operating systems: %s", err) + s.ebpfService = nil + } + } log.Debugf("starting dns on %s", s.server.Addr) go func() { s.setListenerStatus(true) @@ -69,9 +82,10 @@ func (s *serviceViaListener) Listen() error { err := s.server.ListenAndServe() if err != nil { - log.Errorf("dns server running with %d port returned an error: %v. Will not retry", s.runtimePort, err) + log.Errorf("dns server running with %d port returned an error: %v. Will not retry", s.listenPort, err) } }() + return nil } @@ -90,6 +104,13 @@ func (s *serviceViaListener) Stop() { if err != nil { log.Errorf("stopping dns server listener returned an error: %v", err) } + + if s.ebpfService != nil { + err = s.ebpfService.FreeDNSFwd() + if err != nil { + log.Errorf("stopping traffic forwarder returned an error: %v", err) + } + } } func (s *serviceViaListener) RegisterMux(pattern string, handler dns.Handler) { @@ -101,11 +122,18 @@ func (s *serviceViaListener) DeregisterMux(pattern string) { } func (s *serviceViaListener) RuntimePort() int { - return s.runtimePort + s.listenerFlagLock.Lock() + defer s.listenerFlagLock.Unlock() + + if s.ebpfService != nil { + return defaultPort + } else { + return s.listenPort + } } func (s *serviceViaListener) RuntimeIP() string { - return s.runtimeIP + return s.listenIP } func (s *serviceViaListener) setListenerStatus(running bool) { @@ -136,10 +164,30 @@ func (s *serviceViaListener) getFirstListenerAvailable() (string, int, error) { return "", 0, fmt.Errorf("unable to find an unused ip and port combination. IPs tested: %v and ports %v", ips, ports) } -func (s *serviceViaListener) evalRuntimeAddress() (string, int, error) { +func (s *serviceViaListener) evalListenAddress() (string, int, error) { if s.customAddr != nil { return s.customAddr.Addr().String(), int(s.customAddr.Port()), nil } return s.getFirstListenerAvailable() } + +// shouldApplyPortFwd decides whether to apply eBPF program to capture DNS traffic on port 53. +// This is needed because on some operating systems if we start a DNS server not on a default port 53, the domain name +// resolution won't work. +// So, in case we are running on Linux and picked a non-default port (53) we should fall back to the eBPF solution that will capture +// traffic on port 53 and forward it to a local DNS server running on 5053. +func (s *serviceViaListener) shouldApplyPortFwd() bool { + if runtime.GOOS != "linux" { + return false + } + + if s.customAddr != nil { + return false + } + + if s.listenPort == defaultPort { + return false + } + return true +} diff --git a/client/internal/wgproxy/ebpf/bpf_bpfeb.go b/client/internal/ebpf/ebpf/bpf_bpfeb.go similarity index 84% rename from client/internal/wgproxy/ebpf/bpf_bpfeb.go rename to client/internal/ebpf/ebpf/bpf_bpfeb.go index c4875c3ae..5d2765862 100644 --- a/client/internal/wgproxy/ebpf/bpf_bpfeb.go +++ b/client/internal/ebpf/ebpf/bpf_bpfeb.go @@ -54,13 +54,16 @@ type bpfSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type bpfProgramSpecs struct { - NbWgProxy *ebpf.ProgramSpec `ebpf:"nb_wg_proxy"` + NbXdpProg *ebpf.ProgramSpec `ebpf:"nb_xdp_prog"` } // bpfMapSpecs contains maps before they are loaded into the kernel. // // It can be passed ebpf.CollectionSpec.Assign. type bpfMapSpecs struct { + NbFeatures *ebpf.MapSpec `ebpf:"nb_features"` + NbMapDnsIp *ebpf.MapSpec `ebpf:"nb_map_dns_ip"` + NbMapDnsPort *ebpf.MapSpec `ebpf:"nb_map_dns_port"` NbWgProxySettingsMap *ebpf.MapSpec `ebpf:"nb_wg_proxy_settings_map"` } @@ -83,11 +86,17 @@ func (o *bpfObjects) Close() error { // // It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. type bpfMaps struct { + NbFeatures *ebpf.Map `ebpf:"nb_features"` + NbMapDnsIp *ebpf.Map `ebpf:"nb_map_dns_ip"` + NbMapDnsPort *ebpf.Map `ebpf:"nb_map_dns_port"` NbWgProxySettingsMap *ebpf.Map `ebpf:"nb_wg_proxy_settings_map"` } func (m *bpfMaps) Close() error { return _BpfClose( + m.NbFeatures, + m.NbMapDnsIp, + m.NbMapDnsPort, m.NbWgProxySettingsMap, ) } @@ -96,12 +105,12 @@ func (m *bpfMaps) Close() error { // // It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. type bpfPrograms struct { - NbWgProxy *ebpf.Program `ebpf:"nb_wg_proxy"` + NbXdpProg *ebpf.Program `ebpf:"nb_xdp_prog"` } func (p *bpfPrograms) Close() error { return _BpfClose( - p.NbWgProxy, + p.NbXdpProg, ) } diff --git a/client/internal/ebpf/ebpf/bpf_bpfeb.o b/client/internal/ebpf/ebpf/bpf_bpfeb.o new file mode 100644 index 0000000000000000000000000000000000000000..0559da486ebeb13a7f0dbf21ed0d3bf4d40e76c3 GIT binary patch literal 15960 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 literal 0 HcmV?d00001 diff --git a/client/internal/ebpf/ebpf/dns_fwd_linux.go b/client/internal/ebpf/ebpf/dns_fwd_linux.go new file mode 100644 index 000000000..1b6493692 --- /dev/null +++ b/client/internal/ebpf/ebpf/dns_fwd_linux.go @@ -0,0 +1,51 @@ +package ebpf + +import ( + "encoding/binary" + "net" + + log "github.com/sirupsen/logrus" +) + +const ( + mapKeyDNSIP uint32 = 0 + mapKeyDNSPort uint32 = 1 +) + +func (tf *GeneralManager) LoadDNSFwd(ip string, dnsPort int) error { + log.Debugf("load ebpf DNS forwarder: address: %s:%d", ip, dnsPort) + tf.lock.Lock() + defer tf.lock.Unlock() + + err := tf.loadXdp() + if err != nil { + return err + } + + err = tf.bpfObjs.NbMapDnsIp.Put(mapKeyDNSIP, ip2int(ip)) + if err != nil { + return err + } + + err = tf.bpfObjs.NbMapDnsPort.Put(mapKeyDNSPort, uint16(dnsPort)) + if err != nil { + return err + } + + tf.setFeatureFlag(featureFlagDnsForwarder) + err = tf.bpfObjs.NbFeatures.Put(mapKeyFeatures, tf.featureFlags) + if err != nil { + return err + } + return nil +} + +func (tf *GeneralManager) FreeDNSFwd() error { + log.Debugf("free ebpf DNS forwarder") + return tf.unsetFeatureFlag(featureFlagDnsForwarder) +} + +func ip2int(ipString string) uint32 { + ip := net.ParseIP(ipString) + return binary.BigEndian.Uint32(ip.To4()) +} diff --git a/client/internal/ebpf/ebpf/manager_linux.go b/client/internal/ebpf/ebpf/manager_linux.go new file mode 100644 index 000000000..9dfdc0ad1 --- /dev/null +++ b/client/internal/ebpf/ebpf/manager_linux.go @@ -0,0 +1,116 @@ +package ebpf + +import ( + _ "embed" + "net" + "sync" + + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/rlimit" + log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/client/internal/ebpf/manager" +) + +const ( + mapKeyFeatures uint32 = 0 + + featureFlagWGProxy = 0b00000001 + featureFlagDnsForwarder = 0b00000010 +) + +var ( + singleton manager.Manager + singletonLock = &sync.Mutex{} +) + +// required packages libbpf-dev, libc6-dev-i386-amd64-cross + +// GeneralManager is used to load multiple eBPF programs with a custom check (if then) done in prog.c +// The manager simply adds a feature (byte) of each program to a map that is shared between the userspace and kernel. +// When packet arrives, the C code checks for each feature (if it is set) and executes each enabled program (e.g., dns_fwd.c and wg_proxy.c). +// +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang-14 bpf src/prog.c -- -I /usr/x86_64-linux-gnu/include +type GeneralManager struct { + lock sync.Mutex + link link.Link + featureFlags uint16 + bpfObjs bpfObjects +} + +// GetEbpfManagerInstance return a static eBpf Manager instance +func GetEbpfManagerInstance() manager.Manager { + singletonLock.Lock() + defer singletonLock.Unlock() + if singleton != nil { + return singleton + } + singleton = &GeneralManager{} + return singleton +} + +func (tf *GeneralManager) setFeatureFlag(feature uint16) { + tf.featureFlags = tf.featureFlags | feature +} + +func (tf *GeneralManager) loadXdp() error { + if tf.link != nil { + return nil + } + // it required for Docker + err := rlimit.RemoveMemlock() + if err != nil { + return err + } + + iFace, err := net.InterfaceByName("lo") + if err != nil { + return err + } + + // load pre-compiled programs into the kernel. + err = loadBpfObjects(&tf.bpfObjs, nil) + if err != nil { + return err + } + + tf.link, err = link.AttachXDP(link.XDPOptions{ + Program: tf.bpfObjs.NbXdpProg, + Interface: iFace.Index, + }) + + if err != nil { + _ = tf.bpfObjs.Close() + tf.link = nil + return err + } + return nil +} + +func (tf *GeneralManager) unsetFeatureFlag(feature uint16) error { + tf.lock.Lock() + defer tf.lock.Unlock() + tf.featureFlags &^= feature + + if tf.link == nil { + return nil + } + + if tf.featureFlags == 0 { + return tf.close() + } + + return tf.bpfObjs.NbFeatures.Put(mapKeyFeatures, tf.featureFlags) +} + +func (tf *GeneralManager) close() error { + log.Debugf("detach ebpf program ") + err := tf.bpfObjs.Close() + if err != nil { + log.Warnf("failed to close eBpf objects: %s", err) + } + + err = tf.link.Close() + tf.link = nil + return err +} diff --git a/client/internal/ebpf/ebpf/manager_linux_test.go b/client/internal/ebpf/ebpf/manager_linux_test.go new file mode 100644 index 000000000..956499e5b --- /dev/null +++ b/client/internal/ebpf/ebpf/manager_linux_test.go @@ -0,0 +1,40 @@ +package ebpf + +import ( + "testing" +) + +func TestManager_setFeatureFlag(t *testing.T) { + mgr := GeneralManager{} + mgr.setFeatureFlag(featureFlagWGProxy) + if mgr.featureFlags != 1 { + t.Errorf("invalid faeture state") + } + + mgr.setFeatureFlag(featureFlagDnsForwarder) + if mgr.featureFlags != 3 { + t.Errorf("invalid faeture state") + } +} + +func TestManager_unsetFeatureFlag(t *testing.T) { + mgr := GeneralManager{} + mgr.setFeatureFlag(featureFlagWGProxy) + mgr.setFeatureFlag(featureFlagDnsForwarder) + + err := mgr.unsetFeatureFlag(featureFlagWGProxy) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + if mgr.featureFlags != 2 { + t.Errorf("invalid faeture state, expected: %d, got: %d", 2, mgr.featureFlags) + } + + err = mgr.unsetFeatureFlag(featureFlagDnsForwarder) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + if mgr.featureFlags != 0 { + t.Errorf("invalid faeture state, expected: %d, got: %d", 0, mgr.featureFlags) + } +} diff --git a/client/internal/ebpf/ebpf/src/dns_fwd.c b/client/internal/ebpf/ebpf/src/dns_fwd.c new file mode 100644 index 000000000..5228c7e75 --- /dev/null +++ b/client/internal/ebpf/ebpf/src/dns_fwd.c @@ -0,0 +1,64 @@ +const __u32 map_key_dns_ip = 0; +const __u32 map_key_dns_port = 1; + +struct bpf_map_def SEC("maps") nb_map_dns_ip = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(__u32), + .value_size = sizeof(__u32), + .max_entries = 10, +}; + +struct bpf_map_def SEC("maps") nb_map_dns_port = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(__u32), + .value_size = sizeof(__u16), + .max_entries = 10, +}; + +__be32 dns_ip = 0; +__be16 dns_port = 0; + +// 13568 is 53 in big endian +__be16 GENERAL_DNS_PORT = 13568; + +bool read_settings() { + __u16 *port_value; + __u32 *ip_value; + + // read dns ip + ip_value = bpf_map_lookup_elem(&nb_map_dns_ip, &map_key_dns_ip); + if(!ip_value) { + return false; + } + dns_ip = htonl(*ip_value); + + // read dns port + port_value = bpf_map_lookup_elem(&nb_map_dns_port, &map_key_dns_port); + if (!port_value) { + return false; + } + dns_port = htons(*port_value); + return true; +} + +int xdp_dns_fwd(struct iphdr *ip, struct udphdr *udp) { + if (dns_port == 0) { + if(!read_settings()){ + return XDP_PASS; + } + bpf_printk("dns port: %d", ntohs(dns_port)); + bpf_printk("dns ip: %d", ntohl(dns_ip)); + } + + if (udp->dest == GENERAL_DNS_PORT && ip->daddr == dns_ip) { + udp->dest = dns_port; + return XDP_PASS; + } + + if (udp->source == dns_port && ip->saddr == dns_ip) { + udp->source = GENERAL_DNS_PORT; + return XDP_PASS; + } + + return XDP_PASS; +} \ No newline at end of file diff --git a/client/internal/ebpf/ebpf/src/prog.c b/client/internal/ebpf/ebpf/src/prog.c new file mode 100644 index 000000000..09b649370 --- /dev/null +++ b/client/internal/ebpf/ebpf/src/prog.c @@ -0,0 +1,66 @@ +#include +#include // ETH_P_IP +#include +#include +#include +#include +#include +#include "dns_fwd.c" +#include "wg_proxy.c" + +#define bpf_printk(fmt, ...) \ + ({ \ + char ____fmt[] = fmt; \ + bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \ + }) + +const __u16 flag_feature_wg_proxy = 0b01; +const __u16 flag_feature_dns_fwd = 0b10; + +const __u32 map_key_features = 0; +struct bpf_map_def SEC("maps") nb_features = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(__u32), + .value_size = sizeof(__u16), + .max_entries = 10, +}; + +SEC("xdp") +int nb_xdp_prog(struct xdp_md *ctx) { + __u16 *features; + features = bpf_map_lookup_elem(&nb_features, &map_key_features); + if (!features) { + return XDP_PASS; + } + + 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; + } + + // skip non UPD packages + if (ip->protocol != IPPROTO_UDP) { + return XDP_PASS; + } + + if (*features & flag_feature_dns_fwd) { + xdp_dns_fwd(ip, udp); + } + + if (*features & flag_feature_wg_proxy) { + xdp_wg_proxy(ip, udp); + } + return XDP_PASS; +} +char _license[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/client/internal/ebpf/ebpf/src/wg_proxy.c b/client/internal/ebpf/ebpf/src/wg_proxy.c new file mode 100644 index 000000000..ecfedc6b3 --- /dev/null +++ b/client/internal/ebpf/ebpf/src/wg_proxy.c @@ -0,0 +1,54 @@ +const __u32 map_key_proxy_port = 0; +const __u32 map_key_wg_port = 1; + +struct bpf_map_def SEC("maps") nb_wg_proxy_settings_map = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(__u32), + .value_size = sizeof(__u16), + .max_entries = 10, +}; + +__u16 proxy_port = 0; +__u16 wg_port = 0; + +bool read_port_settings() { + __u16 *value; + value = bpf_map_lookup_elem(&nb_wg_proxy_settings_map, &map_key_proxy_port); + if (!value) { + return false; + } + + proxy_port = *value; + + value = bpf_map_lookup_elem(&nb_wg_proxy_settings_map, &map_key_wg_port); + if (!value) { + return false; + } + wg_port = htons(*value); + + return true; +} + +int xdp_wg_proxy(struct iphdr *ip, struct udphdr *udp) { + if (proxy_port == 0 || wg_port == 0) { + if (!read_port_settings()){ + return XDP_PASS; + } + bpf_printk("proxy port: %d, wg port: %d", proxy_port, wg_port); + } + + // 2130706433 = 127.0.0.1 + if (ip->daddr != htonl(2130706433)) { + return XDP_PASS; + } + + if (udp->source != wg_port){ + return XDP_PASS; + } + + __be16 new_src_port = udp->dest; + __be16 new_dst_port = htons(proxy_port); + udp->dest = new_dst_port; + udp->source = new_src_port; + return XDP_PASS; +} \ No newline at end of file diff --git a/client/internal/ebpf/ebpf/wg_proxy_linux.go b/client/internal/ebpf/ebpf/wg_proxy_linux.go new file mode 100644 index 000000000..4e0df7329 --- /dev/null +++ b/client/internal/ebpf/ebpf/wg_proxy_linux.go @@ -0,0 +1,41 @@ +package ebpf + +import log "github.com/sirupsen/logrus" + +const ( + mapKeyProxyPort uint32 = 0 + mapKeyWgPort uint32 = 1 +) + +func (tf *GeneralManager) LoadWgProxy(proxyPort, wgPort int) error { + log.Debugf("load ebpf WG proxy") + tf.lock.Lock() + defer tf.lock.Unlock() + + err := tf.loadXdp() + if err != nil { + return err + } + + err = tf.bpfObjs.NbWgProxySettingsMap.Put(mapKeyProxyPort, uint16(proxyPort)) + if err != nil { + return err + } + + err = tf.bpfObjs.NbWgProxySettingsMap.Put(mapKeyWgPort, uint16(wgPort)) + if err != nil { + return err + } + + tf.setFeatureFlag(featureFlagWGProxy) + err = tf.bpfObjs.NbFeatures.Put(mapKeyFeatures, tf.featureFlags) + if err != nil { + return err + } + return nil +} + +func (tf *GeneralManager) FreeWGProxy() error { + log.Debugf("free ebpf WG proxy") + return tf.unsetFeatureFlag(featureFlagWGProxy) +} diff --git a/client/internal/ebpf/instantiater_linux.go b/client/internal/ebpf/instantiater_linux.go new file mode 100644 index 000000000..20d8145b4 --- /dev/null +++ b/client/internal/ebpf/instantiater_linux.go @@ -0,0 +1,15 @@ +//go:build !android + +package ebpf + +import ( + "github.com/netbirdio/netbird/client/internal/ebpf/ebpf" + "github.com/netbirdio/netbird/client/internal/ebpf/manager" +) + +// GetEbpfManagerInstance is a wrapper function. This encapsulation is required because if the code import the internal +// ebpf package the Go compiler will include the object files. But it is not supported on Android. It can cause instant +// panic on older Android version. +func GetEbpfManagerInstance() manager.Manager { + return ebpf.GetEbpfManagerInstance() +} diff --git a/client/internal/ebpf/instantiater_nonlinux.go b/client/internal/ebpf/instantiater_nonlinux.go new file mode 100644 index 000000000..b7c38733a --- /dev/null +++ b/client/internal/ebpf/instantiater_nonlinux.go @@ -0,0 +1,10 @@ +//go:build !linux || android + +package ebpf + +import "github.com/netbirdio/netbird/client/internal/ebpf/manager" + +// GetEbpfManagerInstance return error because ebpf is not supported on all os +func GetEbpfManagerInstance() manager.Manager { + panic("unsupported os") +} diff --git a/client/internal/ebpf/manager/manager.go b/client/internal/ebpf/manager/manager.go new file mode 100644 index 000000000..af10142d5 --- /dev/null +++ b/client/internal/ebpf/manager/manager.go @@ -0,0 +1,9 @@ +package manager + +// Manager is used to load multiple eBPF programs. E.g., current DNS programs and WireGuard proxy +type Manager interface { + LoadDNSFwd(ip string, dnsPort int) error + FreeDNSFwd() error + LoadWgProxy(proxyPort, wgPort int) error + FreeWGProxy() error +} diff --git a/client/internal/wgproxy/ebpf/bpf_bpfeb.o b/client/internal/wgproxy/ebpf/bpf_bpfeb.o deleted file mode 100644 index 82d7fc35b4c64f55f312a2f8c963a72e40cec6b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6264 zcmb_gU2K$D89v{1c42>T3kA1PVonioicmW33RNu5;<8(k8oF#rSjBuyJ6}7KPG^Rh zDGV+fdf_G**95bx>4k}G!Uc&YV%7vMH1xs?61^a1HDsen;}zw@CdPw8v;mKx`i|C@1f&D(dYvbo_YvyKCAb*e&O$l$MGleqq|R9|39*wx|?4=UTLf4@90)(QsH=< z{B*b4y`DUrm&*gj`(^64=U*f0w7K(^4&!}-zk{?W<<_^G+iv-{k#D`Hpr+qjeMAoZ zSL@BMkiIypYj3=_`jO-BDeTvzA=cnObo@)YRwZ4lS6kq(kS-nOTdBLBvHd3aU0q#W zxvdS}Df50Zzt`q5|7ojtH!J#srr&p9NEylbKDC9M^I7#Q`&ldBd~%3LsndgsE3+AT z&l{}Ge%hMvQ=b#O1)KLusZZ@9?zQ$LO)CxC{sYVJ{`Tk9CUV03mW(y#m$mCV*JJ&C zppAX^+&0U^$_D{h8?HN2! zO@AT4hvAIp9J@LFb@XPv4zQ6X&f&M52=MpqcmGd3c6Hj zs_AA6PMXTg6JwI6Jzl2KtS?MAwH=P$2Q!x(WXsRbs26E2@!`*p`DuO*kKr!G`W1-*~?U@DNSjOFxa%GJtjKL&Q-sFNO1o;ss%tnOA` zXK1Xys2ip4B-I%=@1$8Q7V7;UozENFl zRx6Fn@yW4xB0e$E@3KYJQtiM|qc>eGQ}x8e#Ho{$C*#vEO@wNNe4$Wx?#h{=?C{{> z!9y<$53g~EfgL#7s4mo}6Q|qAxa`Up<9I5`9@3TMVq9o6Wf^6Zokfwe;wj^FI%N~4 zkTjY$t$x%|+of4A zB@N?XrkuyU;%)FXmxIDV$sXz)ltvMU)P5_?3{jrge!7^iL;1y1r(S$Le(l6JkH?d* zPaKb96_-oXNu`mffvE=WuX>gHKshF6)llx+s^$LMjUG?x2A+Gu;~mZ_#@(T~&ZF;n zn4c?t3#fx{oM{?wd^cPCJhP%8M#*!TzD5N9SgE&pujf2WKW7C0LaBFYzbF{?OSHc# z_&-YB;Jtm#!_>cJafI*0EsWWWRfET-fq$UXuSwGv@%u_G3*E-7HO8#GJ&&4TH;OO0S36Y~?izH8Nc@a#Fb=#xV%3jE8 zf+v;wGqH>}bw#O1qrg{1PE7yT1h1&*S$vA4TOOwTj>TQiplrhUqGbz7Cc`cy{npHy@fbxLYP z@V}56?2A;+V(f#c5P{yXVMDt*!`JyOfa5b4?FRjH8+3@@fpq+_4qN@E!;gv{{|_C0 zD(CTE#i4L<9eWu>8IzRy&aqkdsomg94cq2(9Q(m%4*f>Ihdq5aUiM$}^j&=R5ys>6 z#8+wp#5>TJQdQV%^%&dby&E*vw2gD1Y3t-?K>Pa4lhnMR>=|c$=k%%T+h>4%`4}*D zoId^g@fU%8`z2ss|0b{>pL^N!Z}zT-&0h8VrI;^b$yRo85?cy)rHSY8 zI55j$r(Xcg@d;M@L33^ne-$)s9p?J@@^b;M1$Z&Q*8+S!z_$Xt9N_x_ehBQ3@6lQu zac-VG>J2dS<;zC`JQmts z=e4;*n%~a6o7i&Z-F`hcBIG&r-FV!0!r7Bo#N);j#CL4-{eW7#IeRjA*KHJ^`;zTC z9>#L_owFzJ=XlzIKzprgLV(kEGUtbwcdxS#)}iZ+81TF6H{bph2%P`WzAQ$*K3Oi0 zq(8*t;t!G0X-R?-GUBdvCd}mp9|X0-wj}YJw4Se_bo7WAZ=Vd=i>MN7mSVPZ~y=R diff --git a/client/internal/wgproxy/ebpf/bpf_bpfel.o b/client/internal/wgproxy/ebpf/bpf_bpfel.o deleted file mode 100644 index 2fc0ff8fca8b8aa1a6bfd2b6f7079195dfd478c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6264 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 diff --git a/client/internal/wgproxy/ebpf/loader.go b/client/internal/wgproxy/ebpf/loader.go deleted file mode 100644 index e154c0d9e..000000000 --- a/client/internal/wgproxy/ebpf/loader.go +++ /dev/null @@ -1,84 +0,0 @@ -//go:build linux && !android - -package ebpf - -import ( - _ "embed" - "net" - - "github.com/cilium/ebpf/link" - "github.com/cilium/ebpf/rlimit" -) - -const ( - mapKeyProxyPort uint32 = 0 - mapKeyWgPort uint32 = 1 -) - -//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang-14 bpf src/portreplace.c -- - -// EBPF is a wrapper for eBPF program -type EBPF struct { - link link.Link -} - -// NewEBPF create new EBPF instance -func NewEBPF() *EBPF { - return &EBPF{} -} - -// Load load ebpf program -func (l *EBPF) Load(proxyPort, wgPort int) error { - // it required for Docker - err := rlimit.RemoveMemlock() - if err != nil { - return err - } - - ifce, err := net.InterfaceByName("lo") - 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.NbWgProxySettingsMap.Put(mapKeyProxyPort, uint16(proxyPort)) - if err != nil { - return err - } - - err = objs.NbWgProxySettingsMap.Put(mapKeyWgPort, uint16(wgPort)) - if err != nil { - return err - } - - defer func() { - _ = objs.NbWgProxySettingsMap.Close() - }() - - l.link, err = link.AttachXDP(link.XDPOptions{ - Program: objs.NbWgProxy, - Interface: ifce.Index, - }) - if err != nil { - return err - } - - return err -} - -// Free ebpf program -func (l *EBPF) Free() error { - if l.link != nil { - return l.link.Close() - } - return nil -} diff --git a/client/internal/wgproxy/ebpf/loader_test.go b/client/internal/wgproxy/ebpf/loader_test.go deleted file mode 100644 index 6ce323e70..000000000 --- a/client/internal/wgproxy/ebpf/loader_test.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build linux - -package ebpf - -import ( - "testing" -) - -func Test_newEBPF(t *testing.T) { - ebpf := NewEBPF() - err := ebpf.Load(1234, 51892) - defer func() { - _ = ebpf.Free() - }() - if err != nil { - t.Errorf("%s", err) - } -} diff --git a/client/internal/wgproxy/ebpf/src/portreplace.c b/client/internal/wgproxy/ebpf/src/portreplace.c deleted file mode 100644 index dc95ee53f..000000000 --- a/client/internal/wgproxy/ebpf/src/portreplace.c +++ /dev/null @@ -1,90 +0,0 @@ -#include -#include // ETH_P_IP -#include -#include -#include -#include -#include - -#define bpf_printk(fmt, ...) \ - ({ \ - char ____fmt[] = fmt; \ - bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \ - }) - -const __u32 map_key_proxy_port = 0; -const __u32 map_key_wg_port = 1; - -struct bpf_map_def SEC("maps") nb_wg_proxy_settings_map = { - .type = BPF_MAP_TYPE_ARRAY, - .key_size = sizeof(__u32), - .value_size = sizeof(__u16), - .max_entries = 10, -}; - -__u16 proxy_port = 0; -__u16 wg_port = 0; - -bool read_port_settings() { - __u16 *value; - value = bpf_map_lookup_elem(&nb_wg_proxy_settings_map, &map_key_proxy_port); - if(!value) { - return false; - } - - proxy_port = *value; - - value = bpf_map_lookup_elem(&nb_wg_proxy_settings_map, &map_key_wg_port); - if(!value) { - return false; - } - wg_port = *value; - - return true; -} - -SEC("xdp") -int nb_wg_proxy(struct xdp_md *ctx) { - if(proxy_port == 0 || wg_port == 0) { - if(!read_port_settings()){ - return XDP_PASS; - } - bpf_printk("proxy port: %d, wg port: %d", proxy_port, wg_port); - } - - 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 != htonl(2130706433)) { - return XDP_PASS; - } - - if (udp->source != htons(wg_port)){ - return XDP_PASS; - } - - __be16 new_src_port = udp->dest; - __be16 new_dst_port = htons(proxy_port); - udp->dest = new_dst_port; - udp->source = new_src_port; - return XDP_PASS; -} -char _license[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/client/internal/wgproxy/proxy_ebpf.go b/client/internal/wgproxy/proxy_ebpf.go index 8be6b0c19..ff8ff665b 100644 --- a/client/internal/wgproxy/proxy_ebpf.go +++ b/client/internal/wgproxy/proxy_ebpf.go @@ -12,15 +12,15 @@ import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" - log "github.com/sirupsen/logrus" - ebpf2 "github.com/netbirdio/netbird/client/internal/wgproxy/ebpf" + "github.com/netbirdio/netbird/client/internal/ebpf" + ebpfMgr "github.com/netbirdio/netbird/client/internal/ebpf/manager" ) // WGEBPFProxy definition for proxy with EBPF support type WGEBPFProxy struct { - ebpf *ebpf2.EBPF + ebpfManager ebpfMgr.Manager lastUsedPort uint16 localWGListenPort int @@ -36,7 +36,7 @@ func NewWGEBPFProxy(wgPort int) *WGEBPFProxy { log.Debugf("instantiate ebpf proxy") wgProxy := &WGEBPFProxy{ localWGListenPort: wgPort, - ebpf: ebpf2.NewEBPF(), + ebpfManager: ebpf.GetEbpfManagerInstance(), lastUsedPort: 0, turnConnStore: make(map[uint16]net.Conn), } @@ -56,7 +56,7 @@ func (p *WGEBPFProxy) Listen() error { return err } - err = p.ebpf.Load(wgPorxyPort, p.localWGListenPort) + err = p.ebpfManager.LoadWgProxy(wgPorxyPort, p.localWGListenPort) if err != nil { return err } @@ -110,7 +110,7 @@ func (p *WGEBPFProxy) Free() error { err1 = p.conn.Close() } - err2 = p.ebpf.Free() + err2 = p.ebpfManager.FreeWGProxy() if p.rawConn != nil { err3 = p.rawConn.Close() } diff --git a/iface/iface.go b/iface/iface.go index 58167d1e8..55891d047 100644 --- a/iface/iface.go +++ b/iface/iface.go @@ -112,7 +112,7 @@ func (w *WGIface) Close() error { return w.tun.Close() } -// SetFilter sets packet filters for the userspace impelemntation +// SetFilter sets packet filters for the userspace implementation func (w *WGIface) SetFilter(filter PacketFilter) error { w.mu.Lock() defer w.mu.Unlock()