Skip to content
Permalink
Browse files

bpf: Add XDP program and means to build it

The program implements blocking the traffic by consulting the BPF maps
that serve as blacklists.

This also adds a docker file for building an image with clang, so we
can compile the C code into an ELF object file with the the BPF
bytecode.

The above are used in the Makefile.

Co-authored-by: Alban Crequy <alban@kinvolk.io>
  • Loading branch information...
iaguis and alban committed Feb 28, 2019
1 parent ab4636f commit 336d34c307f477b73692656058b890d1c01e73f4
Showing with 299 additions and 4 deletions.
  1. +1 −0 .gitignore
  2. +34 −4 Makefile
  3. +48 −0 bpf/xdp/bpf.h
  4. +206 −0 bpf/xdp/filter.c
  5. +10 −0 docker-build-images/bpf-clang-builder.Dockerfile.amd64
@@ -14,6 +14,7 @@

# Build directories.
/bin/
/bpf/xdp/generated/
/docker-image/bin/
/build/
/dist/
@@ -204,10 +204,10 @@ LDFLAGS:=-ldflags "\

# List of Go files that are generated by the build process. Builds should
# depend on these, clean removes them.
GENERATED_GO_FILES:=proto/felixbackend.pb.go
GENERATED_FILES:=proto/felixbackend.pb.go bpf/xdp/generated/xdp.o

# All Felix go files.
SRC_FILES:=$(shell find . $(foreach dir,$(NON_FELIX_DIRS),-path ./$(dir) -prune -o) -type f -name '*.go' -print) $(GENERATED_GO_FILES)
SRC_FILES:=$(shell find . $(foreach dir,$(NON_FELIX_DIRS),-path ./$(dir) -prune -o) -type f -name '*.go' -print) $(GENERATED_FILES)

# Figure out the users UID/GID. These are needed to run docker containers
# as the current user and ensure that files built inside containers are
@@ -241,7 +241,7 @@ clean:
dist \
build \
fv/fv.test \
$(GENERATED_GO_FILES) \
$(GENERATED_FILES) \
go/docs/calc.pdf \
.glide \
vendor \
@@ -321,6 +321,36 @@ protobuf proto/felixbackend.pb.go: proto/felixbackend.proto
--gogofaster_out=plugins=grpc:. \
felixbackend.proto

bpf/xdp/generated/xdp.o: bpf/xdp/filter.c
mkdir -p bpf/xdp/generated
# the bpf object file is not arch dependent, so we can build with the current ARCH
docker build -t calico-build/bpf-clang -f docker-build-images/bpf-clang-builder.Dockerfile.$(BUILDARCH) .
docker run --rm --user $(LOCAL_USER_ID):$(LOCAL_GROUP_ID) \
-v $(CURDIR):/go/src/$(PACKAGE_NAME):rw \
calico-build/bpf-clang \
/bin/sh -c \
"cd /go/src/$(PACKAGE_NAME) && \
clang \
-D__KERNEL__ \
-D__ASM_SYSREG_H \
-Wno-unused-value \
-Wno-pointer-sign \
-Wno-compare-distinct-pointer-types \
-Wunused \
-Wall \
-Werror \
-fno-stack-protector \
-O2 \
-emit-llvm \
-c /go/src/$(PACKAGE_NAME)/bpf/xdp/filter.c \
-o /go/src/$(PACKAGE_NAME)/bpf/xdp/generated/xdp.ll && \
llc \
-march=bpf \
-filetype=obj \
-o /go/src/$(PACKAGE_NAME)/bpf/xdp/generated/xdp.o \
/go/src/$(PACKAGE_NAME)/bpf/xdp/generated/xdp.ll && \
rm -f /go/src/$(PACKAGE_NAME)/bpf/xdp/generated/xdp.ll"

###############################################################################
# Building the image
###############################################################################
@@ -483,7 +513,7 @@ check-licenses/dependency-licenses.txt: vendor/.up-to-date
$(DOCKER_RUN) $(CALICO_BUILD) sh -c 'licenses ./cmd/calico-felix > check-licenses/dependency-licenses.txt'

