From 398fab48284c396610f4277b60f322596101852d Mon Sep 17 00:00:00 2001 From: igmainc Date: Mon, 1 Jun 2026 19:37:15 +0800 Subject: [PATCH] fix(containers): bypass Docker bridge traffic before sidecar TPROXY The local proxy-everything sidecar installs TPROXY/DIVERT rules in mangle PREROUTING. Without an earlier bridge-CIDR RETURN rule, Docker bridge traffic can be intercepted before the container startup and egress readiness path completes. Install the bridge bypass idempotently after sidecar start and during recovery, while preserving the existing main-container helper exec behavior. Fixes #6793 --- src/workerd/server/container-client.c++ | 22 ++++++++++++++++++++-- src/workerd/server/container-client.h | 3 +++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/workerd/server/container-client.c++ b/src/workerd/server/container-client.c++ index 69f2f0c6201..b708eccb323 100644 --- a/src/workerd/server/container-client.c++ +++ b/src/workerd/server/container-client.c++ @@ -1612,6 +1612,15 @@ kj::Promise> ContainerClient: }; } +kj::Promise ContainerClient::ensureSidecarBridgeBypass(kj::String networkCidr) { + auto script = kj::str("cidr=$1\n" + "iptables -t mangle -C PREROUTING -s \"$cidr\" -d \"$cidr\" -j RETURN 2>/dev/null || " + "iptables -t mangle -I PREROUTING 1 -s \"$cidr\" -d \"$cidr\" -j RETURN\n"); + auto cmd = + kj::arr(kj::str("sh"), kj::str("-c"), kj::mv(script), kj::str("sh"), kj::mv(networkCidr)); + co_await runSimpleExecInContainer(sidecarContainerName, cmd.asPtr()); +} + kj::Promise ContainerClient::updateSidecarEgressPort( uint16_t ingressHostPort, uint16_t egressPort) { capnp::JsonCodec codec; @@ -1844,7 +1853,8 @@ kj::Promise ContainerClient::inspectExec( }; } -kj::Promise ContainerClient::runSimpleExec(kj::ArrayPtr cmd) { +kj::Promise ContainerClient::runSimpleExecInContainer( + kj::StringPtr targetContainerName, kj::ArrayPtr cmd) { capnp::JsonCodec codec; codec.handleByAnnotation(); @@ -1862,7 +1872,7 @@ kj::Promise ContainerClient::runSimpleExec(kj::ArrayPtr auto createResponse = co_await dockerApiRequest(network, kj::str(dockerPath), kj::HttpMethod::POST, - kj::str("/containers/", containerName, "/exec"), codec.encode(createRequest)); + kj::str("/containers/", targetContainerName, "/exec"), codec.encode(createRequest)); JSG_REQUIRE(createResponse.statusCode == 201, Error, "Creating helper Docker exec failed with [", createResponse.statusCode, "] ", createResponse.body); @@ -1894,6 +1904,10 @@ kj::Promise ContainerClient::runSimpleExec(kj::ArrayPtr } } +kj::Promise ContainerClient::runSimpleExec(kj::ArrayPtr cmd) { + co_await runSimpleExecInContainer(containerName, cmd); +} + kj::Promise ContainerClient::startContainer() { auto endpoint = kj::str("/containers/", containerName, "/start"); // We have to send an empty body since docker API will throw an error if we don't. @@ -2202,6 +2216,8 @@ kj::Promise ContainerClient::status(StatusContext context) { containerSidecarStarted.store(true, std::memory_order_release); this->sidecarIngressHostPort = sidecar.ingressHostPort; co_await ensureEgressListenerStarted(); + auto ipamConfig = co_await getDockerBridgeIPAMConfig(); + co_await ensureSidecarBridgeBypass(kj::mv(ipamConfig.subnet)); co_await updateSidecarEgressPort(sidecar.ingressHostPort, egressListenerPort); co_await readCACert(); } @@ -2673,8 +2689,10 @@ kj::Promise ContainerClient::ensureSidecarStarted() { KJ_ON_SCOPE_FAILURE(containerSidecarStarted.store(false, std::memory_order_release)); auto ipamConfig = co_await getDockerBridgeIPAMConfig(); + auto networkCidr = kj::str(ipamConfig.subnet); co_await createSidecarContainer(egressListenerPort, kj::mv(ipamConfig.subnet)); co_await startSidecarContainer(); + co_await ensureSidecarBridgeBypass(kj::mv(networkCidr)); auto sidecar = KJ_REQUIRE_NONNULL(co_await inspectSidecar(), "started sidecar not running"); this->sidecarIngressHostPort = sidecar.ingressHostPort; diff --git a/src/workerd/server/container-client.h b/src/workerd/server/container-client.h index 1cc6c244bd2..d629fe0c5af 100644 --- a/src/workerd/server/container-client.h +++ b/src/workerd/server/container-client.h @@ -163,6 +163,7 @@ class ContainerClient final: public rpc::Container::Server, public kj::Refcounte kj::Promise> inspectContainer(); + kj::Promise ensureSidecarBridgeBypass(kj::String networkCidr); kj::Promise updateSidecarEgressPort(uint16_t ingressHostPort, uint16_t egressPort); kj::Promise updateSidecarEgressConfig(uint16_t ingressHostPort, uint16_t egressPort); kj::Promise createContainer(kj::StringPtr effectiveImage, @@ -176,6 +177,8 @@ class ContainerClient final: public rpc::Container::Server, public kj::Refcounte bool attachStderr); kj::Promise> startExec(kj::String execId); kj::Promise inspectExec(kj::StringPtr execId); + kj::Promise runSimpleExecInContainer(kj::StringPtr targetContainerName, + kj::ArrayPtr cmd); kj::Promise runSimpleExec(kj::ArrayPtr cmd); kj::Promise startContainer(); kj::Promise stopContainer();