From 5c864ee72381fdbbe775c8549bf45dbe3fa9f8fd Mon Sep 17 00:00:00 2001 From: Dan Fuhry Date: Tue, 30 Jun 2026 13:15:29 -0400 Subject: [PATCH] use tool to generate ProgramType and AttachType enums ## Summary Add a tool that generates the `ProgramType` and `AttachType` enums automatically from Linux headers installed on the system. The generated files are checked in, in order to preserve building in a single step and support building on systems which do not have linux-api-headers installed. The tool that generates Go from C-language enums was generated by Claude and lightly modified by me. ## Test Plan Confirmed the generated output makes sense and constant names match the existing ones. Co-Authored-By: Claude Sonnet 4.6 Signed-Off-By: Dan Fuhry --- attach_type.gen.go | 70 +++++++++ cmd/bpf_enum_gen/main.go | 303 +++++++++++++++++++++++++++++++++++++++ program_base.go | 85 +---------- program_type.gen.go | 44 ++++++ 4 files changed, 419 insertions(+), 83 deletions(-) create mode 100644 attach_type.gen.go create mode 100644 cmd/bpf_enum_gen/main.go create mode 100644 program_type.gen.go diff --git a/attach_type.gen.go b/attach_type.gen.go new file mode 100644 index 0000000..a5f6f66 --- /dev/null +++ b/attach_type.gen.go @@ -0,0 +1,70 @@ +// @generated by bpf_enum_gen. DO NOT EDIT. +// Source: /usr/include/linux/bpf.h + +package goebpf + +// AttachType is generated from enum bpf_attach_type in /usr/include/linux/bpf.h +type AttachType int + +const ( + AttachTypeCgroupInetIngress AttachType = iota + AttachTypeCgroupInetEgress + AttachTypeCgroupInetSockCreate + AttachTypeCgroupSockOps + AttachTypeSkSkbStreamParser + AttachTypeSkSkbStreamVerdict + AttachTypeCgroupDevice + AttachTypeSkMsgVerdict + AttachTypeCgroupInet4Bind + AttachTypeCgroupInet6Bind + AttachTypeCgroupInet4Connect + AttachTypeCgroupInet6Connect + AttachTypeCgroupInet4PostBind + AttachTypeCgroupInet6PostBind + AttachTypeCgroupUdp4Sendmsg + AttachTypeCgroupUdp6Sendmsg + AttachTypeLircMode2 + AttachTypeFlowDissector + AttachTypeCgroupSysctl + AttachTypeCgroupUdp4Recvmsg + AttachTypeCgroupUdp6Recvmsg + AttachTypeCgroupGetsockopt + AttachTypeCgroupSetsockopt + AttachTypeTraceRawTp + AttachTypeTraceFentry + AttachTypeTraceFexit + AttachTypeModifyReturn + AttachTypeLsmMac + AttachTypeTraceIter + AttachTypeCgroupInet4Getpeername + AttachTypeCgroupInet6Getpeername + AttachTypeCgroupInet4Getsockname + AttachTypeCgroupInet6Getsockname + AttachTypeXdpDevmap + AttachTypeCgroupInetSockRelease + AttachTypeXdpCpumap + AttachTypeSkLookup + AttachTypeXdp + AttachTypeSkSkbVerdict + AttachTypeSkReuseportSelect + AttachTypeSkReuseportSelectOrMigrate + AttachTypePerfEvent + AttachTypeTraceKprobeMulti + AttachTypeLsmCgroup + AttachTypeStructOps + AttachTypeNetfilter + AttachTypeTcxIngress + AttachTypeTcxEgress + AttachTypeTraceUprobeMulti + AttachTypeCgroupUnixConnect + AttachTypeCgroupUnixSendmsg + AttachTypeCgroupUnixRecvmsg + AttachTypeCgroupUnixGetpeername + AttachTypeCgroupUnixGetsockname + AttachTypeNetkitPrimary + AttachTypeNetkitPeer + AttachTypeTraceKprobeSession + AttachTypeTraceUprobeSession + AttachTypeTraceFsession + maxBpfAttachType +) diff --git a/cmd/bpf_enum_gen/main.go b/cmd/bpf_enum_gen/main.go new file mode 100644 index 0000000..6971035 --- /dev/null +++ b/cmd/bpf_enum_gen/main.go @@ -0,0 +1,303 @@ +// bpf_enum_gen generates Go constants from a C enum in a header file. +// Usage: bpf_enum_gen -header -enum -type -pkg -out +// +// The C enum members are converted to Go identifiers by stripping the +// longest common prefix (e.g. "BPF_PROG_TYPE_"), then title-casing each +// underscore-separated word. The __MAX_* sentinel becomes a private +// constant named maxBpf. +package main + +import ( + "bufio" + "flag" + "fmt" + "os" + "strings" + "text/template" + "unicode" +) + +const outputTemplate = `// @generated by bpf_enum_gen. DO NOT EDIT. +// Source: {{.Header}} + +package {{.Package}} + +// {{.GoType}} is generated from enum {{.EnumName}} in {{.Header}} +type {{.GoType}} int + +const ( +{{- range .Members}} +{{- if .First}} + {{.Name}} {{$.GoType}} = iota +{{- else}} + {{.Name}} +{{- end}} +{{- end}} +) +` + +type member struct { + Name string + First bool +} + +type templateData struct { + Header string + Package string + GoType string + EnumName string + Members []member +} + +func main() { + header := flag.String("header", "", "path to C header file") + enumName := flag.String("enum", "", "C enum name (e.g. bpf_prog_type)") + goType := flag.String("type", "", "Go type name (e.g. ProgramType)") + pkg := flag.String("pkg", "goebpf", "Go package name") + out := flag.String("out", "", "output file (default: stdout)") + flag.Parse() + + if *header == "" || *enumName == "" || *goType == "" { + fmt.Fprintln(os.Stderr, "bpf_enum_gen: -header, -enum, and -type are required") + flag.Usage() + os.Exit(1) + } + + members, err := parseEnum(*header, *enumName) + if err != nil { + fmt.Fprintf(os.Stderr, "bpf_enum_gen: %v\n", err) + os.Exit(1) + } + + goNames := toGoNames(members, *goType) + + data := templateData{ + Header: *header, + Package: *pkg, + GoType: *goType, + EnumName: *enumName, + Members: goNames, + } + + w := os.Stdout + if *out != "" { + f, err := os.Create(*out) + if err != nil { + fmt.Fprintf(os.Stderr, "bpf_enum_gen: %v\n", err) + os.Exit(1) + } + defer f.Close() + w = f + } + + tmpl := template.Must(template.New("output").Parse(outputTemplate)) + if err := tmpl.Execute(w, data); err != nil { + fmt.Fprintf(os.Stderr, "bpf_enum_gen: %v\n", err) + os.Exit(1) + } +} + +// parseEnum extracts the raw C member names from the named enum in the file. +func parseEnum(path, enumName string) ([]string, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + target := "enum " + enumName + " {" + var members []string + inEnum := false + depth := 0 + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + // Strip line comments + if idx := strings.Index(line, "//"); idx >= 0 { + line = strings.TrimSpace(line[:idx]) + } + if idx := strings.Index(line, "/*"); idx >= 0 { + line = strings.TrimSpace(line[:idx]) + } + + if !inEnum { + if strings.Contains(line, target) { + inEnum = true + depth = strings.Count(line, "{") - strings.Count(line, "}") + // Grab any member on the same line as the opening brace + after := line[strings.Index(line, "{")+1:] + collectMembers(after, &members) + } + continue + } + + depth += strings.Count(line, "{") - strings.Count(line, "}") + if depth <= 0 { + // Last line of enum body before closing brace + before := line + if idx := strings.LastIndex(line, "}"); idx >= 0 { + before = line[:idx] + } + collectMembers(before, &members) + break + } + collectMembers(line, &members) + } + + if err := scanner.Err(); err != nil { + return nil, err + } + if !inEnum { + return nil, fmt.Errorf("enum %q not found in %s", enumName, path) + } + return members, nil +} + +// collectMembers extracts comma-separated C identifiers (member names) from a +// partial enum body line, stripping optional = assignments. +func collectMembers(line string, out *[]string) { + // Strip inline comments again (already done at line level, but handle residue) + if idx := strings.Index(line, "/*"); idx >= 0 { + line = line[:idx] + } + for _, tok := range strings.Split(line, ",") { + tok = strings.TrimSpace(tok) + if tok == "" { + continue + } + // Strip = value + if idx := strings.Index(tok, "="); idx >= 0 { + tok = strings.TrimSpace(tok[:idx]) + } + if tok == "" || tok == "{" || tok == "}" { + continue + } + // Must look like a C identifier + if isIdent(tok) { + *out = append(*out, tok) + } + } +} + +func isIdent(s string) bool { + if s == "" { + return false + } + for i, r := range s { + if i == 0 && !unicode.IsLetter(r) && r != '_' { + return false + } + if i > 0 && !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '_' { + return false + } + } + return true +} + +// toGoNames converts C enum member names to Go identifiers. +// +// Strategy: +// 1. Find the longest common prefix that is a full underscore-delimited +// token boundary (shared by all non-sentinel members). +// 2. Strip that prefix, title-case the remaining words, prepend goType. +// 3. __MAX_* sentinel → maxBpf. +func toGoNames(cNames []string, goType string) []member { + // Separate sentinel (__MAX_*) from real members + var real []string + var sentinels []string + for _, n := range cNames { + if strings.HasPrefix(n, "__MAX_") || (strings.HasPrefix(n, "__") && strings.HasSuffix(n, "_MAX")) { + sentinels = append(sentinels, n) + } else { + real = append(real, n) + } + } + + prefix := longestCommonTokenPrefix(real) + + var result []member + for i, n := range real { + rest := strings.TrimPrefix(n, prefix) + result = append(result, member{ + Name: goType + titleCase(rest), + First: i == 0, + }) + } + + for range sentinels { + // __MAX_BPF_PROG_TYPE → maxBpfProgramType (always derived from goType) + // __MAX_BPF_ATTACH_TYPE → maxBpfAttachType + result = append(result, member{Name: "maxBpf" + goType}) + } + + return result +} + +// longestCommonTokenPrefix finds the longest prefix, measured in whole +// underscore-delimited tokens, shared by all strings. +func longestCommonTokenPrefix(ss []string) string { + if len(ss) == 0 { + return "" + } + parts := strings.Split(ss[0], "_") + // Try prefixes from longest to shortest + for length := len(parts); length > 0; length-- { + candidate := strings.Join(parts[:length], "_") + "_" + allMatch := true + for _, s := range ss[1:] { + if !strings.HasPrefix(s, candidate) { + allMatch = false + break + } + } + if allMatch { + return candidate + } + } + return "" +} + +// titleCase converts SNAKE_CASE to TitleCase. +// Special handling for well-known abbreviations to match existing names. +func titleCase(s string) string { + words := strings.Split(s, "_") + var b strings.Builder + for _, w := range words { + if w == "" { + continue + } + b.WriteString(titleCaseWord(w)) + } + return b.String() +} + +// knownAbbreviations are substrings that should keep their capitalisation. +// Checked case-insensitively; the value is the exact output spelling. +var knownAbbreviations = map[string]string{ + "xdp": "Xdp", + "skb": "Skb", + "cls": "Cls", + "act": "Act", + "ops": "Ops", + "msg": "Msg", + "sk": "Sk", + "lsm": "Lsm", + "lrc": "Lrc", + "udp": "Udp", + "tcp": "Tcp", + "ipv4": "Ipv4", + "ipv6": "Ipv6", +} + +func titleCaseWord(w string) string { + lower := strings.ToLower(w) + if v, ok := knownAbbreviations[lower]; ok { + return v + } + if len(w) == 0 { + return w + } + return strings.ToUpper(w[:1]) + strings.ToLower(w[1:]) +} diff --git a/program_base.go b/program_base.go index 9fc33b3..ef8f4ad 100644 --- a/program_base.go +++ b/program_base.go @@ -57,89 +57,8 @@ import ( "unsafe" ) -// ProgramType is eBPF program types enum -type ProgramType int - -// Must be in sync with enum bpf_prog_type from -const ( - ProgramTypeUnspec ProgramType = iota - ProgramTypeSocketFilter - ProgramTypeKprobe - ProgramTypeSchedCls - ProgramTypeSchedAct - ProgramTypeTracepoint - ProgramTypeXdp - ProgramTypePerfEvent - ProgramTypeCgroupSkb - ProgramTypeCgroupSock - ProgramTypeLwtIn - ProgramTypeLwtOut - ProgramTypeLwtXmit - ProgramTypeSockOps -) - -// AttachType is eBPF program attach type -type AttachType int - -// Must be in sync with enum bpf_attach_type from -const ( - AttachTypeCgroupInetIngress AttachType = iota - AttachTypeCgroupInetEgress - AttachTypeCgroupInetSockCreate - AttachTypeCgroupSockOps - AttachTypeSkSkbStreamParser - AttachTypeSkSkbStreamVerdict - AttachTypeCgroupDevice - AttachTypeSkMsgVerdict - AttachTypeCgroupInet4Bind - AttachTypeCgroupInet6Bind - AttachTypeCgroupInet4Connect - AttachTypeCgroupInet6Connect - AttachTypeCgroupInet4PostBind - AttachTypeCgroupInet6PostBind - AttachTypeCgroupUdp4Sendmsg - AttachTypeCgroupUdp6Sendmsg - AttachTypeLircMode2 - AttachTypeFlowDissector - AttachTypeCgroupSysctl - AttachTypeCgroupUdp4Recvmsg - AttachTypeCgroupUdp6Recvmsg - AttachTypeCgroupGetsockopt - AttachTypeCgroupSetsockopt - AttachTypeTraceRawTp - AttachTypeTraceFentry - AttachTypeTraceFexit - AttachTypeModifyReturn - AttachTypeLsmMac - AttachTypeTraceIter - AttachTypeCgroupInet4Getpeername - AttachTypeCgroupInet6Getpeername - AttachTypeCgroupInet4Getsockname - AttachTypeCgroupInet6Getsockname - AttachTypeXdpDevmap - AttachTypeCgroupInetSockRelease - AttachTypeXdpCpumap - AttachTypeSkLookup - AttachTypeXdp - AttachTypeSkSkbVerdict - AttachTypeSkReuseportSelect - AttachTypeSkReuseportSelectOrMigrate - AttachTypePerfEvent - AttachTypeTraceKprobeMulti - AttachTypeLsmCgroup - AttachTypeStructOps - AttachTypeNetfilter - AttachTypeTcxIngress - AttachTypeTcxEgress - AttachTypeTraceUprobeMulti - AttachTypeCgroupUnixConnect - AttachTypeCgroupUnixSendmsg - AttachTypeCgroupUnixRecvmsg - AttachTypeCgroupUnixGetpeername - AttachTypeCgroupUnixGetsockname - AttachTypeNetkitPrimary - AttachTypeNetkitPeer -) +//go:generate go run ./cmd/bpf_enum_gen -header /usr/include/linux/bpf.h -enum bpf_prog_type -type ProgramType -pkg goebpf -out program_type.gen.go +//go:generate go run ./cmd/bpf_enum_gen -header /usr/include/linux/bpf.h -enum bpf_attach_type -type AttachType -pkg goebpf -out attach_type.gen.go func (t ProgramType) String() string { switch t { diff --git a/program_type.gen.go b/program_type.gen.go new file mode 100644 index 0000000..7897db5 --- /dev/null +++ b/program_type.gen.go @@ -0,0 +1,44 @@ +// @generated by bpf_enum_gen. DO NOT EDIT. +// Source: /usr/include/linux/bpf.h + +package goebpf + +// ProgramType is generated from enum bpf_prog_type in /usr/include/linux/bpf.h +type ProgramType int + +const ( + ProgramTypeUnspec ProgramType = iota + ProgramTypeSocketFilter + ProgramTypeKprobe + ProgramTypeSchedCls + ProgramTypeSchedAct + ProgramTypeTracepoint + ProgramTypeXdp + ProgramTypePerfEvent + ProgramTypeCgroupSkb + ProgramTypeCgroupSock + ProgramTypeLwtIn + ProgramTypeLwtOut + ProgramTypeLwtXmit + ProgramTypeSockOps + ProgramTypeSkSkb + ProgramTypeCgroupDevice + ProgramTypeSkMsg + ProgramTypeRawTracepoint + ProgramTypeCgroupSockAddr + ProgramTypeLwtSeg6local + ProgramTypeLircMode2 + ProgramTypeSkReuseport + ProgramTypeFlowDissector + ProgramTypeCgroupSysctl + ProgramTypeRawTracepointWritable + ProgramTypeCgroupSockopt + ProgramTypeTracing + ProgramTypeStructOps + ProgramTypeExt + ProgramTypeLsm + ProgramTypeSkLookup + ProgramTypeSyscall + ProgramTypeNetfilter + maxBpfProgramType +)