Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions attach_type.gen.go
Original file line number Diff line number Diff line change
@@ -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
)
303 changes: 303 additions & 0 deletions cmd/bpf_enum_gen/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
// bpf_enum_gen generates Go constants from a C enum in a header file.
// Usage: bpf_enum_gen -header <file> -enum <enum_name> -type <GoType> -pkg <package> -out <file>
//
// 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<suffix>.
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 = <value> 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<TitleCaseSuffix>.
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:])
}
Loading
Loading