.PHONY: go-meta-linter
go-meta-linter: vendor/.up-to-date $(GENERATED_GO_FILES)
go-meta-linter: vendor/.up-to-date $(GENERATED_FILES)
# Run staticcheck stand-alone since gometalinter runs concurrent copies, which
# uses a lot of RAM.
$(DOCKER_RUN) $(CALICO_BUILD) sh -c 'glide nv | xargs -n 3 staticcheck'
@@ -0,0 +1,48 @@
// from kernel headers
#include <asm/byteorder.h>

#ifndef __section
# define __section(NAME) \
__attribute__((section(NAME), used))
#endif

#ifndef __inline
# define __inline \
inline __attribute__((always_inline))
#endif

#ifndef BPF_FUNC
# define BPF_FUNC(NAME, ...) \
(*NAME)(__VA_ARGS__) = (void *)BPF_FUNC_##NAME
#endif

#if __BYTE_ORDER == __LITTLE_ENDIAN
# define __bpf_ntohs(x) __builtin_bswap16(x)
# define __bpf_htons(x) __builtin_bswap16(x)
# define __bpf_ntohl(x) __builtin_bswap32(x)
# define __bpf_htonl(x) __builtin_bswap32(x)
#elif __BYTE_ORDER == __BIG_ENDIAN
# define __bpf_ntohs(x) (x)
# define __bpf_htons(x) (x)
# define __bpf_ntohl(x) (x)
# define __bpf_htonl(x) (x)
#else
# error "Fix your __BYTE_ORDER?!"
#endif

#define bpf_htons(x) \
(__builtin_constant_p(x) ? \
__constant_htons(x) : __bpf_htons(x))
#define bpf_ntohs(x) \
(__builtin_constant_p(x) ? \
__constant_ntohs(x) : __bpf_ntohs(x))

#define bpf_htonl(x) \
(__builtin_constant_p(x) ? \
__constant_htonl(x) : __bpf_htonl(x))
#define bpf_ntohl(x) \
(__builtin_constant_p(x) ? \
__constant_ntohl(x) : __bpf_ntohl(x))

static void *BPF_FUNC(map_lookup_elem, void *map, const void *key);
static int BPF_FUNC(skb_load_bytes, void *ctx, int off, void *to, int len);
@@ -0,0 +1,206 @@
// Copyright (c) 2019 Tigera, Inc. All rights reserved.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <linux/bpf.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <linux/udp.h>
#include <linux/if_ether.h>
#include <iproute2/bpf_elf.h>
#include <stdbool.h>

#include "bpf.h"

static __always_inline void *xdp_data(const struct xdp_md *xdp)
{
return (void *)(unsigned long)xdp->data;
}

static __always_inline void *xdp_data_end(const struct xdp_md *xdp)
{
return (void *)(unsigned long)xdp->data_end;
}

static __always_inline bool xdp_no_room(const void *needed, const void *limit)
{
return needed > limit;
}

struct lpm_v4_key {
struct bpf_lpm_trie_key lpm;
__u8 addr[4];
};

struct lpm_val {
__u32 ref_count;
};

struct failsafe_key {
__u8 proto;
__u8 pad;
__u16 port;
};

struct failsafe_value {
__u8 dummy;
};

// calico_prefilter_v4 contains one entry per CIDR that should be dropped by
// the prefilter.
//
// Key: the CIDR, formatted for LPM lookup
// Value: reference count, used only by felix
struct bpf_elf_map calico_prefilter_v4 __section(ELF_SECTION_MAPS) = {
.type = BPF_MAP_TYPE_LPM_TRIE,
.size_key = sizeof(struct lpm_v4_key),
.size_value = sizeof(struct lpm_val),
.flags = BPF_F_NO_PREALLOC,
.max_elem = 512000, // arbitrary
};

