From 933a1f9c1af2d62bf4b2377375b20eac5291b1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E7=84=B6?= Date: Thu, 26 Feb 2026 09:58:18 +0800 Subject: [PATCH 1/4] feat(egress): unified internal logger --- .github/workflows/egress-test.yaml.yml | 9 ++- components/egress/go.mod | 5 ++ components/egress/go.sum | 14 +++++ components/egress/main.go | 22 ++++++-- components/egress/nameserver.go | 6 +- components/egress/nft.go | 8 +-- .../egress/pkg/constants/configuration.go | 1 + components/egress/pkg/dnsproxy/proxy.go | 6 +- components/egress/pkg/log/logger.go | 55 +++++++++++++++++++ components/egress/policy_server.go | 6 +- components/egress/tests/bench-dns-nft.sh | 8 +++ components/internal/logger/zap.go | 44 +++++++++++---- 12 files changed, 153 insertions(+), 31 deletions(-) create mode 100644 components/egress/pkg/log/logger.go diff --git a/.github/workflows/egress-test.yaml.yml b/.github/workflows/egress-test.yaml.yml index 102f9556..59fd1bab 100644 --- a/.github/workflows/egress-test.yaml.yml +++ b/.github/workflows/egress-test.yaml.yml @@ -23,14 +23,14 @@ jobs: go-version: '1.24.0' - name: Run Build + working-directory: components/egress run: | - cd components/egress go vet ./... go build . - name: Run tests + working-directory: components/egress run: | - cd components/egress go test ./... smoke: @@ -70,3 +70,8 @@ jobs: ./tests/bench-dns-nft.sh env: BENCH_SAMPLE_SIZE: "20" + + - name: Eval bench logs + if: always() + run: | + cat /tmp/egress-logs diff --git a/components/egress/go.mod b/components/egress/go.mod index c7304491..ef02bdf9 100644 --- a/components/egress/go.mod +++ b/components/egress/go.mod @@ -3,13 +3,18 @@ module github.com/alibaba/opensandbox/egress go 1.24.0 require ( + github.com/alibaba/opensandbox/internal v0.0.0 github.com/miekg/dns v1.1.61 golang.org/x/sys v0.31.0 ) require ( + go.uber.org/multierr v1.10.0 // indirect + go.uber.org/zap v1.27.0 // indirect golang.org/x/mod v0.18.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/tools v0.22.0 // indirect ) + +replace github.com/alibaba/opensandbox/internal => ../internal diff --git a/components/egress/go.sum b/components/egress/go.sum index 459f89cd..3e90717e 100644 --- a/components/egress/go.sum +++ b/components/egress/go.sum @@ -1,5 +1,17 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= @@ -10,3 +22,5 @@ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/components/egress/main.go b/components/egress/main.go index bca6d33b..c984e797 100644 --- a/components/egress/main.go +++ b/components/egress/main.go @@ -16,7 +16,6 @@ package main import ( "context" - "log" "os" "os/signal" "strings" @@ -25,12 +24,17 @@ import ( "github.com/alibaba/opensandbox/egress/pkg/constants" "github.com/alibaba/opensandbox/egress/pkg/dnsproxy" "github.com/alibaba/opensandbox/egress/pkg/iptables" + "github.com/alibaba/opensandbox/egress/pkg/log" + slogger "github.com/alibaba/opensandbox/internal/logger" ) func main() { ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer cancel() + ctx = withLogger(ctx) + defer log.Logger.Sync() + initialRules, err := dnsproxy.LoadPolicyFromEnvVar(constants.EnvEgressRules) if err != nil { log.Fatalf("failed to parse %s: %v", constants.EnvEgressRules, err) @@ -47,12 +51,12 @@ func main() { if err := proxy.Start(ctx); err != nil { log.Fatalf("failed to start dns proxy: %v", err) } - log.Println("dns proxy started on 127.0.0.1:15353") + log.Infof("dns proxy started on 127.0.0.1:15353") if err := iptables.SetupRedirect(15353); err != nil { log.Fatalf("failed to install iptables redirect: %v", err) } - log.Printf("iptables redirect configured (OUTPUT 53 -> 15353) with SO_MARK bypass for proxy upstream traffic") + log.Infof("iptables redirect configured (OUTPUT 53 -> 15353) with SO_MARK bypass for proxy upstream traffic") setupNft(ctx, nftMgr, initialRules, proxy, allowIPs) @@ -61,13 +65,19 @@ func main() { if err = startPolicyServer(ctx, proxy, nftMgr, mode, httpAddr, os.Getenv(constants.EnvEgressToken), allowIPs); err != nil { log.Fatalf("failed to start policy server: %v", err) } - log.Printf("policy server listening on %s (POST /policy)", httpAddr) + log.Infof("policy server listening on %s (POST /policy)", httpAddr) <-ctx.Done() - log.Println("received shutdown signal; exiting") + log.Infof("received shutdown signal; exiting") _ = os.Stderr.Sync() } +func withLogger(ctx context.Context) context.Context { + level := envOrDefault(constants.EnvEgressLogLevel, "info") + logger := slogger.MustNew(slogger.Config{Level: level}).Named("opensandbox.egress") + return log.WithLogger(ctx, logger) +} + func envOrDefault(key, defaultVal string) string { if v := strings.TrimSpace(os.Getenv(key)); v != "" { return v @@ -92,7 +102,7 @@ func parseMode() string { case constants.PolicyDnsNft: return constants.PolicyDnsNft default: - log.Printf("invalid %s=%s, falling back to dns", constants.EnvEgressMode, mode) + log.Warnf("invalid %s=%s, falling back to dns", constants.EnvEgressMode, mode) return constants.PolicyDnsOnly } } diff --git a/components/egress/nameserver.go b/components/egress/nameserver.go index a3d84c47..3b7be058 100644 --- a/components/egress/nameserver.go +++ b/components/egress/nameserver.go @@ -15,13 +15,13 @@ package main import ( - "log" "net/netip" "os" "strconv" "github.com/alibaba/opensandbox/egress/pkg/constants" "github.com/alibaba/opensandbox/egress/pkg/dnsproxy" + "github.com/alibaba/opensandbox/egress/pkg/log" ) // AllowIPsForNft returns the list of IPs to merge into the nft allow set for DNS in dns+nft mode: @@ -49,9 +49,9 @@ func AllowIPsForNft(resolvPath string) []netip.Addr { out = append(out, validated...) if len(out) > 1 { - log.Printf("[dns] whitelisting proxy listen + %d nameserver(s) for nft: %v", len(validated), formatIPs(out)) + log.Infof("[dns] whitelisting proxy listen + %d nameserver(s) for nft: %v", len(validated), formatIPs(out)) } else { - log.Printf("[dns] whitelisting proxy listen (127.0.0.1); no valid nameserver IPs from %s", resolvPath) + log.Infof("[dns] whitelisting proxy listen (127.0.0.1); no valid nameserver IPs from %s", resolvPath) } return out } diff --git a/components/egress/nft.go b/components/egress/nft.go index 74ce65a1..0e0a3f2f 100644 --- a/components/egress/nft.go +++ b/components/egress/nft.go @@ -16,13 +16,13 @@ package main import ( "context" - "log" "net/netip" "os" "strings" "github.com/alibaba/opensandbox/egress/pkg/constants" "github.com/alibaba/opensandbox/egress/pkg/dnsproxy" + "github.com/alibaba/opensandbox/egress/pkg/log" "github.com/alibaba/opensandbox/egress/pkg/nftables" "github.com/alibaba/opensandbox/egress/pkg/policy" ) @@ -45,10 +45,10 @@ func setupNft(ctx context.Context, nftMgr nftApplier, initialPolicy *policy.Netw if err := nftMgr.ApplyStatic(ctx, policyWithNS); err != nil { log.Fatalf("nftables static apply failed: %v", err) } - log.Printf("nftables static policy applied (table inet opensandbox)") + log.Infof("nftables static policy applied (table inet opensandbox)") proxy.SetOnResolved(func(domain string, ips []nftables.ResolvedIP) { if err := nftMgr.AddResolvedIPs(ctx, ips); err != nil { - log.Printf("[dns] add resolved IPs to nft failed: %v", err) + log.Warnf("[dns] add resolved IPs to nft failed: %v", err) } }) } @@ -81,7 +81,7 @@ func parseNftOptions() nftables.Options { } continue } - log.Printf("ignoring invalid DoH blocklist entry: %s", target) + log.Warnf("ignoring invalid DoH blocklist entry: %s", target) } } return opts diff --git a/components/egress/pkg/constants/configuration.go b/components/egress/pkg/constants/configuration.go index d4d44fe2..e1ae70ef 100644 --- a/components/egress/pkg/constants/configuration.go +++ b/components/egress/pkg/constants/configuration.go @@ -21,6 +21,7 @@ const ( EnvEgressHTTPAddr = "OPENSANDBOX_EGRESS_HTTP_ADDR" EnvEgressToken = "OPENSANDBOX_EGRESS_TOKEN" EnvEgressRules = "OPENSANDBOX_EGRESS_RULES" + EnvEgressLogLevel = "OPENSANDBOX_EGRESS_LOG_LEVEL" EnvMaxNameservers = "OPENSANDBOX_EGRESS_MAX_NS" ) diff --git a/components/egress/pkg/dnsproxy/proxy.go b/components/egress/pkg/dnsproxy/proxy.go index bd761612..4965ee1b 100644 --- a/components/egress/pkg/dnsproxy/proxy.go +++ b/components/egress/pkg/dnsproxy/proxy.go @@ -17,7 +17,6 @@ package dnsproxy import ( "context" "fmt" - "log" "net" "net/netip" "os" @@ -26,6 +25,7 @@ import ( "github.com/miekg/dns" + "github.com/alibaba/opensandbox/egress/pkg/log" "github.com/alibaba/opensandbox/egress/pkg/nftables" "github.com/alibaba/opensandbox/egress/pkg/policy" ) @@ -117,7 +117,7 @@ func (p *Proxy) serveDNS(w dns.ResponseWriter, r *dns.Msg) { resp, err := p.forward(r) if err != nil { - log.Printf("[dns] forward error for %s: %v", domain, err) + log.Warnf("[dns] forward error for %s: %v", domain, err) fail := new(dns.Msg) fail.SetRcode(r, dns.RcodeServerFailure) _ = w.WriteMsg(fail) @@ -220,7 +220,7 @@ func discoverUpstream() (string, error) { cfg, err := dns.ClientConfigFromFile("/etc/resolv.conf") if err != nil || len(cfg.Servers) == 0 { if err != nil { - log.Printf("[dns] fallback upstream resolver due to error: %v", err) + log.Warnf("[dns] fallback upstream resolver due to error: %v", err) } return fallbackUpstream, nil } diff --git a/components/egress/pkg/log/logger.go b/components/egress/pkg/log/logger.go new file mode 100644 index 00000000..c2573d9d --- /dev/null +++ b/components/egress/pkg/log/logger.go @@ -0,0 +1,55 @@ +// Copyright 2026 Alibaba Group Holding Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +import ( + "context" + "os" + + slogger "github.com/alibaba/opensandbox/internal/logger" +) + +// Logger is the shared logger instance for egress. +var Logger slogger.Logger = slogger.MustNew(slogger.Config{Level: "info"}).Named("opensandbox.egress") + +// WithLogger replaces the global logger used by egress components. +func WithLogger(ctx context.Context, logger slogger.Logger) context.Context { + if logger != nil { + Logger = logger + } + return ctx +} + +func Debugf(template string, args ...any) { + Logger.Debugf(template, args...) +} + +func Infof(template string, args ...any) { + Logger.Infof(template, args...) +} + +func Warnf(template string, args ...any) { + Logger.Warnf(template, args...) +} + +func Errorf(template string, args ...any) { + Logger.Errorf(template, args...) +} + +func Fatalf(template string, args ...any) { + Logger.Errorf(template, args...) + _ = Logger.Sync() + os.Exit(1) +} diff --git a/components/egress/policy_server.go b/components/egress/policy_server.go index 25117dce..3b923c20 100644 --- a/components/egress/policy_server.go +++ b/components/egress/policy_server.go @@ -21,13 +21,13 @@ import ( "errors" "fmt" "io" - "log" "net/http" "net/netip" "strings" "time" "github.com/alibaba/opensandbox/egress/pkg/constants" + "github.com/alibaba/opensandbox/egress/pkg/log" "github.com/alibaba/opensandbox/egress/pkg/nftables" "github.com/alibaba/opensandbox/egress/pkg/policy" ) @@ -72,7 +72,7 @@ func startPolicyServer(ctx context.Context, proxy policyUpdater, nft nftApplier, shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Printf("policy server shutdown error: %v", err) + log.Warnf("policy server shutdown error: %v", err) } }() @@ -90,7 +90,7 @@ func startPolicyServer(ctx context.Context, proxy policyUpdater, nft nftApplier, // assume healthy start; keep logging future errors go func() { if err := <-errCh; err != nil { - log.Printf("policy server error: %v", err) + log.Errorf("policy server error: %v", err) } }() return nil diff --git a/components/egress/tests/bench-dns-nft.sh b/components/egress/tests/bench-dns-nft.sh index be046344..6a128689 100755 --- a/components/egress/tests/bench-dns-nft.sh +++ b/components/egress/tests/bench-dns-nft.sh @@ -35,6 +35,11 @@ BASELINE_IMG="${BASELINE_IMG:-curlimages/curl:latest}" CONTAINER_NAME="egress-bench-e2e" POLICY_PORT=18080 ROUNDS=10 +# Optional: where to write egress logs on host. Override via LOG_HOST_DIR / LOG_FILE. +LOG_HOST_DIR="${LOG_HOST_DIR:-/tmp/egress-logs}" +LOG_FILE="${LOG_FILE:-egress.log}" +LOG_CONTAINER_DIR="/var/log/opensandbox" +LOG_CONTAINER_FILE="${LOG_CONTAINER_DIR}/${LOG_FILE}" # Load benchmark domains from hostname.txt (one domain per line). if [[ ! -f "${HOSTNAME_FILE}" ]] || [[ ! -s "${HOSTNAME_FILE}" ]]; then @@ -209,11 +214,14 @@ run_phase() { local mode="$1" info "Phase: ${mode}" cleanup + mkdir -p "${LOG_HOST_DIR}" docker run -d --name "${CONTAINER_NAME}" \ --cap-add=NET_ADMIN \ --sysctl net.ipv6.conf.all.disable_ipv6=1 \ --sysctl net.ipv6.conf.default.disable_ipv6=1 \ -e OPENSANDBOX_EGRESS_MODE="${mode}" \ + -e OPENSANDBOX_LOG_OUTPUT="${LOG_CONTAINER_FILE}" \ + -v "${LOG_HOST_DIR}:${LOG_CONTAINER_DIR}" \ -p "${POLICY_PORT}:18080" \ "${IMG}" diff --git a/components/internal/logger/zap.go b/components/internal/logger/zap.go index d5fc832d..e01c36c9 100644 --- a/components/internal/logger/zap.go +++ b/components/internal/logger/zap.go @@ -15,12 +15,15 @@ package logger import ( + "os" "strings" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) +const envLogOutput = "OPENSANDBOX_LOG_OUTPUT" + // Config is the minimal configuration to align execd/ingress defaults. // - JSON encoding, ISO8601 time // - Caller/stacktrace disabled @@ -34,6 +37,8 @@ type Config struct { // New creates a zap-backed Logger with the provided config. func New(cfg Config) (Logger, error) { + cfg = applyEnvOutputs(cfg) + zapCfg := zap.NewProductionConfig() zapCfg.Level = zap.NewAtomicLevelAt(parseLevel(cfg.Level)) zapCfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder @@ -42,16 +47,8 @@ func New(cfg Config) (Logger, error) { zapCfg.DisableStacktrace = true zapCfg.EncoderConfig.StacktraceKey = "" - if len(cfg.OutputPaths) == 0 { - zapCfg.OutputPaths = []string{"stdout"} - } else { - zapCfg.OutputPaths = cfg.OutputPaths - } - if len(cfg.ErrorOutputPaths) == 0 { - zapCfg.ErrorOutputPaths = zapCfg.OutputPaths - } else { - zapCfg.ErrorOutputPaths = cfg.ErrorOutputPaths - } + zapCfg.OutputPaths = cfg.OutputPaths + zapCfg.ErrorOutputPaths = cfg.ErrorOutputPaths base, err := zapCfg.Build() if err != nil { @@ -134,3 +131,30 @@ func parseLevel(level string) zapcore.Level { return zapcore.InfoLevel } } + +func applyEnvOutputs(cfg Config) Config { + envVal := strings.TrimSpace(os.Getenv(envLogOutput)) + if len(cfg.OutputPaths) == 0 { + if envVal != "" { + cfg.OutputPaths = splitAndTrim(envVal) + } else { + cfg.OutputPaths = []string{"stdout"} + } + } + if len(cfg.ErrorOutputPaths) == 0 { + // Default error output matches output paths. + cfg.ErrorOutputPaths = cfg.OutputPaths + } + return cfg +} + +func splitAndTrim(s string) []string { + parts := strings.Split(s, ",") + out := make([]string, 0, len(parts)) + for _, p := range parts { + if v := strings.TrimSpace(p); v != "" { + out = append(out, v) + } + } + return out +} From e2a7ad71b8995270d54d131c9b014568ccf1f49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E7=84=B6?= Date: Thu, 26 Feb 2026 10:10:32 +0800 Subject: [PATCH 2/4] fix(egress): fix docker build contexts --- .github/workflows/egress-test.yaml.yml | 2 +- components/egress/Dockerfile | 12 +++++++++--- components/egress/build.sh | 6 +++--- components/egress/tests/bench-dns-nft.sh | 4 +++- components/egress/tests/smoke-dns.sh | 6 +++++- components/egress/tests/smoke-dynamic-ip.sh | 6 +++++- components/egress/tests/smoke-nft.sh | 6 +++++- 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/.github/workflows/egress-test.yaml.yml b/.github/workflows/egress-test.yaml.yml index 59fd1bab..00212c8f 100644 --- a/.github/workflows/egress-test.yaml.yml +++ b/.github/workflows/egress-test.yaml.yml @@ -74,4 +74,4 @@ jobs: - name: Eval bench logs if: always() run: | - cat /tmp/egress-logs + cat /tmp/egress-logs/egress.log diff --git a/components/egress/Dockerfile b/components/egress/Dockerfile index c4084b63..cc2c33c4 100644 --- a/components/egress/Dockerfile +++ b/components/egress/Dockerfile @@ -14,15 +14,21 @@ FROM golang:1.24-bookworm AS builder -WORKDIR /app +WORKDIR /workspace -COPY go.mod go.sum ./ +# Copy only go mod/sum first for better caching +COPY components/egress/go.mod components/egress/go.sum ./components/egress/ +# Bring internal module so replace ../internal works during download/build +COPY components/internal ./components/internal + +WORKDIR /workspace/components/egress # Static-ish build (no cgo) to simplify runtime deps ENV CGO_ENABLED=0 RUN go mod download -COPY . . +# Copy the rest of the egress sources +COPY components/egress ./ RUN go build -o /out/egress . FROM debian:bookworm-slim diff --git a/components/egress/build.sh b/components/egress/build.sh index b1a40632..23434fd4 100755 --- a/components/egress/build.sh +++ b/components/egress/build.sh @@ -16,18 +16,18 @@ set -ex TAG=${TAG:-latest} +REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || realpath "$(dirname "$0")/../..") +cd "${REPO_ROOT}" docker buildx rm egress-builder || true - docker buildx create --use --name egress-builder - docker buildx inspect --bootstrap - docker buildx ls docker buildx build \ -t opensandbox/egress:${TAG} \ -t sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/egress:${TAG} \ + -f components/egress/Dockerfile \ --platform linux/amd64,linux/arm64 \ --push \ . diff --git a/components/egress/tests/bench-dns-nft.sh b/components/egress/tests/bench-dns-nft.sh index 6a128689..323eb64f 100755 --- a/components/egress/tests/bench-dns-nft.sh +++ b/components/egress/tests/bench-dns-nft.sh @@ -29,6 +29,8 @@ info() { echo "[$(date +%H:%M:%S)] $*"; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" HOSTNAME_FILE="${SCRIPT_DIR}/hostname.txt" +# tests/ is two levels under repo root: components/egress/tests -> climb 3 levels. +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" IMG="opensandbox/egress:local" BASELINE_IMG="${BASELINE_IMG:-curlimages/curl:latest}" @@ -302,7 +304,7 @@ report() { } info "Building image ${IMG}" -docker build -t "${IMG}" . > /dev/null 2>&1 +docker build -t "${IMG}" -f "${REPO_ROOT}/components/egress/Dockerfile" "${REPO_ROOT}" > /dev/null 2>&1 run_phase_baseline run_phase "dns+nft" diff --git a/components/egress/tests/smoke-dns.sh b/components/egress/tests/smoke-dns.sh index 082e337f..2014955e 100755 --- a/components/egress/tests/smoke-dns.sh +++ b/components/egress/tests/smoke-dns.sh @@ -19,6 +19,10 @@ set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# tests/ is two levels under repo root: components/egress/tests -> climb 3 levels. +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" + IMG="opensandbox/egress:local" containerName="egress-smoke-dns" POLICY_PORT=18080 @@ -31,7 +35,7 @@ cleanup() { trap cleanup EXIT info "Building image ${IMG}" -docker build -t "${IMG}" . +docker build -t "${IMG}" -f "${REPO_ROOT}/components/egress/Dockerfile" "${REPO_ROOT}" info "Starting containerName" docker run -d --name "${containerName}" \ diff --git a/components/egress/tests/smoke-dynamic-ip.sh b/components/egress/tests/smoke-dynamic-ip.sh index 22947779..4deac05a 100755 --- a/components/egress/tests/smoke-dynamic-ip.sh +++ b/components/egress/tests/smoke-dynamic-ip.sh @@ -20,6 +20,10 @@ set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# tests/ is two levels under repo root: components/egress/tests -> climb 3 levels. +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" + IMG="opensandbox/egress:local" containerName="egress-smoke-dynamic-ip" POLICY_PORT=18080 @@ -32,7 +36,7 @@ cleanup() { trap cleanup EXIT info "Building image ${IMG}" -docker build -t "${IMG}" . +docker build -t "${IMG}" -f "${REPO_ROOT}/components/egress/Dockerfile" "${REPO_ROOT}" info "Starting sidecar (dns+nft)" docker run -d --name "${containerName}" \ diff --git a/components/egress/tests/smoke-nft.sh b/components/egress/tests/smoke-nft.sh index e2052963..49c7db4c 100755 --- a/components/egress/tests/smoke-nft.sh +++ b/components/egress/tests/smoke-nft.sh @@ -19,6 +19,10 @@ set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# tests/ is two levels under repo root: components/egress/tests -> climb 3 levels. +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" + IMG="opensandbox/egress:local" containerName="egress-smoke-nft" POLICY_PORT=18080 @@ -31,7 +35,7 @@ cleanup() { trap cleanup EXIT info "Building image ${IMG}" -docker build -t "${IMG}" . +docker build -t "${IMG}" -f "${REPO_ROOT}/components/egress/Dockerfile" "${REPO_ROOT}" info "Starting containerName" docker run -d --name "${containerName}" \ From 4bce406a1faa98fd5f4ac585360cd26489c3da25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E7=84=B6?= Date: Thu, 26 Feb 2026 10:28:00 +0800 Subject: [PATCH 3/4] chore(egress): add audit log for redirect action --- .github/workflows/egress-test.yaml.yml | 9 ++++++--- components/egress/main.go | 1 + components/egress/nft.go | 6 ++++-- components/egress/pkg/iptables/redirect.go | 3 +++ components/egress/pkg/nftables/manager.go | 6 ++++++ components/egress/policy_server.go | 9 ++++++++- 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/.github/workflows/egress-test.yaml.yml b/.github/workflows/egress-test.yaml.yml index 00212c8f..d666f2e0 100644 --- a/.github/workflows/egress-test.yaml.yml +++ b/.github/workflows/egress-test.yaml.yml @@ -71,7 +71,10 @@ jobs: env: BENCH_SAMPLE_SIZE: "20" - - name: Eval bench logs + - name: Upload egress logs if: always() - run: | - cat /tmp/egress-logs/egress.log + uses: actions/upload-artifact@v4 + with: + name: egress-log-for-bench + path: /tmp/egress-logs/ + retention-days: 5 diff --git a/components/egress/main.go b/components/egress/main.go index c984e797..ff46dc1c 100644 --- a/components/egress/main.go +++ b/components/egress/main.go @@ -43,6 +43,7 @@ func main() { allowIPs := AllowIPsForNft("/etc/resolv.conf") mode := parseMode() + log.Infof("enforcement mode: %s", mode) nftMgr := createNftManager(mode) proxy, err := dnsproxy.New(initialRules, "") if err != nil { diff --git a/components/egress/nft.go b/components/egress/nft.go index 0e0a3f2f..e8541ac6 100644 --- a/components/egress/nft.go +++ b/components/egress/nft.go @@ -39,16 +39,18 @@ func createNftManager(mode string) nftApplier { // nameserverIPs are merged into the allow set at startup so system DNS works (client + proxy upstream, e.g. private DNS). func setupNft(ctx context.Context, nftMgr nftApplier, initialPolicy *policy.NetworkPolicy, proxy *dnsproxy.Proxy, nameserverIPs []netip.Addr) { if nftMgr == nil { + log.Warnf("nftables disabled (dns-only mode)") return } + log.Infof("applying nftables static policy (dns+nft mode) with %d nameserver IP(s) merged into allow set", len(nameserverIPs)) policyWithNS := initialPolicy.WithExtraAllowIPs(nameserverIPs) if err := nftMgr.ApplyStatic(ctx, policyWithNS); err != nil { log.Fatalf("nftables static apply failed: %v", err) } - log.Infof("nftables static policy applied (table inet opensandbox)") + log.Infof("nftables static policy applied (table inet opensandbox); DNS-resolved IPs will be added to dynamic allow sets") proxy.SetOnResolved(func(domain string, ips []nftables.ResolvedIP) { if err := nftMgr.AddResolvedIPs(ctx, ips); err != nil { - log.Warnf("[dns] add resolved IPs to nft failed: %v", err) + log.Warnf("[dns] add resolved IPs to nft failed for domain %q: %v", domain, err) } }) } diff --git a/components/egress/pkg/iptables/redirect.go b/components/egress/pkg/iptables/redirect.go index 4e1023b0..d626df55 100644 --- a/components/egress/pkg/iptables/redirect.go +++ b/components/egress/pkg/iptables/redirect.go @@ -20,12 +20,14 @@ import ( "strconv" "github.com/alibaba/opensandbox/egress/pkg/constants" + "github.com/alibaba/opensandbox/egress/pkg/log" ) // SetupRedirect installs OUTPUT nat redirect for DNS (udp/tcp 53 -> port). // Packets carrying mark bypassMark will RETURN (used by the proxy's own upstream // queries to avoid redirect loops). Requires CAP_NET_ADMIN inside the namespace. func SetupRedirect(port int) error { + log.Infof("installing iptables DNS redirect: OUTPUT port 53 -> %d (mark %s bypass)", port, constants.MarkHex) targetPort := strconv.Itoa(port) rules := [][]string{ @@ -47,5 +49,6 @@ func SetupRedirect(port int) error { return fmt.Errorf("iptables command failed: %v (output: %s)", err, output) } } + log.Infof("iptables DNS redirect installed successfully") return nil } diff --git a/components/egress/pkg/nftables/manager.go b/components/egress/pkg/nftables/manager.go index 882efa3f..eb0b8495 100644 --- a/components/egress/pkg/nftables/manager.go +++ b/components/egress/pkg/nftables/manager.go @@ -22,6 +22,7 @@ import ( "sync" "github.com/alibaba/opensandbox/egress/pkg/constants" + "github.com/alibaba/opensandbox/egress/pkg/log" "github.com/alibaba/opensandbox/egress/pkg/policy" ) @@ -84,6 +85,9 @@ func (m *Manager) ApplyStatic(ctx context.Context, p *policy.NetworkPolicy) erro if p == nil { p = policy.DefaultDenyPolicy() } + allowV4, allowV6, denyV4, denyV6 := p.StaticIPSets() + log.Infof("nftables: applying static policy: default=%s, allow_v4=%d, allow_v6=%d, deny_v4=%d, deny_v6=%d", + p.DefaultAction, len(allowV4), len(allowV6), len(denyV4), len(denyV6)) m.mu.Lock() defer m.mu.Unlock() script := buildRuleset(p, m.opts) @@ -99,6 +103,7 @@ func (m *Manager) ApplyStatic(ctx context.Context, p *policy.NetworkPolicy) erro } return err } + log.Infof("nftables: static policy applied successfully") return nil } @@ -115,6 +120,7 @@ func (m *Manager) AddResolvedIPs(ctx context.Context, ips []ResolvedIP) error { if script == "" { return nil } + log.Infof("nftables: adding %d resolved IP(s) to dynamic allow sets with script statement %s", len(ips), script) _, err := m.run(ctx, script) return err } diff --git a/components/egress/policy_server.go b/components/egress/policy_server.go index 3b923c20..bda0411b 100644 --- a/components/egress/policy_server.go +++ b/components/egress/policy_server.go @@ -142,15 +142,18 @@ func (s *policyServer) handlePost(w http.ResponseWriter, r *http.Request) { } raw := strings.TrimSpace(string(body)) if raw == "" { + log.Infof("policy API: reset to default deny-all") def := policy.DefaultDenyPolicy() if s.nft != nil { defWithNS := def.WithExtraAllowIPs(s.nameserverIPs) if err := s.nft.ApplyStatic(r.Context(), defWithNS); err != nil { + log.Errorf("policy API: nftables apply failed on reset: %v", err) http.Error(w, fmt.Sprintf("failed to apply nftables: %v", err), http.StatusInternalServerError) return } } s.proxy.UpdatePolicy(def) + log.Infof("policy API: proxy and nftables updated to deny_all") writeJSON(w, http.StatusOK, map[string]any{ "status": "ok", "mode": "deny_all", @@ -164,17 +167,21 @@ func (s *policyServer) handlePost(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("invalid policy: %v", err), http.StatusBadRequest) return } + mode := modeFromPolicy(pol) + log.Infof("policy API: updating policy to mode=%s, enforcement=%s", mode, s.enforcementMode) if s.nft != nil { polWithNS := pol.WithExtraAllowIPs(s.nameserverIPs) if err := s.nft.ApplyStatic(r.Context(), polWithNS); err != nil { + log.Errorf("policy API: nftables apply failed: %v", err) http.Error(w, fmt.Sprintf("failed to apply nftables policy: %v", err), http.StatusInternalServerError) return } } s.proxy.UpdatePolicy(pol) + log.Infof("policy API: proxy and nftables updated successfully") writeJSON(w, http.StatusOK, map[string]any{ "status": "ok", - "mode": modeFromPolicy(pol), + "mode": mode, "enforcementMode": s.enforcementMode, }) } From 4a992d44fa6761c70dd26257662090c01cdb22e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E7=84=B6?= Date: Thu, 26 Feb 2026 11:08:49 +0800 Subject: [PATCH 4/4] chore(actions): trigger components test when internal package changed --- .github/workflows/egress-test.yaml.yml | 1 + .github/workflows/execd-test.yml | 1 + .github/workflows/ingress-test.yaml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/egress-test.yaml.yml b/.github/workflows/egress-test.yaml.yml index d666f2e0..9d7b0e44 100644 --- a/.github/workflows/egress-test.yaml.yml +++ b/.github/workflows/egress-test.yaml.yml @@ -5,6 +5,7 @@ on: branches: [ main ] paths: - 'components/egress/**' + - 'components/internal/**' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} diff --git a/.github/workflows/execd-test.yml b/.github/workflows/execd-test.yml index c398b3ba..f48194f2 100644 --- a/.github/workflows/execd-test.yml +++ b/.github/workflows/execd-test.yml @@ -5,6 +5,7 @@ on: branches: [ main ] paths: - 'components/execd/**' + - 'components/internal/**' permissions: contents: read diff --git a/.github/workflows/ingress-test.yaml b/.github/workflows/ingress-test.yaml index 70a68694..39bd2232 100644 --- a/.github/workflows/ingress-test.yaml +++ b/.github/workflows/ingress-test.yaml @@ -5,6 +5,7 @@ on: branches: [ main ] paths: - 'components/ingress/**' + - 'components/internal/**' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}