Skip to content

Commit 1e35718

Browse files
feat: switch binary to attach/detach mode, to use in initcontainer
1 parent 4f64164 commit 1e35718

File tree

3 files changed

+114
-165
lines changed

3 files changed

+114
-165
lines changed

bpf-prog/azure-block-iptables/cmd/azure-block-iptables/main.go

Lines changed: 64 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -4,118 +4,61 @@
44
package main
55

66
import (
7-
"context"
7+
"flag"
88
"fmt"
99
"log"
1010
"os"
11-
"os/signal"
12-
"path/filepath"
13-
"syscall"
14-
"time"
1511

1612
"github.com/Azure/azure-container-networking/bpf-prog/azure-block-iptables/pkg/bpfprogram"
1713
"github.com/cilium/ebpf/rlimit"
18-
"github.com/fsnotify/fsnotify"
1914
)
2015

21-
const (
22-
DefaultConfigFile = "/etc/cni/net.d/iptables-allow-list"
23-
)
16+
// ProgramVersion is set during build
17+
var version = "unknown"
2418

25-
// BlockConfig holds configuration for the application
26-
type BlockConfig struct {
27-
ConfigFile string
19+
// Config holds configuration for the application
20+
type Config struct {
21+
Mode string // "attach" or "detach"
2822
AttacherFactory bpfprogram.AttacherFactory
2923
}
3024

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-
}
25+
// parseArgs parses command line arguments and returns the configuration
26+
func parseArgs() (*Config, error) {
27+
var (
28+
mode = flag.String("mode", "", "Operation mode: 'attach' or 'detach' (required)")
29+
showVersion = flag.Bool("version", false, "Show version information")
30+
showHelp = flag.Bool("help", false, "Show help information")
31+
)
3832

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-
}
33+
flag.Parse()
5134

52-
if stat.Size() == 0 {
53-
log.Printf("Config file %s is empty", filename)
54-
return 1 // File empty
35+
if *showVersion {
36+
fmt.Printf("azure-block-iptables version %s\n", version)
37+
os.Exit(0)
5538
}
5639

57-
log.Printf("Config file %s has content (size: %d bytes)", filename, stat.Size())
58-
return 0 // File exists and has content
59-
}
60-
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)
40+
if *showHelp {
41+
flag.PrintDefaults()
42+
os.Exit(0)
6643
}
6744

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)
45+
if *mode == "" {
46+
return nil, fmt.Errorf("mode is required. Use -mode=attach or -mode=detach")
7447
}
7548

76-
log.Printf("Watching directory %s for changes to %s", dir, configFile)
77-
return watcher, nil
78-
}
79-
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)
98-
}
99-
}
100-
}
101-
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
49+
if *mode != "attach" && *mode != "detach" {
50+
return nil, fmt.Errorf("invalid mode '%s'. Must be 'attach' or 'detach'", *mode)
10751
}
10852

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)
53+
return &Config{
54+
Mode: *mode,
55+
AttacherFactory: bpfprogram.NewProgram,
56+
}, nil
11457
}
11558

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)
59+
// attachMode handles the attach operation
60+
func attachMode(config *Config) error {
61+
log.Println("Starting attach mode...")
11962

12063
// Remove memory limit for eBPF
12164
if err := rlimit.RemoveMemlock(); err != nil {
@@ -124,62 +67,45 @@ func run(config *BlockConfig) error {
12467

12568
// Initialize BPF program attacher using the factory
12669
bp := config.AttacherFactory()
127-
defer bp.Close()
12870

129-
// Check initial state of the config file
130-
checkFileStatusAndUpdateBPF(config.ConfigFile, bp)
131-
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)
71+
// Attach the BPF program
72+
if err := bp.Attach(); err != nil {
73+
return fmt.Errorf("failed to attach BPF program: %w", err)
13674
}
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-
}
75+
76+
log.Println("BPF program attached successfully")
77+
return nil
78+
}
79+
80+
// detachMode handles the detach operation
81+
func detachMode(config *Config) error {
82+
log.Println("Starting detach mode...")
83+
84+
// Initialize BPF program attacher using the factory
85+
bp := config.AttacherFactory()
86+
bp.Detach()
87+
log.Println("BPF program detached successfully")
88+
return nil
89+
}
90+
91+
// run is the main application logic
92+
func run(config *Config) error {
93+
switch config.Mode {
94+
case "attach":
95+
return attachMode(config)
96+
case "detach":
97+
return detachMode(config)
98+
default:
99+
return fmt.Errorf("unsupported mode: %s", config.Mode)
174100
}
175101
}
176102