// calico_failsafe_ports contains one entry per port/proto that we should NOT
// block even if there's a blacklist rule. This corresponds with the failsafe
// ports option in Felix and is populated by Felix at startup time.
//
// Key: the protocol and port
// Value: not used
struct bpf_elf_map calico_failsafe_ports __section(ELF_SECTION_MAPS) = {
.type = BPF_MAP_TYPE_HASH,
.size_key = sizeof(struct failsafe_key),
.size_value = sizeof(struct failsafe_value),
.flags = BPF_F_NO_PREALLOC,
.max_elem = 65535 * 2, // number of ports for TCP and UDP
};

static __always_inline
__u16 get_dest_port_ipv4_udp(struct xdp_md *ctx, __u64 nh_off)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct iphdr *iph = data + nh_off;
struct udphdr *udph;
__u16 dport;

if (iph + 1 > data_end) {
return 0;
}
if (!(iph->protocol == IPPROTO_UDP)) {
return 0;
}

udph = (void *)(iph + 1);
if (udph + 1 > data_end) {
return 0;
}

dport = bpf_ntohs(udph->dest);
return dport;
}

static __always_inline
__u16 get_dest_port_ipv4_tcp(struct xdp_md *ctx, __u64 nh_off)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct iphdr *iph = data + nh_off;
struct tcphdr *tcph;
__u16 dport;

if (iph + 1 > data_end) {
return 0;
}
if (!(iph->protocol == IPPROTO_TCP)) {
return 0;
}

tcph = (void *)(iph + 1);
if (tcph + 1 > data_end) {
return 0;
}

dport = bpf_ntohs(tcph->dest);
return dport;
}


static __always_inline int check_v4(struct xdp_md *xdp)
{
void *data_end = xdp_data_end(xdp);
void *data = xdp_data(xdp);
struct iphdr *ipv4_hdr = data + sizeof(struct ethhdr);
struct lpm_v4_key pfx;
__u16 dest_port;

if (xdp_no_room(ipv4_hdr + 1, data_end)) {
return XDP_DROP;
}

__builtin_memcpy(pfx.lpm.data, &ipv4_hdr->saddr, sizeof(pfx.addr));
pfx.lpm.prefixlen = 32;

if (map_lookup_elem(&calico_prefilter_v4, &pfx)) {
// check failsafe ports
switch (ipv4_hdr->protocol) {
case IPPROTO_TCP:
dest_port = get_dest_port_ipv4_tcp(xdp, sizeof(struct ethhdr));
break;
case IPPROTO_UDP:
dest_port = get_dest_port_ipv4_udp(xdp, sizeof(struct ethhdr));
break;
default:
return XDP_DROP;
}

struct failsafe_key key = {};

key.proto = ipv4_hdr->protocol;
key.port = dest_port;
if (map_lookup_elem(&calico_failsafe_ports, &key)) {
return XDP_PASS;
}

// no failsafe ports matched, drop
return XDP_DROP;
}

return XDP_PASS;
}


static __always_inline int check_prefilter(struct xdp_md *xdp)
{
void *data_end = xdp_data_end(xdp);
void *data = xdp_data(xdp);
struct ethhdr *eth = data;
__u16 proto;

if (xdp_no_room(eth + 1, data_end)) {
return XDP_DROP;
}

proto = eth->h_proto;
if (proto == bpf_htons(ETH_P_IP)) {
return check_v4(xdp);
} else {
/* other traffic can continue */
return XDP_PASS;
}
}

__section("pre-filter")
int xdp_enter(struct xdp_md *xdp)
{
return check_prefilter(xdp);
}
@@ -0,0 +1,10 @@
FROM debian:buster-slim
ENV GOPATH /go
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y --no-install-recommends \
llvm clang linux-headers-amd64 make binutils git file iproute2 golang-go ca-certificates && \
apt-get purge --auto-remove && \
apt-get clean && \
mkdir -p /src /go && \
rm -rf /go/src

0 comments on commit 336d34c

Please sign in to comment.
You can’t perform that action at this time.