|
4 | 4 | package main |
5 | 5 |
|
6 | 6 | import ( |
7 | | - "context" |
| 7 | + "flag" |
8 | 8 | "fmt" |
9 | 9 | "log" |
10 | 10 | "os" |
11 | | - "os/signal" |
12 | | - "path/filepath" |
13 | | - "syscall" |
14 | | - "time" |
15 | 11 |
|
16 | 12 | "github.com/Azure/azure-container-networking/bpf-prog/azure-block-iptables/pkg/bpfprogram" |
17 | | - "github.com/cilium/ebpf/rlimit" |
18 | | - "github.com/fsnotify/fsnotify" |
| 13 | + "github.com/pkg/errors" |
19 | 14 | ) |
20 | 15 |
|
21 | | -const ( |
22 | | - DefaultConfigFile = "/etc/cni/net.d/iptables-allow-list" |
| 16 | +// ProgramVersion is set during build |
| 17 | +var ( |
| 18 | + version = "unknown" |
| 19 | + ErrModeRequired = errors.New("mode is required") |
| 20 | + ErrInvalidMode = errors.New("invalid mode. Use -mode=attach or -mode=detach") |
23 | 21 | ) |
24 | 22 |
|
25 | | -// BlockConfig holds configuration for the application |
26 | | -type BlockConfig struct { |
27 | | - ConfigFile string |
| 23 | +// Config holds configuration for the application |
| 24 | +type Config struct { |
| 25 | + Mode string // "attach" or "detach" |
| 26 | + Overwrite bool // force detach before attach |
28 | 27 | AttacherFactory bpfprogram.AttacherFactory |
29 | 28 | } |
30 | 29 |
|
31 | | -// NewDefaultBlockConfig creates a new BlockConfig with default values |
32 | | -func NewDefaultBlockConfig() *BlockConfig { |
33 | | - return &BlockConfig{ |
34 | | - ConfigFile: DefaultConfigFile, |
35 | | - AttacherFactory: bpfprogram.NewProgram, |
36 | | - } |
37 | | -} |
| 30 | +// parseArgs parses command line arguments and returns the configuration |
| 31 | +func parseArgs() (*Config, error) { |
| 32 | + var ( |
| 33 | + mode = flag.String("mode", "", "Operation mode: 'attach' or 'detach' (required)") |
| 34 | + overwrite = flag.Bool("overwrite", false, "Force detach before attach (only applies to attach mode)") |
| 35 | + showVersion = flag.Bool("version", false, "Show version information") |
| 36 | + showHelp = flag.Bool("help", false, "Show help information") |
| 37 | + ) |
38 | 38 |
|
39 | | -// isFileEmptyOrMissing checks if the config file exists and has content |
40 | | -// Returns: 1 if empty, 0 if has content, -1 if missing/error |
41 | | -func isFileEmptyOrMissing(filename string) int { |
42 | | - stat, err := os.Stat(filename) |
43 | | - if err != nil { |
44 | | - if os.IsNotExist(err) { |
45 | | - log.Printf("Config file %s does not exist", filename) |
46 | | - return -1 // File missing |
47 | | - } |
48 | | - log.Printf("Error checking file %s: %v", filename, err) |
49 | | - return -1 // Treat errors as missing |
50 | | - } |
| 39 | + flag.Parse() |
51 | 40 |
|
52 | | - if stat.Size() == 0 { |
53 | | - log.Printf("Config file %s is empty", filename) |
54 | | - return 1 // File empty |
| 41 | + if *showVersion { |
| 42 | + fmt.Printf("azure-block-iptables version %s\n", version) |
| 43 | + os.Exit(0) |
55 | 44 | } |
56 | 45 |
|
57 | | - log.Printf("Config file %s has content (size: %d bytes)", filename, stat.Size()) |
58 | | - return 0 // File exists and has content |
59 | | -} |
| 46 | + if *showHelp { |
| 47 | + flag.PrintDefaults() |
| 48 | + os.Exit(0) |
| 49 | + } |
60 | 50 |
|
61 | | -// setupFileWatcher sets up a file watcher for the config file |
62 | | -func setupFileWatcher(configFile string) (*fsnotify.Watcher, error) { |
63 | | - watcher, err := fsnotify.NewWatcher() |
64 | | - if err != nil { |
65 | | - return nil, fmt.Errorf("failed to create file watcher: %w", err) |
| 51 | + if *mode == "" { |
| 52 | + return nil, ErrModeRequired |
66 | 53 | } |
67 | 54 |
|
68 | | - // Watch the directory containing the config file |
69 | | - dir := filepath.Dir(configFile) |
70 | | - err = watcher.Add(dir) |
71 | | - if err != nil { |
72 | | - watcher.Close() |
73 | | - return nil, fmt.Errorf("failed to add watch for directory %s: %w", dir, err) |
| 55 | + if *mode != "attach" && *mode != "detach" { |
| 56 | + return nil, ErrInvalidMode |
74 | 57 | } |
75 | 58 |
|
76 | | - log.Printf("Watching directory %s for changes to %s", dir, configFile) |
77 | | - return watcher, nil |
| 59 | + return &Config{ |
| 60 | + Mode: *mode, |
| 61 | + Overwrite: *overwrite, |
| 62 | + AttacherFactory: bpfprogram.NewProgram, |
| 63 | + }, nil |
78 | 64 | } |
79 | 65 |
|
80 | | -func checkFileStatusAndUpdateBPF(configFile string, bp bpfprogram.Attacher) { |
81 | | - // Check current state and take action |
82 | | - fileState := isFileEmptyOrMissing(configFile) |
83 | | - switch fileState { |
84 | | - case 1: // File is empty |
85 | | - log.Println("File is empty, attaching BPF program") |
86 | | - if err := bp.Attach(); err != nil { // No-op if already attached |
87 | | - log.Printf("Failed to attach BPF program: %v", err) |
88 | | - } |
89 | | - case 0: // File has content |
90 | | - log.Println("File has content, detaching BPF program") |
91 | | - if err := bp.Detach(); err != nil { // No-op if already detached |
92 | | - log.Printf("Failed to detach BPF program: %v", err) |
93 | | - } |
94 | | - case -1: // File is missing |
95 | | - log.Println("Config file was deleted, detaching BPF program") |
96 | | - if err := bp.Detach(); err != nil { // No-op if already detached |
97 | | - log.Printf("Failed to detach BPF program: %v", err) |
| 66 | +// attachMode handles the attach operation |
| 67 | +func attachMode(config *Config) error { |
| 68 | + log.Println("Starting attach mode...") |
| 69 | + |
| 70 | + // Initialize BPF program attacher using the factory |
| 71 | + bp := config.AttacherFactory() |
| 72 | + |
| 73 | + // If overwrite is enabled, first detach any existing programs |
| 74 | + if config.Overwrite { |
| 75 | + log.Println("Overwrite mode enabled, detaching any existing programs first...") |
| 76 | + if err := bp.Detach(); err != nil { |
| 77 | + log.Printf("Warning: failed to detach existing programs: %v", err) |
98 | 78 | } |
99 | 79 | } |
100 | | -} |
101 | 80 |
|
102 | | -// handleFileEvent processes file system events |
103 | | -func handleFileEvent(event fsnotify.Event, configFile string, bp bpfprogram.Attacher) { |
104 | | - // Check if the event is for our config file |
105 | | - if filepath.Base(event.Name) != filepath.Base(configFile) { |
106 | | - return |
| 81 | + // Attach the BPF program |
| 82 | + if err := bp.Attach(); err != nil { |
| 83 | + return errors.Wrap(err, "failed to attach BPF program") |
107 | 84 | } |
108 | 85 |
|
109 | | - log.Printf("Config file changed: %s (operation: %s)", event.Name, event.Op) |
110 | | - |
111 | | - // Small delay to handle rapid successive events |
112 | | - time.Sleep(100 * time.Millisecond) |
113 | | - checkFileStatusAndUpdateBPF(configFile, bp) |
| 86 | + log.Println("BPF program attached successfully") |
| 87 | + return nil |
114 | 88 | } |
115 | 89 |
|
116 | | -// run is the main application logic, separated for easier testing |
117 | | -func run(config *BlockConfig) error { |
118 | | - log.Printf("Using config file: %s", config.ConfigFile) |
119 | | - |
120 | | - // Remove memory limit for eBPF |
121 | | - if err := rlimit.RemoveMemlock(); err != nil { |
122 | | - return fmt.Errorf("failed to remove memlock rlimit: %w", err) |
123 | | - } |
| 90 | +// detachMode handles the detach operation |
| 91 | +func detachMode(config *Config) error { |
| 92 | + log.Println("Starting detach mode...") |
124 | 93 |
|
125 | 94 | // Initialize BPF program attacher using the factory |
126 | 95 | bp := config.AttacherFactory() |
127 | | - defer bp.Close() |
128 | | - |
129 | | - // Check initial state of the config file |
130 | | - checkFileStatusAndUpdateBPF(config.ConfigFile, bp) |
131 | 96 |
|
132 | | - // Setup file watcher |
133 | | - watcher, err := setupFileWatcher(config.ConfigFile) |
134 | | - if err != nil { |
135 | | - return fmt.Errorf("failed to setup file watcher: %w", err) |
| 97 | + // Detach the BPF program |
| 98 | + if err := bp.Detach(); err != nil { |
| 99 | + return errors.Wrap(err, "failed to detach BPF program") |
136 | 100 | } |
137 | | - defer watcher.Close() |
138 | | - |
139 | | - // Setup signal handling |
140 | | - ctx, cancel := context.WithCancel(context.Background()) |
141 | | - defer cancel() |
142 | | - |
143 | | - sigChan := make(chan os.Signal, 1) |
144 | | - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) |
145 | | - |
146 | | - log.Println("Starting file watch loop...") |
147 | | - |
148 | | - // Main event loop |
149 | | - for { |
150 | | - select { |
151 | | - case event, ok := <-watcher.Events: |
152 | | - if !ok { |
153 | | - log.Println("Watcher events channel closed") |
154 | | - return nil |
155 | | - } |
156 | | - handleFileEvent(event, config.ConfigFile, bp) |
157 | | - |
158 | | - case err, ok := <-watcher.Errors: |
159 | | - if !ok { |
160 | | - log.Println("Watcher errors channel closed") |
161 | | - return nil |
162 | | - } |
163 | | - log.Printf("Watcher error: %v", err) |
164 | | - |
165 | | - case sig := <-sigChan: |
166 | | - log.Printf("Received signal: %v", sig) |
167 | | - cancel() |
168 | | - return nil |
169 | | - |
170 | | - case <-ctx.Done(): |
171 | | - log.Println("Context cancelled, exiting") |
172 | | - return nil |
173 | | - } |
| 101 | + |
| 102 | + log.Println("BPF program detached successfully") |
| 103 | + return nil |
| 104 | +} |
| 105 | + |
| 106 | +// run is the main application logic |
| 107 | +func run(config *Config) error { |
| 108 | + switch config.Mode { |
| 109 | + case "attach": |
| 110 | + return attachMode(config) |
| 111 | + case "detach": |
| 112 | + return detachMode(config) |
| 113 | + default: |
| 114 | + return ErrInvalidMode |
174 | 115 | } |
175 | 116 | } |
176 | 117 |
|
177 | 118 | func main() { |
178 | | - config := NewDefaultBlockConfig() |
179 | | - |
180 | | - // Parse command line arguments |
181 | | - if len(os.Args) > 1 { |
182 | | - config.ConfigFile = os.Args[1] |
| 119 | + config, err := parseArgs() |
| 120 | + if err != nil { |
| 121 | + log.Printf("Error parsing arguments: %v", err) |
| 122 | + flag.PrintDefaults() |
| 123 | + os.Exit(1) |
183 | 124 | } |
184 | 125 |
|
185 | 126 | if err := run(config); err != nil { |
|
0 commit comments