177103
func main() {
178-
config := NewDefaultBlockConfig()
179-
180-
// Parse command line arguments
181-
if len(os.Args) > 1 {
182-
config.ConfigFile = os.Args[1]
104+
config, err := parseArgs()
105+
if err != nil {
106+
log.Printf("Error parsing arguments: %v", err)
107+
flag.PrintDefaults()
108+
os.Exit(1)
183109
}
184110

185111
if err := run(config); err != nil {

bpf-prog/azure-block-iptables/pkg/bpfprogram/interface.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ package bpfprogram
33
// Attacher defines the interface for BPF program attachment operations.
44
// This interface allows for dependency injection and easier testing with mock implementations.
55
type Attacher interface {
6-
// Attach attaches the BPF program to LSM hooks
6+
// Attach attaches the BPF program to LSM hooks and pins the links and maps
77
Attach() error
88

9-
// Detach detaches the BPF program from LSM hooks
9+
// Unpins the links and maps (causes detachment)
1010
Detach() error
1111

1212
// IsAttached returns true if the BPF program is currently attached

bpf-prog/azure-block-iptables/pkg/bpfprogram/program.go

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ const (
2020
BPFMapPinPath = "/sys/fs/bpf/block-iptables"
2121
// EventCounterMapName is the name used for pinning the event counter map
2222
EventCounterMapName = "iptables_block_event_counter"
23+
// IptablesLegacyBlockProgramName is the name used for pinning the legacy iptables block program
24+
IptablesLegacyBlockProgramName = "iptables_legacy_block"
25+
// IptablesNftablesBlockProgramName is the name used for pinning the nftables block program
26+
IptablesNftablesBlockProgramName = "iptables_nftables_block"
2327
// NetNSPath is the path to the host network namespace
2428
NetNSPath = "/proc/self/ns/net"
2529
)
@@ -75,6 +79,33 @@ func (p *Program) unpinEventCounterMap() error {
7579
return nil
7680
}
7781

82+
// unpinLinks unpins the links to BPF programs from the filesystem
83+
func (p *Program) unpinLinks() error {
84+
var errs []error
85+
86+
// Unpin the legacy iptables block program
87+
legacyPinPath := filepath.Join(BPFMapPinPath, IptablesLegacyBlockProgramName)
88+
if err := os.Remove(legacyPinPath); err != nil && !os.IsNotExist(err) {
89+
errs = append(errs, errors.Wrapf(err, "failed to remove pinned legacy program %s", legacyPinPath))
90+
} else {
91+
log.Printf("Legacy iptables block program unpinned from %s", legacyPinPath)
92+
}
93+
94+
// Unpin the nftables block program
95+
nftablesPinPath := filepath.Join(BPFMapPinPath, IptablesNftablesBlockProgramName)
96+
if err := os.Remove(nftablesPinPath); err != nil && !os.IsNotExist(err) {
97+
errs = append(errs, errors.Wrapf(err, "failed to remove pinned nftables program %s", nftablesPinPath))
98+
} else {
99+
log.Printf("Nftables block program unpinned from %s", nftablesPinPath)
100+
}
101+
102+
if len(errs) > 0 {
103+
return errors.Errorf("failed to unpin programs: %v", errs)
104+
}
105+
106+
return nil
107+
}
108+
78109
func getHostNetnsInode() (uint64, error) {
79110
var stat syscall.Stat_t
80111
err := syscall.Stat(NetNSPath, &stat)
@@ -149,6 +180,9 @@ func (p *Program) Attach() error {
149180
p.objs = nil
150181
return errors.Wrap(err, "failed to attach iptables_legacy_block LSM")
151182
}
183+
184+
pinPath := filepath.Join(BPFMapPinPath, IptablesLegacyBlockProgramName)
185+
l.Pin(pinPath)
152186
links = append(links, l)
153187
}
154188

@@ -167,6 +201,8 @@ func (p *Program) Attach() error {
167201
p.objs = nil
168202
return errors.Wrap(err, "failed to attach block_nf_netlink LSM")
169203
}
204+
pinPath := filepath.Join(BPFMapPinPath, IptablesNftablesBlockProgramName)
205+
l.Pin(pinPath)
170206
links = append(links, l)
171207
}
172208

@@ -177,38 +213,25 @@ func (p *Program) Attach() error {
177213
return nil
178214
}
179215

180-
// Detach detaches the BPF program from LSM hooks.
181216
func (p *Program) Detach() error {
182-
if !p.attached {
183-
log.Println("BPF program already detached")
184-
return nil
185-
}
217+
return p.cleanupPinnedResources()
218+
}
186219

187-
log.Println("Detaching BPF program...")
220+
// cleanupPinnedResources removes pinned resources even when the program is not currently attached
221+
func (p *Program) cleanupPinnedResources() error {
222+
log.Println("Cleaning up pinned resources...")
188223

189-
// Unpin the event counter map from filesystem
190-
if err := p.unpinEventCounterMap(); err != nil {
191-
log.Printf("Warning: failed to unpin event counter map: %v", err)
224+
// Try to unpin links
225+
if err := p.unpinLinks(); err != nil {
226+
log.Printf("Warning: failed to unpin links: %v", err)
192227
}
193228

194-
// Close all links
195-
for _, l := range p.links {
196-
if err := l.Close(); err != nil {
197-
log.Printf("Warning: failed to close link: %v", err)
198-
}
199-
}
200-
p.links = nil
201-
202-
// Close objects
203-
if p.objs != nil {
204-
if err := p.objs.Close(); err != nil {
205-
log.Printf("Warning: failed to close BPF objects: %v", err)
206-
}
207-
p.objs = nil
229+
// Try to unpin the event counter map
230+
if err := p.unpinEventCounterMap(); err != nil {
231+
log.Printf("Warning: failed to unpin event counter map: %v", err)
208232
}
209233

210-
p.attached = false
211-
log.Println("BPF program detached successfully")
234+
log.Println("Pinned resources cleanup completed")
212235
return nil
213236
}
214237

0 commit comments

Comments
 (0)