Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 32 additions & 42 deletions internal/ateomnet/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import (
const (
HostVethName = "ateom0"
ActorVethName = "eth0"
ActorVethTempName = "ateom1"
HostVethCIDR = "169.254.17.1/30"
ActorVethCIDR = "169.254.17.2/30"
ActorVethGateway = "169.254.17.1"
Expand Down Expand Up @@ -84,13 +83,12 @@ func MustParseMAC(s string) net.HardwareAddr {
// ConfigureActorVeth configures the actor veth inside the interior netns.
// It assumes it is already running inside the target network namespace.
func ConfigureActorVeth(ctx context.Context) error {
// Run inside the gVisor interior netns after setupActorNetwork moves the
// veth peer there. gVisor reads link names, addresses, and routes from this
// namespace when the workload starts, so the peer is deliberately renamed to
// eth0 and configured like a normal container interface:
// Run inside the gVisor interior netns. SetupActorNetwork has already created
// the veth peer here, under its final name, so this only has to address it.
// gVisor reads link names, addresses, and routes from this namespace when the
// workload starts, so eth0 is configured like a normal container interface:
//
// * lo is brought up for localhost behavior.
// * the temporary veth peer is renamed to eth0.
// * eth0 receives the actor-side /30 address.
// * the default route points to the worker-side veth gateway.
loLink, err := netlink.LinkByName("lo")
Expand All @@ -101,17 +99,10 @@ func ConfigureActorVeth(ctx context.Context) error {
return fmt.Errorf("while bringing up lo in interior netns: %w", err)
}

actorLink, err := netlink.LinkByName(ActorVethTempName)
actorLink, err := netlink.LinkByName(ActorVethName)
if err != nil {
return fmt.Errorf("while acquiring actor veth in interior netns: %w", err)
}
if err := netlink.LinkSetName(actorLink, ActorVethName); err != nil {
return fmt.Errorf("while renaming actor veth to %q: %w", ActorVethName, err)
}
actorLink, err = netlink.LinkByName(ActorVethName)
if err != nil {
return fmt.Errorf("while reacquiring actor veth in interior netns: %w", err)
}

if err := netlink.AddrReplace(actorLink, ActorVethAddr); err != nil {
return fmt.Errorf("while assigning actor veth address: %w", err)
Expand All @@ -134,10 +125,10 @@ func ConfigureActorVeth(ctx context.Context) error {
// Intentionally idempotent.
func CleanupActorNetwork(ctx context.Context, interiorNetNS netns.NsHandle) error {
// Remove all per-activation network state owned by ateom. Deleting the
// worker-side veth also deletes its peer when the pair is still connected,
// but failed setup can leave the peer already moved into the actor netns.
// For that reason cleanup also enters the interior netns and deletes either
// the final actor interface name or the temporary peer name if present.
// worker-side veth also deletes its peer, but the pair is born with its peer
// already in the actor netns, so a setup that failed before the worker side
// was named can leave that peer behind on its own. For that reason cleanup
// also enters the interior netns and deletes the actor interface if present.
//
// This function is intentionally idempotent so it can run before setup, after
// checkpoint, and from setup failure cleanup without requiring the caller to
Expand All @@ -153,23 +144,21 @@ func CleanupActorNetwork(ctx context.Context, interiorNetNS netns.NsHandle) erro
cleanupErr = errors.Join(cleanupErr, fmt.Errorf("while deleting host veth: %w", err))
slog.WarnContext(ctx, "Failed to delete host veth; continuing actor netns cleanup", "err", err)
}
} else if _, ok := err.(netlink.LinkNotFoundError); !ok {
} else if _, notFound := errors.AsType[netlink.LinkNotFoundError](err); !notFound {
cleanupErr = errors.Join(cleanupErr, fmt.Errorf("while looking up host veth: %w", err))
slog.WarnContext(ctx, "Failed to look up host veth; continuing actor netns cleanup", "err", err)
}

if err := NetNSDo(ctx, interiorNetNS, func(_ context.Context) error {
for _, name := range []string{ActorVethName, ActorVethTempName} {
link, err := netlink.LinkByName(name)
if err == nil {
if err := netlink.LinkDel(link); err != nil {
return fmt.Errorf("while deleting interior veth %q: %w", name, err)
}
continue
}
if _, ok := err.(netlink.LinkNotFoundError); !ok {
return fmt.Errorf("while looking up interior veth %q: %w", name, err)
link, err := netlink.LinkByName(ActorVethName)
if err == nil {
if err := netlink.LinkDel(link); err != nil {
return fmt.Errorf("while deleting interior veth %q: %w", ActorVethName, err)
}
return nil
}
if _, notFound := errors.AsType[netlink.LinkNotFoundError](err); !notFound {
return fmt.Errorf("while looking up interior veth %q: %w", ActorVethName, err)
}
return nil
}); err != nil {
Expand Down Expand Up @@ -495,10 +484,10 @@ type NetworkConfig struct {
// pod netns and the interior netns.
func SetupActorNetwork(ctx context.Context, cfg NetworkConfig) (retErr error) {
// Build a fresh point-to-point network between the worker pod netns and the
// gVisor interior netns. The worker side keeps the pod's real eth0, creates
// ateom0 as the gateway, and moves only the veth peer into the actor netns.
// The actor side renames that peer to eth0 and installs a default route via
// the worker-side veth address. This replaces the old behavior of moving the
// gVisor interior netns. The worker side keeps the pod's real eth0 and creates
// ateom0 as the gateway; the pair's peer is born inside the actor netns as
// eth0, where it gets the actor-side address and a default route via the
// worker-side veth address. This replaces the old behavior of moving the
// Kubernetes-provided eth0 out of the worker pod.
//
// The nftables rules installed here redirect actor TCP egress to atunnel
Expand Down Expand Up @@ -538,11 +527,20 @@ func SetupActorNetwork(ctx context.Context, cfg NetworkConfig) (retErr error) {
}
}

// The peer is born in the interior netns under its final name. Do not replace
// this with the obvious "create locally, LinkSetNsFd across, rename to eth0":
// moving and renaming a netdev each cost an RCU grace period under the global
// RTNL lock, which is ~18ms vs ~3ms for all of SetupActorNetwork, on the
// resume path. Naming the peer here is safe because its name is resolved in
// its own netns, so it never collides with the pod's eth0.
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{
Name: HostVethName,
},
PeerName: ActorVethTempName,
PeerName: ActorVethName,
// netlink.NsFd, not netns.NsHandle: only the netlink type is recognized
// as IFLA_NET_NS_FD on the peer, though both are file descriptors.
PeerNamespace: netlink.NsFd(int(cfg.InteriorNetNS)),
}
if len(cfg.HostVethHWAddr) > 0 {
veth.LinkAttrs.HardwareAddr = cfg.HostVethHWAddr
Expand All @@ -563,14 +561,6 @@ func SetupActorNetwork(ctx context.Context, cfg NetworkConfig) (retErr error) {
return fmt.Errorf("while bringing up host veth: %w", err)
}

actorLink, err := netlink.LinkByName(ActorVethTempName)
if err != nil {
return fmt.Errorf("while getting actor veth peer: %w", err)
}
if err := netlink.LinkSetNsFd(actorLink, int(cfg.InteriorNetNS)); err != nil {
return fmt.Errorf("while moving actor veth peer into interior netns: %w", err)
}

if err := NetNSDo(ctx, cfg.InteriorNetNS, ConfigureActorVeth); err != nil {
return fmt.Errorf("while configuring actor veth in interior netns: %w", err)
}
Expand Down
Loading
Loading