github.com/cilium/cilium@v1.16.2/pkg/datapath/loader/compile.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package loader 5 6 import ( 7 "bufio" 8 "bytes" 9 "context" 10 "errors" 11 "fmt" 12 "io" 13 "os" 14 "path" 15 "path/filepath" 16 "strings" 17 "sync" 18 "syscall" 19 20 "github.com/cilium/ebpf" 21 "github.com/cilium/ebpf/asm" 22 "github.com/sirupsen/logrus" 23 24 "github.com/cilium/cilium/pkg/command/exec" 25 "github.com/cilium/cilium/pkg/datapath/linux/probes" 26 "github.com/cilium/cilium/pkg/datapath/types" 27 "github.com/cilium/cilium/pkg/lock" 28 "github.com/cilium/cilium/pkg/logging/logfields" 29 "github.com/cilium/cilium/pkg/option" 30 ) 31 32 // outputType determines the type to be generated by the compilation steps. 33 type outputType string 34 35 const ( 36 outputObject = outputType("obj") 37 outputSource = outputType("c") 38 39 compiler = "clang" 40 41 endpointPrefix = "bpf_lxc" 42 endpointProg = endpointPrefix + "." + string(outputSource) 43 endpointObj = endpointPrefix + ".o" 44 45 hostEndpointPrefix = "bpf_host" 46 hostEndpointNetdevPrefix = "bpf_netdev_" 47 hostEndpointProg = hostEndpointPrefix + "." + string(outputSource) 48 hostEndpointObj = hostEndpointPrefix + ".o" 49 50 networkPrefix = "bpf_network" 51 networkProg = networkPrefix + "." + string(outputSource) 52 networkObj = networkPrefix + ".o" 53 54 xdpPrefix = "bpf_xdp" 55 xdpProg = xdpPrefix + "." + string(outputSource) 56 xdpObj = xdpPrefix + ".o" 57 58 overlayPrefix = "bpf_overlay" 59 overlayProg = overlayPrefix + "." + string(outputSource) 60 overlayObj = overlayPrefix + ".o" 61 62 wireguardPrefix = "bpf_wireguard" 63 wireguardProg = wireguardPrefix + "." + string(outputSource) 64 wireguardObj = wireguardPrefix + ".o" 65 ) 66 67 var ( 68 probeCPUOnce sync.Once 69 70 // default fallback 71 nameBPFCPU = "v1" 72 ) 73 74 // progInfo describes a program to be compiled with the expected output format 75 type progInfo struct { 76 // Source is the program source (base) filename to be compiled 77 Source string 78 // Output is the expected (base) filename produced from the source 79 Output string 80 // OutputType to be created by LLVM 81 OutputType outputType 82 // Options are passed directly to LLVM as individual parameters 83 Options []string 84 } 85 86 func (pi *progInfo) AbsoluteOutput(dir *directoryInfo) string { 87 return filepath.Join(dir.Output, pi.Output) 88 } 89 90 // directoryInfo includes relevant directories for compilation and linking 91 type directoryInfo struct { 92 // Library contains the library code to be used for compilation 93 Library string 94 // Runtime contains headers for compilation 95 Runtime string 96 // State contains node, lxc, and features headers for templatization 97 State string 98 // Output is the directory where the files will be stored 99 Output string 100 } 101 102 var ( 103 standardCFlags = []string{"-O2", "--target=bpf", "-std=gnu89", 104 "-nostdinc", 105 "-Wall", "-Wextra", "-Werror", "-Wshadow", 106 "-Wno-address-of-packed-member", 107 "-Wno-unknown-warning-option", 108 "-Wno-gnu-variable-sized-type-not-at-end", 109 "-Wdeclaration-after-statement", 110 "-Wimplicit-int-conversion", 111 "-Wenum-conversion"} 112 113 // testIncludes allows the unit tests to inject additional include 114 // paths into the compile command at test time. It is usually nil. 115 testIncludes []string 116 117 epProg = &progInfo{ 118 Source: endpointProg, 119 Output: endpointObj, 120 OutputType: outputObject, 121 } 122 hostEpProg = &progInfo{ 123 Source: hostEndpointProg, 124 Output: hostEndpointObj, 125 OutputType: outputObject, 126 } 127 networkTcProg = &progInfo{ 128 Source: networkProg, 129 Output: networkObj, 130 OutputType: outputObject, 131 } 132 ) 133 134 // getBPFCPU returns the BPF CPU for this host. 135 func getBPFCPU() string { 136 probeCPUOnce.Do(func() { 137 if !option.Config.DryMode { 138 // We can probe the availability of BPF instructions indirectly 139 // based on what kernel helpers are available when both were 140 // added in the same release. 141 // We want to enable v3 only on kernels 5.10+ where we have 142 // tested it and need it to work around complexity issues. 143 if probes.HaveV3ISA() == nil { 144 if probes.HaveProgramHelper(ebpf.SchedCLS, asm.FnRedirectNeigh) == nil { 145 nameBPFCPU = "v3" 146 return 147 } 148 } 149 // We want to enable v2 on all kernels that support it, that is, 150 // kernels 4.14+. 151 if probes.HaveV2ISA() == nil { 152 nameBPFCPU = "v2" 153 } 154 } 155 }) 156 return nameBPFCPU 157 } 158 159 func pidFromProcess(proc *os.Process) string { 160 result := "not-started" 161 if proc != nil { 162 result = fmt.Sprintf("%d", proc.Pid) 163 } 164 return result 165 } 166 167 // compile and optionally link a program. 168 // 169 // May output assembly or source code after prepocessing. 170 func compile(ctx context.Context, prog *progInfo, dir *directoryInfo) (string, error) { 171 possibleCPUs, err := ebpf.PossibleCPU() 172 if err != nil { 173 return "", fmt.Errorf("failed to get number of possible CPUs: %w", err) 174 } 175 176 compileArgs := append(testIncludes, 177 fmt.Sprintf("-I%s", path.Join(dir.Runtime, "globals")), 178 fmt.Sprintf("-I%s", dir.State), 179 fmt.Sprintf("-I%s", dir.Library), 180 fmt.Sprintf("-I%s", path.Join(dir.Library, "include")), 181 ) 182 183 switch prog.OutputType { 184 case outputSource: 185 compileArgs = append(compileArgs, "-E") // Preprocessor 186 case outputObject: 187 compileArgs = append(compileArgs, "-g") 188 } 189 190 compileArgs = append(compileArgs, standardCFlags...) 191 compileArgs = append(compileArgs, fmt.Sprintf("-D__NR_CPUS__=%d", possibleCPUs)) 192 compileArgs = append(compileArgs, "-mcpu="+getBPFCPU()) 193 compileArgs = append(compileArgs, prog.Options...) 194 compileArgs = append(compileArgs, 195 "-c", path.Join(dir.Library, prog.Source), 196 "-o", "-", // Always output to stdout 197 ) 198 199 log.WithFields(logrus.Fields{ 200 "target": compiler, 201 "args": compileArgs, 202 }).Debug("Launching compiler") 203 204 compileCmd, cancelCompile := exec.WithCancel(ctx, compiler, compileArgs...) 205 defer cancelCompile() 206 207 output, err := os.Create(prog.AbsoluteOutput(dir)) 208 if err != nil { 209 return "", err 210 } 211 defer output.Close() 212 compileCmd.Stdout = output 213 214 var compilerStderr bytes.Buffer 215 compileCmd.Stderr = &compilerStderr 216 217 if err := compileCmd.Run(); err != nil { 218 err = fmt.Errorf("Failed to compile %s: %w", prog.Output, err) 219 220 // In linux/unix based implementations, cancelling the context for a cmd.Run() will 221 // return errors: "context cancelled" if the context is cancelled prior to the process 222 // starting and "signal: killed" if it is already running. 223 // This can mess up calling logging logic which expects the returned error to have 224 // context.Cancelled so we join this error in to fix that. 225 if errors.Is(ctx.Err(), context.Canceled) && 226 compileCmd.ProcessState != nil && 227 !compileCmd.ProcessState.Exited() && 228 strings.HasSuffix(err.Error(), syscall.SIGKILL.String()) { 229 err = errors.Join(err, ctx.Err()) 230 } 231 232 if !errors.Is(err, context.Canceled) { 233 log.WithFields(logrus.Fields{ 234 "compiler-pid": pidFromProcess(compileCmd.Process), 235 }).Error(err) 236 } 237 238 scanner := bufio.NewScanner(io.LimitReader(&compilerStderr, 1_000_000)) 239 for scanner.Scan() { 240 log.Warn(scanner.Text()) 241 } 242 243 return "", err 244 } 245 246 // Cmd.ProcessState is populated by Cmd.Wait(). Cmd.Run() bails out if 247 // Cmd.Start() fails, which will leave Cmd.ProcessState nil. Only log peak 248 // RSS if the compilation succeeded, which will be the majority of cases. 249 if usage, ok := compileCmd.ProcessState.SysUsage().(*syscall.Rusage); ok { 250 log.WithFields(logrus.Fields{ 251 "compiler-pid": compileCmd.Process.Pid, 252 "output": output.Name(), 253 }).Debugf("Compilation had peak RSS of %d bytes", usage.Maxrss) 254 } 255 256 return output.Name(), nil 257 } 258 259 // compileDatapath invokes the compiler and linker to create all state files for 260 // the BPF datapath, with the primary target being the BPF ELF binary. 261 // 262 // It also creates the following output files: 263 // * Preprocessed C 264 // * Assembly 265 // * Object compiled with debug symbols 266 func compileDatapath(ctx context.Context, dirs *directoryInfo, isHost bool, logger *logrus.Entry) error { 267 scopedLog := logger.WithField(logfields.Debug, true) 268 269 versionCmd := exec.CommandContext(ctx, compiler, "--version") 270 compilerVersion, err := versionCmd.CombinedOutput(scopedLog, true) 271 if err != nil { 272 return err 273 } 274 scopedLog.WithFields(logrus.Fields{ 275 compiler: string(compilerVersion), 276 }).Debug("Compiling datapath") 277 278 prog := epProg 279 if isHost { 280 prog = hostEpProg 281 } 282 283 if option.Config.Debug && prog.OutputType == outputObject { 284 // Write out preprocessing files for debugging purposes 285 debugProg := *prog 286 debugProg.Output = debugProg.Source 287 debugProg.OutputType = outputSource 288 289 if _, err := compile(ctx, &debugProg, dirs); err != nil { 290 // Only log an error here if the context was not canceled. This log message 291 // should only represent failures with respect to compiling the program. 292 if !errors.Is(err, context.Canceled) { 293 scopedLog.WithField(logfields.Params, logfields.Repr(debugProg)).WithError(err).Debug("JoinEP: Failed to compile") 294 } 295 return err 296 } 297 } 298 299 if _, err := compile(ctx, prog, dirs); err != nil { 300 // Only log an error here if the context was not canceled. This log message 301 // should only represent failures with respect to compiling the program. 302 if !errors.Is(err, context.Canceled) { 303 scopedLog.WithField(logfields.Params, logfields.Repr(prog)).WithError(err).Warn("JoinEP: Failed to compile") 304 } 305 return err 306 } 307 308 return nil 309 } 310 311 // compileWithOptions compiles a BPF program generating an object file, 312 // using a set of provided compiler options. 313 func compileWithOptions(ctx context.Context, src string, out string, opts []string) error { 314 prog := progInfo{ 315 Source: src, 316 Options: opts, 317 Output: out, 318 OutputType: outputObject, 319 } 320 dirs := directoryInfo{ 321 Library: option.Config.BpfDir, 322 Runtime: option.Config.StateDir, 323 Output: option.Config.StateDir, 324 State: option.Config.StateDir, 325 } 326 _, err := compile(ctx, &prog, &dirs) 327 return err 328 } 329 330 // compileDefault compiles a BPF program generating an object file with default options. 331 func compileDefault(ctx context.Context, src string, out string) error { 332 return compileWithOptions(ctx, src, out, nil) 333 } 334 335 // compileNetwork compiles a BPF program attached to network 336 func compileNetwork(ctx context.Context) error { 337 dirs := directoryInfo{ 338 Library: option.Config.BpfDir, 339 Runtime: option.Config.StateDir, 340 Output: option.Config.StateDir, 341 State: option.Config.StateDir, 342 } 343 scopedLog := log.WithField(logfields.Debug, true) 344 345 versionCmd := exec.CommandContext(ctx, compiler, "--version") 346 compilerVersion, err := versionCmd.CombinedOutput(scopedLog, true) 347 if err != nil { 348 return err 349 } 350 scopedLog.WithFields(logrus.Fields{ 351 compiler: string(compilerVersion), 352 }).Debug("Compiling network programs") 353 354 // Write out assembly and preprocessing files for debugging purposes 355 if _, err := compile(ctx, networkTcProg, &dirs); err != nil { 356 scopedLog.WithField(logfields.Params, logfields.Repr(networkTcProg)). 357 WithError(err).Warn("Failed to compile") 358 return err 359 } 360 return nil 361 } 362 363 // compileOverlay compiles BPF programs in bpf_overlay.c. 364 func compileOverlay(ctx context.Context, opts []string) error { 365 dirs := &directoryInfo{ 366 Library: option.Config.BpfDir, 367 Runtime: option.Config.StateDir, 368 Output: option.Config.StateDir, 369 State: option.Config.StateDir, 370 } 371 scopedLog := log.WithField(logfields.Debug, true) 372 373 versionCmd := exec.CommandContext(ctx, compiler, "--version") 374 compilerVersion, err := versionCmd.CombinedOutput(scopedLog, true) 375 if err != nil { 376 return err 377 } 378 scopedLog.WithFields(logrus.Fields{ 379 compiler: string(compilerVersion), 380 }).Debug("Compiling overlay programs") 381 382 prog := &progInfo{ 383 Source: overlayProg, 384 Output: overlayObj, 385 OutputType: outputObject, 386 Options: opts, 387 } 388 // Write out assembly and preprocessing files for debugging purposes 389 if _, err := compile(ctx, prog, dirs); err != nil { 390 scopedLog.WithField(logfields.Params, logfields.Repr(prog)). 391 WithError(err).Warn("Failed to compile") 392 return err 393 } 394 return nil 395 } 396 397 func compileWireguard(ctx context.Context, opts []string) (err error) { 398 dirs := &directoryInfo{ 399 Library: option.Config.BpfDir, 400 Runtime: option.Config.StateDir, 401 Output: option.Config.StateDir, 402 State: option.Config.StateDir, 403 } 404 scopedLog := log.WithField(logfields.Debug, true) 405 406 versionCmd := exec.CommandContext(ctx, compiler, "--version") 407 compilerVersion, err := versionCmd.CombinedOutput(scopedLog, true) 408 if err != nil { 409 return err 410 } 411 scopedLog.WithFields(logrus.Fields{ 412 compiler: string(compilerVersion), 413 }).Debug("Compiling wireguard programs") 414 415 prog := &progInfo{ 416 Source: wireguardProg, 417 Output: wireguardObj, 418 OutputType: outputObject, 419 Options: opts, 420 } 421 // Write out assembly and preprocessing files for debugging purposes 422 if _, err := compile(ctx, prog, dirs); err != nil { 423 scopedLog.WithField(logfields.Params, logfields.Repr(prog)). 424 WithError(err).Warn("Failed to compile") 425 return err 426 } 427 return nil 428 } 429 430 type compilationLock struct { 431 lock.RWMutex 432 } 433 434 func NewCompilationLock() types.CompilationLock { 435 return &compilationLock{} 436 }