@@ -59,7 +59,7 @@ func (c *Container) getNetworkOptions(networkOpts map[string]types.PerNetworkOpt
5959 DNSServers : nameservers ,
6060 ContainerHostname : c .NetworkHostname (),
6161 }
62- opts .PortMappings = c .convertPortMappings ()
62+ opts .PortMappings = portMappingsForNetavark ( c .convertPortMappings () )
6363
6464 // If the container requested special network options use this instead of the config.
6565 // This is the case for container restore or network reload.
@@ -71,6 +71,25 @@ func (c *Container) getNetworkOptions(networkOpts map[string]types.PerNetworkOpt
7171 return opts
7272}
7373
74+ // portMappingsForNetavark strips HostIP from port mappings in rootless mode.
75+ //
76+ // Pesto handles host-side address binding; netavark creates DNAT rules inside
77+ // the rootless netns. If HostIP is kept, netavark adds an "ip daddr <HostIP>"
78+ // constraint to the DNAT rule, but pasta's splice delivers traffic with a
79+ // different destination address (the host virtual IP), so the rule never
80+ // matches and the connection resets. Rootful is unaffected (no-op).
81+ func portMappingsForNetavark (ports []types.PortMapping ) []types.PortMapping {
82+ if ! rootless .IsRootless () || len (ports ) == 0 {
83+ return ports
84+ }
85+ stripped := make ([]types.PortMapping , len (ports ))
86+ copy (stripped , ports )
87+ for i := range stripped {
88+ stripped [i ].HostIP = ""
89+ }
90+ return stripped
91+ }
92+
7493// setUpNetwork will set up the networks, on error it will also tear down the
7594// networks. If rootless it will join/create the rootless network namespace.
7695func (r * Runtime ) setUpNetwork (ns string , opts types.NetworkOptions ) (map [string ]types.StatusBlock , error ) {
@@ -110,11 +129,25 @@ func (r *Runtime) teardownNetwork(ctr *Container) error {
110129 return err
111130 }
112131
113- if ! ctr .config .NetMode .IsPasta () && len (networks ) > 0 {
114- netOpts := ctr .getNetworkOptions (networks )
115- return r .teardownNetworkBackend (ctr .state .NetNS , netOpts )
132+ if len (networks ) == 0 {
133+ return nil
116134 }
117- return nil
135+
136+ // --net=pasta: per-container pasta cleans up when it exits, nothing to tear down.
137+ if ctr .config .NetMode .IsPasta () {
138+ return nil
139+ }
140+
141+ // Bridge mode: update pesto before netavark so pasta stops forwarding
142+ // ports for this container before the bridge/nftables rules are removed.
143+ if rootless .IsRootless () && ctr .config .NetMode .IsBridge () && len (ctr .config .PortMappings ) > 0 {
144+ if err := r .teardownRootlessPortMappingViaPesto (ctr ); err != nil {
145+ logrus .Warnf ("pesto port cleanup failed for container %s: %v" , ctr .ID (), err )
146+ }
147+ }
148+
149+ netOpts := ctr .getNetworkOptions (networks )
150+ return r .teardownNetworkBackend (ctr .state .NetNS , netOpts )
118151}
119152
120153// isBridgeNetMode checks if the given network mode is bridge.
@@ -402,7 +435,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, _ bool) error {
402435 ContainerID : c .config .ID ,
403436 ContainerName : getNetworkPodName (c ),
404437 }
405- opts .PortMappings = c .convertPortMappings ()
438+ opts .PortMappings = portMappingsForNetavark ( c .convertPortMappings () )
406439 opts .Networks = map [string ]types.PerNetworkOptions {
407440 netName : networks [netName ],
408441 }
@@ -421,11 +454,10 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, _ bool) error {
421454 return err
422455 }
423456
424- // Reload ports when there are still connected networks, maybe we removed the network interface with the child ip.
425- // Reloading without connected networks does not make sense, so we can skip this step.
426- if rootless .IsRootless () && len (networkStatus ) > 0 {
427- if err := c .reloadRootlessRLKPortMapping (); err != nil {
428- return err
457+ // Update pesto's forwarding table after disconnect so pasta reflects the new network state.
458+ if rootless .IsRootless () && len (networkStatus ) > 0 && len (c .config .PortMappings ) > 0 {
459+ if err := c .runtime .setupRootlessPortMappingViaPesto (c ); err != nil {
460+ logrus .Warnf ("pesto port reload failed after network disconnect for container %s: %v" , c .ID (), err )
429461 }
430462 }
431463
@@ -542,7 +574,7 @@ func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNe
542574 ContainerID : c .config .ID ,
543575 ContainerName : getNetworkPodName (c ),
544576 }
545- opts .PortMappings = c .convertPortMappings ()
577+ opts .PortMappings = portMappingsForNetavark ( c .convertPortMappings () )
546578 opts .Networks = map [string ]types.PerNetworkOptions {
547579 netName : netOpts ,
548580 }
@@ -575,11 +607,10 @@ func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNe
575607 return err
576608 }
577609
578- // The first network needs a port reload to set the correct child ip for the rootlessport process.
579- // Adding a second network does not require a port reload because the child ip is still valid.
580- if rootless .IsRootless () && len (networks ) == 0 {
581- if err := c .reloadRootlessRLKPortMapping (); err != nil {
582- return err
610+ // Update pesto's forwarding table after connect so pasta reflects the new network state.
611+ if rootless .IsRootless () && len (c .config .PortMappings ) > 0 {
612+ if err := c .runtime .setupRootlessPortMappingViaPesto (c ); err != nil {
613+ logrus .Warnf ("pesto port reload failed after network connect for container %s: %v" , c .ID (), err )
583614 }
584615 }
585616
0 commit comments