Skip to content

Commit 72bdaf1

Browse files
feat: include iptables block binary in iptables monitor image (#3945)
* feat: include iptables block binary in iptables monitor image * chore:dockerfiles * feat: switch binary to attach/detach mode, to use in initcontainer * test: update tests * feat: have an option to overwrite existing pinned programs * fix: lint * Update azure-iptables-monitor/Dockerfile Co-authored-by: Copilot <[email protected]> Signed-off-by: Santhosh Prabhu <[email protected]> * fix: fix compilation --------- Signed-off-by: Santhosh Prabhu <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent bcd6fb7 commit 72bdaf1

File tree

8 files changed

+198
-210
lines changed

8 files changed

+198
-210
lines changed

Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ REPO_ROOT ?= $(shell git rev-parse --show-toplevel)
3535
REVISION ?= $(shell git rev-parse --short HEAD)
3636
ACN_VERSION ?= $(shell git describe --exclude "azure-iptables-monitor*" --exclude "azure-ip-masq-merger*" --exclude "azure-ipam*" --exclude "dropgz*" --exclude "zapai*" --exclude "ipv6-hp-bpf*" --exclude "azure-block-iptables*" --tags --always)
3737
IPV6_HP_BPF_VERSION ?= $(notdir $(shell git describe --match "ipv6-hp-bpf*" --tags --always))
38-
AZURE_BLOCK_IPTABLES_VERSION ?= $(notdir $(shell git describe --match "azure-block-iptables*" --tags --always))
38+
AZURE_BLOCK_IPTABLES_VERSION ?= $(notdir $(shell git describe --match "azure-block-iptables*" --tags --always))
3939
AZURE_IPAM_VERSION ?= $(notdir $(shell git describe --match "azure-ipam*" --tags --always))
4040
AZURE_IP_MASQ_MERGER_VERSION ?= $(notdir $(shell git describe --match "azure-ip-masq-merger*" --tags --always))
41-
AZURE_IPTABLES_MONITOR_VERSION ?= $(notdir $(shell git describe --match "azure-iptables-monitor*" --tags --always))
41+
AZURE_IPTABLES_MONITOR_VERSION ?= $(notdir $(shell git describe --match "azure-block-iptables*" --match "azure-iptables-monitor*" --tags --always))
4242
CNI_VERSION ?= $(ACN_VERSION)
4343
CNS_VERSION ?= $(ACN_VERSION)
4444
NPM_VERSION ?= $(ACN_VERSION)
@@ -467,7 +467,8 @@ azure-iptables-monitor-image: ## build azure-iptables-monitor container image.
467467
TAG=$(AZURE_IPTABLES_MONITOR_PLATFORM_TAG) \
468468
TARGET=$(OS) \
469469
OS=$(OS) \
470-
ARCH=$(ARCH)
470+
ARCH=$(ARCH) \
471+
EXTRA_BUILD_ARGS="--build-arg AZURE_BLOCK_IPTABLES_VERSION=$(AZURE_BLOCK_IPTABLES_VERSION)"
471472

472473
azure-iptables-monitor-image-push: ## push azure-iptables-monitor container image.
473474
$(MAKE) container-push \

azure-iptables-monitor/Dockerfile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,42 @@ WORKDIR /azure-iptables-monitor
1717
COPY ./azure-iptables-monitor .
1818
RUN GOOS=$OS CGO_ENABLED=0 go build -a -o /go/bin/iptables-monitor -trimpath -ldflags "-s -w -X main.version="$VERSION"" -gcflags="-dwarflocationlists=true" .
1919

20+
FROM go AS azure-block-iptables
21+
ARG OS
22+
ARG AZURE_BLOCK_IPTABLES_VERSION
23+
ARG ARCH
24+
WORKDIR /azure-container-networking
25+
COPY ./bpf-prog/azure-block-iptables ./bpf-prog/azure-block-iptables
26+
COPY ./go.mod ./go.sum ./
27+
# Install BPF development dependencies for Azure Linux (mariner)
28+
RUN tdnf install -y llvm clang libbpf-devel gcc binutils glibc
29+
# Set up C include path for BPF
30+
ENV C_INCLUDE_PATH=/usr/include/bpf
31+
# Set up architecture-specific symlinks for cross-compilation support
32+
RUN if [ "$ARCH" = "amd64" ]; then \
33+
ARCH_DIR=x86_64-linux-gnu; \
34+
elif [ "$ARCH" = "arm64" ]; then \
35+
ARCH_DIR=aarch64-linux-gnu; \
36+
fi && \
37+
if [ -n "$ARCH_DIR" ] && [ -d "/usr/include/$ARCH_DIR" ]; then \
38+
for dir in /usr/include/"$ARCH_DIR"/*; do \
39+
if [ -d "$dir" ]; then \
40+
ln -sfn "$dir" /usr/include/$(basename "$dir") || echo "Warning: Failed to create symlink for directory $dir" >&2; \
41+
elif [ -f "$dir" ]; then \
42+
ln -Tsfn "$dir" /usr/include/$(basename "$dir") || echo "Warning: Failed to create symlink for file $dir" >&2; \
43+
fi; \
44+
done; \
45+
fi
46+
RUN GOOS=$OS CGO_ENABLED=0 go generate ./bpf-prog/azure-block-iptables/...
47+
RUN GOOS=$OS CGO_ENABLED=0 go build -a -o /go/bin/azure-block-iptables -trimpath -ldflags "-s -w -X main.version="$AZURE_BLOCK_IPTABLES_VERSION"" -gcflags="-dwarflocationlists=true" ./bpf-prog/azure-block-iptables/cmd/azure-block-iptables
48+
2049
FROM mariner-core AS iptables
2150
RUN tdnf install -y iptables
2251

2352
FROM mariner-distroless AS linux
2453
COPY --from=iptables /usr/sbin/*tables* /usr/sbin/
2554
COPY --from=iptables /usr/lib /usr/lib
2655
COPY --from=azure-iptables-monitor /go/bin/iptables-monitor azure-iptables-monitor
56+
COPY --from=azure-block-iptables /go/bin/azure-block-iptables azure-block-iptables
2757

2858
ENTRYPOINT ["/azure-iptables-monitor"]

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

Lines changed: 78 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -4,182 +4,123 @@
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"
17-
"github.com/cilium/ebpf/rlimit"
18-
"github.com/fsnotify/fsnotify"
13+
"github.com/pkg/errors"
1914
)
2015

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")
2321
)
2422

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
2827
AttacherFactory bpfprogram.AttacherFactory
2928
}
3029

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+
)
3838

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()
5140

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)
5544
}
5645

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+
}
6050

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
6653
}
6754

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
7457
}
7558

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
7864
}
7965

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)
9878
}
9979
}
100-
}
10180

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")
10784
}
10885

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
11488
}
11589

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...")
12493

12594
// Initialize BPF program attacher using the factory
12695
bp := config.AttacherFactory()
127-
defer bp.Close()
128-
129-
// Check initial state of the config file
130-
checkFileStatusAndUpdateBPF(config.ConfigFile, bp)
13196

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")
136100
}
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
174115
}
175116
}
176117

177118
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)
183124
}
184125

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

0 commit comments

Comments
 (0)