diff --git a/WORKSPACE b/WORKSPACE index b9edd5615..f017b376d 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -15,11 +15,11 @@ http_file( # Bazel/starlark utilities. http_archive( name = "bazel_skylib", + sha256 = "f7be3474d42aae265405a592bb7da8e171919d74c16f082a5457840f06054728", urls = [ "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.2.1/bazel-skylib-1.2.1.tar.gz", "https://github.com/bazelbuild/bazel-skylib/releases/download/1.2.1/bazel-skylib-1.2.1.tar.gz", ], - sha256 = "f7be3474d42aae265405a592bb7da8e171919d74c16f082a5457840f06054728", ) load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") @@ -1484,8 +1484,8 @@ go_repository( go_repository( name = "com_github_cilium_ebpf", importpath = "github.com/cilium/ebpf", - sum = "h1:QlHdikaxALkqWasW8hAC1mfR0jdmvbfaBdBPFmRSglA=", - version = "v0.4.0", + sum = "h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4=", + version = "v0.9.1", ) go_repository( diff --git a/tools/bazeldefs/defs.bzl b/tools/bazeldefs/defs.bzl index 8de643071..7a54cc31f 100644 --- a/tools/bazeldefs/defs.bzl +++ b/tools/bazeldefs/defs.bzl @@ -72,3 +72,24 @@ def default_net_util(): def coreutil(): return [] # Nothing needed. + +def bpf_program(name, src, bpf_object, visibility, hdrs): + """Generates BPF object files from .c source code. + + Args: + name: target name for BPF program. + src: BPF program souce code in C. + bpf_object: name of generated bpf object code. + visibility: target visibility. + hdrs: header files, but currently unsupported. + """ + if hdrs != []: + fail("hdrs attribute is unsupported") + + native.genrule( + name = name, + srcs = [src], + visibility = visibility, + outs = [bpf_object], + cmd = "clang -O2 -Wall -Werror -target bpf -c $< -o $@", + ) diff --git a/tools/defs.bzl b/tools/defs.bzl index ccc1d2990..297adeb22 100644 --- a/tools/defs.bzl +++ b/tools/defs.bzl @@ -8,7 +8,7 @@ change for Google-internal and bazel-compatible rules. load("//tools/go_stateify:defs.bzl", "go_stateify") load("//tools/go_marshal:defs.bzl", "go_marshal", "marshal_deps", "marshal_test_deps") load("//tools/nogo:defs.bzl", "nogo_test") -load("//tools/bazeldefs:defs.bzl", _BuildSettingInfo = "BuildSettingInfo", _amd64_config = "amd64_config", _arch_config = "arch_config", _arm64_config = "arm64_config", _bool_flag = "bool_flag", _build_test = "build_test", _bzl_library = "bzl_library", _coreutil = "coreutil", _default_net_util = "default_net_util", _more_shards = "more_shards", _most_shards = "most_shards", _proto_library = "proto_library", _select_arch = "select_arch", _select_system = "select_system", _short_path = "short_path", _transition_allowlist = "transition_allowlist", _version = "version") +load("//tools/bazeldefs:defs.bzl", _BuildSettingInfo = "BuildSettingInfo", _amd64_config = "amd64_config", _arch_config = "arch_config", _arm64_config = "arm64_config", _bool_flag = "bool_flag", _bpf_program = "bpf_program", _build_test = "build_test", _bzl_library = "bzl_library", _coreutil = "coreutil", _default_net_util = "default_net_util", _more_shards = "more_shards", _most_shards = "most_shards", _proto_library = "proto_library", _select_arch = "select_arch", _select_system = "select_system", _short_path = "short_path", _transition_allowlist = "transition_allowlist", _version = "version") load("//tools/bazeldefs:cc.bzl", _cc_binary = "cc_binary", _cc_flags_supplier = "cc_flags_supplier", _cc_grpc_library = "cc_grpc_library", _cc_library = "cc_library", _cc_proto_library = "cc_proto_library", _cc_test = "cc_test", _cc_toolchain = "cc_toolchain", _gbenchmark = "gbenchmark", _gbenchmark_internal = "gbenchmark_internal", _grpcpp = "grpcpp", _gtest = "gtest", _vdso_linker_option = "vdso_linker_option") load("//tools/bazeldefs:go.bzl", _gazelle = "gazelle", _go_binary = "go_binary", _go_embed_data = "go_embed_data", _go_grpc_and_proto_libraries = "go_grpc_and_proto_libraries", _go_library = "go_library", _go_path = "go_path", _go_proto_library = "go_proto_library", _go_rule = "go_rule", _go_test = "go_test", _gotsan_flag_values = "gotsan_flag_values", _gotsan_values = "gotsan_values", _select_goarch = "select_goarch", _select_goos = "select_goos") load("//tools/bazeldefs:pkg.bzl", _pkg_deb = "pkg_deb", _pkg_tar = "pkg_tar") @@ -52,6 +52,9 @@ go_proto_library = _go_proto_library gotsan_values = _gotsan_values gotsan_flag_values = _gotsan_flag_values +# BPF rules. +bpf_program = _bpf_program + # Packaging rules. pkg_deb = _pkg_deb pkg_tar = _pkg_tar @@ -117,6 +120,7 @@ def go_binary(name, nogo = True, pure = False, static = False, x_defs = None, ** name = name + "_nogo_library", srcs = kwargs.get("srcs", []), deps = kwargs.get("deps", []), + embedsrcs = kwargs.get("embedsrcs", []), testonly = 1, ) nogo_test( diff --git a/tools/xdp/BUILD b/tools/xdp/BUILD new file mode 100644 index 000000000..30a1fc97b --- /dev/null +++ b/tools/xdp/BUILD @@ -0,0 +1,20 @@ +load("//tools:defs.bzl", "go_binary") + +package(licenses = ["notice"]) + +go_binary( + name = "xdp_loader", + srcs = [ + "main.go", + ], + embedsrcs = [ + "//tools/xdp/bpf:drop_ebpf.o", # keep + "//tools/xdp/bpf:pass_ebpf.o", # keep + ], + visibility = ["//:sandbox"], + deps = [ + "@com_github_cilium_ebpf//:go_default_library", + "@com_github_cilium_ebpf//link:go_default_library", + "@org_golang_x_sys//unix:go_default_library", + ], +) diff --git a/tools/xdp/bpf/BUILD b/tools/xdp/bpf/BUILD new file mode 100644 index 000000000..70f38c6d8 --- /dev/null +++ b/tools/xdp/bpf/BUILD @@ -0,0 +1,19 @@ +load("//tools:defs.bzl", "bpf_program") + +package(licenses = ["notice"]) + +bpf_program( + name = "pass_ebpf", + src = "pass.ebpf.c", + hdrs = [], + bpf_object = "pass_ebpf.o", + visibility = ["//:sandbox"], +) + +bpf_program( + name = "drop_ebpf", + src = "drop.ebpf.c", + hdrs = [], + bpf_object = "drop_ebpf.o", + visibility = ["//:sandbox"], +) diff --git a/tools/xdp/bpf/drop.ebpf.c b/tools/xdp/bpf/drop.ebpf.c new file mode 100644 index 000000000..493ee19e7 --- /dev/null +++ b/tools/xdp/bpf/drop.ebpf.c @@ -0,0 +1,23 @@ +// Copyright 2022 The gVisor Authors. +// +// 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 + +#define section(secname) __attribute__((section(secname), used)) + +char __license[] section("license") = "Apache-2.0"; + +// You probably shouldn't change the section or function name. Each is used by +// BPF tooling, and so changes can cause runtime failures. +section("xdp") int xdp_prog(struct xdp_md *ctx) { return XDP_DROP; } diff --git a/tools/xdp/bpf/pass.ebpf.c b/tools/xdp/bpf/pass.ebpf.c new file mode 100644 index 000000000..6380b7070 --- /dev/null +++ b/tools/xdp/bpf/pass.ebpf.c @@ -0,0 +1,23 @@ +// Copyright 2022 The gVisor Authors. +// +// 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 + +#define section(secname) __attribute__((section(secname), used)) + +char __license[] section("license") = "Apache-2.0"; + +// You probably shouldn't change the section or function name. Each is used by +// BPF tooling, and so changes can cause runtime failures. +section("xdp") int xdp_prog(struct xdp_md *ctx) { return XDP_PASS; } diff --git a/tools/xdp/main.go b/tools/xdp/main.go new file mode 100644 index 000000000..0cc2acc8d --- /dev/null +++ b/tools/xdp/main.go @@ -0,0 +1,131 @@ +// Copyright 2022 The gVisor Authors. +// +// 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. + +// The xdp_loader tool is used to load compiled XDP object files into the XDP +// hook of a net device. It is intended primarily for testing. +package main + +import ( + "bytes" + _ "embed" + "flag" + "log" + "net" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" + "golang.org/x/sys/unix" +) + +var ( + device = flag.String("device", "", "which device to attach to") + program = flag.String("program", "", "which program to install: one of [pass, drop]") +) + +// Builtin programs selectable by users. +var ( + //go:embed bpf/pass_ebpf.o + pass []byte + + //go:embed bpf/drop_ebpf.o + drop []byte +) + +var programs = map[string][]byte{ + "pass": pass, + "drop": drop, +} + +func main() { + // Sanity check. + if len(pass) == 0 { + panic("the pass program failed to embed") + } + if len(drop) == 0 { + panic("the drop program failed to embed") + } + + flag.Parse() + + // Get a net device. + if *device == "" { + log.Fatalf("must specify -device") + } + iface, err := net.InterfaceByName(*device) + if err != nil { + log.Fatalf("unknown device %q: %v", *device, err) + } + + // Choose a program. + if *program == "" { + log.Fatalf("must specify -program") + } + progData, ok := programs[*program] + if !ok { + log.Fatalf("unknown program %q", *program) + } + + // Load into the kernel. Note that this is usually done using bpf2go, + // but since we haven't set up that tool we do everything manually. + spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader(progData)) + if err != nil { + log.Fatalf("failed to load spec: %v", err) + } + + // We need to pass a struct with a field of a specific type and tag. + var objects struct { + Program *ebpf.Program `ebpf:"xdp_prog"` + } + if err := spec.LoadAndAssign(&objects, nil); err != nil { + log.Fatalf("failed to load program: %v", err) + } + defer func() { + if err := objects.Program.Close(); err != nil { + log.Printf("failed to close program: %v", err) + } + }() + + // Attach the program to the XDP hook on the device. Fallback from best + // to worst mode. + modes := []struct { + name string + flag link.XDPAttachFlags + }{ + {name: "offload", flag: link.XDPOffloadMode}, + {name: "driver", flag: link.XDPDriverMode}, + {name: "generic", flag: link.XDPGenericMode}, + } + var attached link.Link + for _, mode := range modes { + attached, err = link.AttachXDP(link.XDPOptions{ + Program: objects.Program, + Interface: iface.Index, + Flags: mode.flag, + }) + if err == nil { + log.Printf("attached with mode %q", mode.name) + break + } + log.Printf("failed to attach with mode %q: %v", mode.name, err) + } + if attached == nil { + log.Fatalf("failed to attach program") + } + defer attached.Close() + + log.Printf("Successfully attached! Press CTRL-C to quit and remove the program from the device.") + for { + unix.Pause() + } +}