Since commit 4e246efcd, individual portmappers are responsible for
setting up firewall rules and proxying according to their needs.
This change moves the responsibility back to the bridge driver, removing
unnecessary code duplication across portmappers. For now, only the nat
portmapper takes advantage of this.
This partially reverts commit 4e246efcd.
Signed-off-by: Albin Kerouanton <albinker@gmail.com>
| ... | ... |
@@ -933,7 +933,8 @@ func networkPlatformOptions(conf *config.Config) []nwconfig.Option {
|
| 933 | 933 |
"DisableFilterForwardDrop": conf.BridgeConfig.DisableFilterForwardDrop, |
| 934 | 934 |
"EnableIPTables": conf.BridgeConfig.EnableIPTables, |
| 935 | 935 |
"EnableIP6Tables": conf.BridgeConfig.EnableIP6Tables, |
| 936 |
- "Hairpin": !conf.EnableUserlandProxy || conf.UserlandProxyPath == "", |
|
| 936 |
+ "EnableProxy": conf.EnableUserlandProxy && conf.UserlandProxyPath != "", |
|
| 937 |
+ "ProxyPath": conf.UserlandProxyPath, |
|
| 937 | 938 |
"AllowDirectRouting": conf.BridgeConfig.AllowDirectRouting, |
| 938 | 939 |
"AcceptFwMark": conf.BridgeConfig.BridgeAcceptFwMark, |
| 939 | 940 |
}, |
| ... | ... |
@@ -69,10 +69,11 @@ type configuration struct {
|
| 69 | 69 |
DisableFilterForwardDrop bool |
| 70 | 70 |
EnableIPTables bool |
| 71 | 71 |
EnableIP6Tables bool |
| 72 |
- // Hairpin indicates whether packets sent from a container to a host port |
|
| 73 |
- // published by another container on the same bridge network should be |
|
| 74 |
- // hairpinned. |
|
| 75 |
- Hairpin bool |
|
| 72 |
+ // EnableProxy indicates whether the userland proxy should be used for NAT |
|
| 73 |
+ // port-mappings that can't be fulfilled with firewall rules alone. This |
|
| 74 |
+ // must not be true if ProxyPath is empty. |
|
| 75 |
+ EnableProxy bool |
|
| 76 |
+ ProxyPath string |
|
| 76 | 77 |
AllowDirectRouting bool |
| 77 | 78 |
AcceptFwMark string |
| 78 | 79 |
} |
| ... | ... |
@@ -472,15 +473,6 @@ func (n *bridgeNetwork) gwMode(v firewaller.IPVersion) gwMode {
|
| 472 | 472 |
return n.config.GwModeIPv6 |
| 473 | 473 |
} |
| 474 | 474 |
|
| 475 |
-func (n *bridgeNetwork) hairpin() bool {
|
|
| 476 |
- n.Lock() |
|
| 477 |
- defer n.Unlock() |
|
| 478 |
- if n.driver == nil {
|
|
| 479 |
- return false |
|
| 480 |
- } |
|
| 481 |
- return n.driver.config.Hairpin |
|
| 482 |
-} |
|
| 483 |
- |
|
| 484 | 475 |
func (n *bridgeNetwork) portMappers() *drvregistry.PortMappers {
|
| 485 | 476 |
n.Lock() |
| 486 | 477 |
defer n.Unlock() |
| ... | ... |
@@ -525,7 +517,7 @@ func (d *driver) configure(option map[string]any) error {
|
| 525 | 525 |
d.firewaller, err = newFirewaller(context.Background(), firewaller.Config{
|
| 526 | 526 |
IPv4: config.EnableIPTables, |
| 527 | 527 |
IPv6: config.EnableIP6Tables, |
| 528 |
- Hairpin: config.Hairpin, |
|
| 528 |
+ Hairpin: !config.EnableProxy, |
|
| 529 | 529 |
AllowDirectRouting: config.AllowDirectRouting, |
| 530 | 530 |
WSL2Mirrored: isRunningUnderWSL2MirroredMode(context.Background()), |
| 531 | 531 |
}) |
| ... | ... |
@@ -853,8 +845,8 @@ func (d *driver) createNetwork(ctx context.Context, config *networkConfiguration |
| 853 | 853 |
} |
| 854 | 854 |
|
| 855 | 855 |
// Module br_netfilter needs to be loaded with net.bridge.bridge-nf-call-ip[6]tables |
| 856 |
- // enabled to implement icc=false, or DNAT when hairpin mode is enabled. |
|
| 857 |
- enableBrNfCallIptables := !config.EnableICC || d.config.Hairpin |
|
| 856 |
+ // enabled to implement icc=false, or DNAT when the userland-proxy is disabled. |
|
| 857 |
+ enableBrNfCallIptables := !config.EnableICC || !d.config.EnableProxy |
|
| 858 | 858 |
|
| 859 | 859 |
// Conditionally queue setup steps depending on configuration values. |
| 860 | 860 |
for _, step := range []struct {
|
| ... | ... |
@@ -906,7 +898,7 @@ func (d *driver) createNetwork(ctx context.Context, config *networkConfiguration |
| 906 | 906 |
}, |
| 907 | 907 |
|
| 908 | 908 |
// Setup Loopback Addresses Routing |
| 909 |
- {d.config.Hairpin, "setupLoopbackAddressesRouting", setupLoopbackAddressesRouting},
|
|
| 909 |
+ {!d.config.EnableProxy, "setupLoopbackAddressesRouting", setupLoopbackAddressesRouting},
|
|
| 910 | 910 |
|
| 911 | 911 |
// Setup DefaultGatewayIPv4 |
| 912 | 912 |
{config.DefaultGatewayIPv4 != nil, "setupGatewayIPv4", setupGatewayIPv4},
|
| ... | ... |
@@ -1185,7 +1177,7 @@ func (d *driver) CreateEndpoint(ctx context.Context, nid, eid string, ifInfo dri |
| 1185 | 1185 |
return fmt.Errorf("adding interface %s to bridge %s failed: %v", hostIfName, config.BridgeName, err)
|
| 1186 | 1186 |
} |
| 1187 | 1187 |
|
| 1188 |
- if dconfig.Hairpin {
|
|
| 1188 |
+ if !dconfig.EnableProxy {
|
|
| 1189 | 1189 |
err = setHairpinMode(d.nlh, host, true) |
| 1190 | 1190 |
if err != nil {
|
| 1191 | 1191 |
return err |
| ... | ... |
@@ -5,15 +5,22 @@ import ( |
| 5 | 5 |
"errors" |
| 6 | 6 |
"fmt" |
| 7 | 7 |
"net" |
| 8 |
+ "net/netip" |
|
| 9 |
+ "os" |
|
| 8 | 10 |
"slices" |
| 9 | 11 |
|
| 10 | 12 |
"github.com/containerd/log" |
| 11 |
- "github.com/moby/moby/v2/daemon/internal/sliceutil" |
|
| 13 |
+ "github.com/moby/moby/v2/daemon/libnetwork/drvregistry" |
|
| 12 | 14 |
"github.com/moby/moby/v2/daemon/libnetwork/netutils" |
| 15 |
+ "github.com/moby/moby/v2/daemon/libnetwork/portallocator" |
|
| 16 |
+ "github.com/moby/moby/v2/daemon/libnetwork/portmapper" |
|
| 13 | 17 |
"github.com/moby/moby/v2/daemon/libnetwork/portmapperapi" |
| 14 | 18 |
"github.com/moby/moby/v2/daemon/libnetwork/types" |
| 15 | 19 |
) |
| 16 | 20 |
|
| 21 |
+// Allow unit tests to supply a dummy StartProxy. |
|
| 22 |
+var startProxy = portmapper.StartProxy |
|
| 23 |
+ |
|
| 17 | 24 |
// addPortMappings takes cfg, the configuration for port mappings, selects host |
| 18 | 25 |
// ports when ranges are given, binds host ports to check they're available and |
| 19 | 26 |
// reserve them, starts docker-proxy if required, and sets up iptables |
| ... | ... |
@@ -72,19 +79,11 @@ func (n *bridgeNetwork) addPortMappings( |
| 72 | 72 |
continue |
| 73 | 73 |
} |
| 74 | 74 |
|
| 75 |
- pm, err := pms.Get(c.Mapper) |
|
| 76 |
- if err != nil {
|
|
| 77 |
- return nil, err |
|
| 78 |
- } |
|
| 79 |
- |
|
| 80 |
- newB, err := pm.MapPorts(ctx, toBind, n.firewallerNetwork) |
|
| 75 |
+ newB, err := n.mapPorts(ctx, pms, toBind) |
|
| 81 | 76 |
if err != nil {
|
| 82 | 77 |
return nil, err |
| 83 | 78 |
} |
| 84 |
- bindings = append(bindings, sliceutil.Map(newB, func(b portmapperapi.PortBinding) portmapperapi.PortBinding {
|
|
| 85 |
- b.Mapper = c.Mapper |
|
| 86 |
- return b |
|
| 87 |
- })...) |
|
| 79 |
+ bindings = append(bindings, newB...) |
|
| 88 | 80 |
|
| 89 | 81 |
// Reset toBind now the ports are bound. |
| 90 | 82 |
toBind = toBind[:0] |
| ... | ... |
@@ -93,6 +92,96 @@ func (n *bridgeNetwork) addPortMappings( |
| 93 | 93 |
return bindings, nil |
| 94 | 94 |
} |
| 95 | 95 |
|
| 96 |
+// mapPorts calls the port mapper used to map the ports in reqs, applies the firewall rules requested by that portmapper, |
|
| 97 |
+// and starts userland proxies if needed. It returns an error if it fails on any of these steps, and rolls back any |
|
| 98 |
+// changes it made. Caller must ensure that reqs is non-empty and all requests have the same Mapper set. |
|
| 99 |
+func (n *bridgeNetwork) mapPorts(ctx context.Context, pms *drvregistry.PortMappers, reqs []portmapperapi.PortBindingReq) (_ []portmapperapi.PortBinding, retErr error) {
|
|
| 100 |
+ mapper := reqs[0].Mapper |
|
| 101 |
+ pm, err := pms.Get(mapper) |
|
| 102 |
+ if err != nil {
|
|
| 103 |
+ return nil, err |
|
| 104 |
+ } |
|
| 105 |
+ |
|
| 106 |
+ bindings, err := pm.MapPorts(ctx, reqs, n.firewallerNetwork) |
|
| 107 |
+ if err != nil {
|
|
| 108 |
+ return nil, err |
|
| 109 |
+ } |
|
| 110 |
+ defer func() {
|
|
| 111 |
+ if retErr != nil {
|
|
| 112 |
+ if err := pm.UnmapPorts(ctx, bindings, n.firewallerNetwork); err != nil {
|
|
| 113 |
+ log.G(ctx).WithFields(log.Fields{
|
|
| 114 |
+ "bindings": bindings, |
|
| 115 |
+ "error": err, |
|
| 116 |
+ "origErr": retErr, |
|
| 117 |
+ }).Warn("Failed to unmap port bindings after error")
|
|
| 118 |
+ } |
|
| 119 |
+ return |
|
| 120 |
+ } |
|
| 121 |
+ }() |
|
| 122 |
+ |
|
| 123 |
+ for i := range bindings {
|
|
| 124 |
+ // Make sure that Mapper is correctly set such that UnmapPorts call the right portmapper. |
|
| 125 |
+ bindings[i].Mapper = mapper |
|
| 126 |
+ } |
|
| 127 |
+ |
|
| 128 |
+ fwPorts := collectFirewallPorts(bindings) |
|
| 129 |
+ if err := n.firewallerNetwork.AddPorts(ctx, fwPorts); err != nil {
|
|
| 130 |
+ return nil, err |
|
| 131 |
+ } |
|
| 132 |
+ defer func() {
|
|
| 133 |
+ if retErr != nil {
|
|
| 134 |
+ if err := n.firewallerNetwork.DelPorts(ctx, fwPorts); err != nil {
|
|
| 135 |
+ log.G(ctx).WithFields(log.Fields{
|
|
| 136 |
+ "bindings": bindings, |
|
| 137 |
+ "error": err, |
|
| 138 |
+ "origErr": retErr, |
|
| 139 |
+ }).Warn("Failed to remove firewall rules after error")
|
|
| 140 |
+ } |
|
| 141 |
+ } |
|
| 142 |
+ }() |
|
| 143 |
+ |
|
| 144 |
+ // Start userland proxy processes. |
|
| 145 |
+ defer func() {
|
|
| 146 |
+ if retErr != nil {
|
|
| 147 |
+ for _, pb := range bindings {
|
|
| 148 |
+ if pb.StopProxy == nil {
|
|
| 149 |
+ continue |
|
| 150 |
+ } |
|
| 151 |
+ if err := pb.StopProxy(); err != nil {
|
|
| 152 |
+ log.G(ctx).WithFields(log.Fields{
|
|
| 153 |
+ "binding": pb.PortBinding, |
|
| 154 |
+ "error": err, |
|
| 155 |
+ }).Warnf("failed to stop userland proxy for port mapping")
|
|
| 156 |
+ } |
|
| 157 |
+ } |
|
| 158 |
+ } |
|
| 159 |
+ }() |
|
| 160 |
+ if n.driver.config.EnableProxy {
|
|
| 161 |
+ for i, pb := range bindings {
|
|
| 162 |
+ if pb.BoundSocket == nil || pb.RootlesskitUnsupported || pb.StopProxy != nil {
|
|
| 163 |
+ continue |
|
| 164 |
+ } |
|
| 165 |
+ if err := portallocator.DetachSocketFilter(bindings[i].BoundSocket); err != nil {
|
|
| 166 |
+ return nil, fmt.Errorf("failed to detach socket filter for port mapping %s: %w", bindings[i].PortBinding, err)
|
|
| 167 |
+ } |
|
| 168 |
+ var err error |
|
| 169 |
+ bindings[i].StopProxy, err = startProxy(pb.ChildPortBinding(), n.driver.config.ProxyPath, pb.BoundSocket) |
|
| 170 |
+ if err != nil {
|
|
| 171 |
+ return nil, fmt.Errorf("failed to start userland proxy for port mapping %s: %w", pb.PortBinding, err)
|
|
| 172 |
+ } |
|
| 173 |
+ if err := bindings[i].BoundSocket.Close(); err != nil {
|
|
| 174 |
+ log.G(ctx).WithFields(log.Fields{
|
|
| 175 |
+ "error": err, |
|
| 176 |
+ "mapping": pb.PortBinding, |
|
| 177 |
+ }).Warnf("failed to close proxy socket")
|
|
| 178 |
+ } |
|
| 179 |
+ bindings[i].BoundSocket = nil |
|
| 180 |
+ } |
|
| 181 |
+ } |
|
| 182 |
+ |
|
| 183 |
+ return bindings, nil |
|
| 184 |
+} |
|
| 185 |
+ |
|
| 96 | 186 |
// sortAndNormPBs transforms cfg into a list of portBindingReq, with all fields |
| 97 | 187 |
// normalized: |
| 98 | 188 |
// |
| ... | ... |
@@ -123,7 +212,6 @@ func (n *bridgeNetwork) sortAndNormPBs( |
| 123 | 123 |
containerIPv6 = ep.addrv6.IP |
| 124 | 124 |
} |
| 125 | 125 |
|
| 126 |
- hairpin := n.hairpin() |
|
| 127 | 126 |
disableNAT4, disableNAT6 := n.getNATDisabled() |
| 128 | 127 |
|
| 129 | 128 |
add4 := !ep.portBindingState.ipv4 && pbmReq.ipv4 || (disableNAT4 && !ep.portBindingState.routed && pbmReq.routed) |
| ... | ... |
@@ -146,7 +234,7 @@ func (n *bridgeNetwork) sortAndNormPBs( |
| 146 | 146 |
// This change was added to keep backward compatibility |
| 147 | 147 |
containerIP := containerIPv6 |
| 148 | 148 |
if containerIPv6 == nil && pbmReq.ipv4 && add6 {
|
| 149 |
- if hairpin {
|
|
| 149 |
+ if !n.driver.config.EnableProxy {
|
|
| 150 | 150 |
// There's no way to map from host-IPv6 to container-IPv4 with the userland proxy |
| 151 | 151 |
// disabled. |
| 152 | 152 |
// If that is required, don't treat it as an error because, as networks are |
| ... | ... |
@@ -189,21 +277,6 @@ func needSamePort(a, b portmapperapi.PortBindingReq) bool {
|
| 189 | 189 |
a.HostPortEnd == b.HostPortEnd |
| 190 | 190 |
} |
| 191 | 191 |
|
| 192 |
-// mergeChildHostIPs take a slice of PortBinding and returns a slice of |
|
| 193 |
-// types.PortBinding, where the HostIP in each of the results has the |
|
| 194 |
-// value of ChildHostIP from the input (if present). |
|
| 195 |
-func mergeChildHostIPs(pbs []portmapperapi.PortBinding) []types.PortBinding {
|
|
| 196 |
- res := make([]types.PortBinding, 0, len(pbs)) |
|
| 197 |
- for _, b := range pbs {
|
|
| 198 |
- pb := b.PortBinding |
|
| 199 |
- if b.ChildHostIP != nil {
|
|
| 200 |
- pb.HostIP = b.ChildHostIP |
|
| 201 |
- } |
|
| 202 |
- res = append(res, pb) |
|
| 203 |
- } |
|
| 204 |
- return res |
|
| 205 |
-} |
|
| 206 |
- |
|
| 207 | 192 |
// configurePortBindingIPv4 returns a new port binding with the HostIP field |
| 208 | 193 |
// populated and true, if a binding is required. Else, false and an empty |
| 209 | 194 |
// binding. |
| ... | ... |
@@ -343,6 +416,15 @@ func (n *bridgeNetwork) unmapPBs(ctx context.Context, bindings []portmapperapi.P |
| 343 | 343 |
if err := pm.UnmapPorts(ctx, []portmapperapi.PortBinding{b}, n.firewallerNetwork); err != nil {
|
| 344 | 344 |
errs = append(errs, fmt.Errorf("unmapping port binding %s: %w", b.PortBinding, err))
|
| 345 | 345 |
} |
| 346 |
+ if b.StopProxy != nil {
|
|
| 347 |
+ if err := b.StopProxy(); err != nil && !errors.Is(err, os.ErrProcessDone) {
|
|
| 348 |
+ errs = append(errs, fmt.Errorf("unmapping port binding %s: failed to stop userland proxy: %w", b.PortBinding, err))
|
|
| 349 |
+ } |
|
| 350 |
+ } |
|
| 351 |
+ } |
|
| 352 |
+ |
|
| 353 |
+ if err := n.firewallerNetwork.DelPorts(ctx, collectFirewallPorts(bindings)); err != nil {
|
|
| 354 |
+ return err |
|
| 346 | 355 |
} |
| 347 | 356 |
|
| 348 | 357 |
return errors.Join(errs...) |
| ... | ... |
@@ -365,7 +447,55 @@ func (n *bridgeNetwork) reapplyPerPortIptables() {
|
| 365 | 365 |
} |
| 366 | 366 |
} |
| 367 | 367 |
|
| 368 |
- if err := n.firewallerNetwork.AddPorts(context.Background(), mergeChildHostIPs(allPBs)); err != nil {
|
|
| 368 |
+ if err := n.firewallerNetwork.AddPorts(context.Background(), collectFirewallPorts(allPBs)); err != nil {
|
|
| 369 | 369 |
log.G(context.TODO()).Warnf("Failed to reconfigure NAT: %s", err)
|
| 370 | 370 |
} |
| 371 | 371 |
} |
| 372 |
+ |
|
| 373 |
+// collectFirewallPorts collects all the types.PortBinding needed to |
|
| 374 |
+// reconfigure the host firewall for a given list of port bindings. If one of |
|
| 375 |
+// the pbs is NATed, but has an invalid NAT field (i.e. multicast address, or a |
|
| 376 |
+// port 0), an error is returned. |
|
| 377 |
+func collectFirewallPorts(pbs []portmapperapi.PortBinding) []types.PortBinding {
|
|
| 378 |
+ var fwPBs []types.PortBinding |
|
| 379 |
+ for _, pb := range pbs {
|
|
| 380 |
+ if pb.NAT.IsValid() {
|
|
| 381 |
+ if pb.NAT.Addr().IsMulticast() || pb.NAT.Port() == 0 {
|
|
| 382 |
+ log.G(context.Background()).WithFields(log.Fields{"pb": pb}).Error("invalid NAT address")
|
|
| 383 |
+ continue |
|
| 384 |
+ } |
|
| 385 |
+ fwPBs = append(fwPBs, toNATBinding(pb)) |
|
| 386 |
+ } else if pb.Forwarding {
|
|
| 387 |
+ fwPBs = append(fwPBs, toFwdBinding(pb)) |
|
| 388 |
+ } |
|
| 389 |
+ } |
|
| 390 |
+ return fwPBs |
|
| 391 |
+} |
|
| 392 |
+ |
|
| 393 |
+// toNATBinding converts a portmapperapi.PortBinding to a types.PortBinding |
|
| 394 |
+// that can be passed to firewaller.Network for setting up a NAT rule. |
|
| 395 |
+func toNATBinding(pb portmapperapi.PortBinding) types.PortBinding {
|
|
| 396 |
+ return types.PortBinding{
|
|
| 397 |
+ IP: pb.IP, |
|
| 398 |
+ Port: pb.Port, |
|
| 399 |
+ Proto: pb.Proto, |
|
| 400 |
+ HostIP: pb.NAT.Addr().AsSlice(), |
|
| 401 |
+ HostPort: pb.NAT.Port(), |
|
| 402 |
+ HostPortEnd: pb.NAT.Port(), |
|
| 403 |
+ } |
|
| 404 |
+} |
|
| 405 |
+ |
|
| 406 |
+// toFwdBinding converts a portmapperapi.PortBinding to a types.PortBinding |
|
| 407 |
+// that can be passed to firewaller.Network for setting up forwarding. |
|
| 408 |
+func toFwdBinding(pb portmapperapi.PortBinding) types.PortBinding {
|
|
| 409 |
+ unspecAddr := netip.IPv4Unspecified() |
|
| 410 |
+ if pb.IP.To4() == nil {
|
|
| 411 |
+ unspecAddr = netip.IPv6Unspecified() |
|
| 412 |
+ } |
|
| 413 |
+ return types.PortBinding{
|
|
| 414 |
+ IP: pb.IP, |
|
| 415 |
+ Port: pb.Port, |
|
| 416 |
+ Proto: pb.Proto, |
|
| 417 |
+ HostIP: unspecAddr.AsSlice(), |
|
| 418 |
+ } |
|
| 419 |
+} |
| ... | ... |
@@ -563,7 +563,6 @@ func TestAddPortMappings(t *testing.T) {
|
| 563 | 563 |
cfg: []portmapperapi.PortBindingReq{
|
| 564 | 564 |
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 22, HostIP: net.IPv6loopback}},
|
| 565 | 565 |
}, |
| 566 |
- hairpin: true, |
|
| 567 | 566 |
expLogs: []string{"Cannot map from IPv6 to an IPv4-only container because the userland proxy is disabled"},
|
| 568 | 567 |
}, |
| 569 | 568 |
{
|
| ... | ... |
@@ -573,7 +572,6 @@ func TestAddPortMappings(t *testing.T) {
|
| 573 | 573 |
cfg: []portmapperapi.PortBindingReq{
|
| 574 | 574 |
{PortBinding: types.PortBinding{Proto: types.TCP, Port: 22}},
|
| 575 | 575 |
}, |
| 576 |
- hairpin: true, |
|
| 577 | 576 |
expLogs: []string{"Cannot map from default host binding address to an IPv4-only container because the userland proxy is disabled"},
|
| 578 | 577 |
}, |
| 579 | 578 |
{
|
| ... | ... |
@@ -718,7 +716,7 @@ func TestAddPortMappings(t *testing.T) {
|
| 718 | 718 |
|
| 719 | 719 |
// Mock the startProxy function used by the code under test. |
| 720 | 720 |
proxies := map[proxyCall]bool{} // proxy -> is not stopped
|
| 721 |
- startProxy := func(pb types.PortBinding, listenSock *os.File) (stop func() error, retErr error) {
|
|
| 721 |
+ startProxy = func(pb types.PortBinding, _ string, listenSock *os.File) (stop func() error, retErr error) {
|
|
| 722 | 722 |
if tc.busyPortIPv4 > 0 && tc.busyPortIPv4 == int(pb.HostPort) && pb.HostIP.To4() != nil {
|
| 723 | 723 |
return nil, errors.New("busy port")
|
| 724 | 724 |
} |
| ... | ... |
@@ -768,9 +766,7 @@ func TestAddPortMappings(t *testing.T) {
|
| 768 | 768 |
|
| 769 | 769 |
pms := &drvregistry.PortMappers{}
|
| 770 | 770 |
err := nat.Register(pms, nat.Config{
|
| 771 |
- RlkClient: pdc, |
|
| 772 |
- EnableProxy: tc.enableProxy, |
|
| 773 |
- StartProxy: startProxy, |
|
| 771 |
+ RlkClient: pdc, |
|
| 774 | 772 |
}) |
| 775 | 773 |
assert.NilError(t, err) |
| 776 | 774 |
err = routed.Register(pms) |
| ... | ... |
@@ -791,7 +787,7 @@ func TestAddPortMappings(t *testing.T) {
|
| 791 | 791 |
netlabel.GenericData: &configuration{
|
| 792 | 792 |
EnableIPTables: true, |
| 793 | 793 |
EnableIP6Tables: true, |
| 794 |
- Hairpin: tc.hairpin, |
|
| 794 |
+ EnableProxy: tc.enableProxy, |
|
| 795 | 795 |
}, |
| 796 | 796 |
} |
| 797 | 797 |
err = n.driver.configure(genericOption) |
| ... | ... |
@@ -892,8 +888,8 @@ func TestAddPortMappings(t *testing.T) {
|
| 892 | 892 |
} |
| 893 | 893 |
|
| 894 | 894 |
// Check a docker-proxy was started and stopped for each expected port binding. |
| 895 |
+ expProxies := map[proxyCall]bool{}
|
|
| 895 | 896 |
if tc.enableProxy {
|
| 896 |
- expProxies := map[proxyCall]bool{}
|
|
| 897 | 897 |
for _, expPB := range tc.expPBs {
|
| 898 | 898 |
hip := expChildIP(expPB.HostIP) |
| 899 | 899 |
is4 := hip.To4() != nil |
| ... | ... |
@@ -905,8 +901,8 @@ func TestAddPortMappings(t *testing.T) {
|
| 905 | 905 |
expPB.IP, int(expPB.Port)) |
| 906 | 906 |
expProxies[p] = tc.expReleaseErr != "" |
| 907 | 907 |
} |
| 908 |
- assert.Check(t, is.DeepEqual(expProxies, proxies)) |
|
| 909 | 908 |
} |
| 909 |
+ assert.Check(t, is.DeepEqual(expProxies, proxies)) |
|
| 910 | 910 |
|
| 911 | 911 |
// Check the port driver has seen the expected port mappings and no others, |
| 912 | 912 |
// and that they have all been closed. |
| ... | ... |
@@ -3,7 +3,6 @@ package libnetwork |
| 3 | 3 |
import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
"fmt" |
| 6 |
- "os" |
|
| 7 | 6 |
|
| 8 | 7 |
"github.com/moby/moby/v2/daemon/libnetwork/config" |
| 9 | 8 |
"github.com/moby/moby/v2/daemon/libnetwork/datastore" |
| ... | ... |
@@ -16,10 +15,8 @@ import ( |
| 16 | 16 |
"github.com/moby/moby/v2/daemon/libnetwork/drivers/overlay" |
| 17 | 17 |
"github.com/moby/moby/v2/daemon/libnetwork/drvregistry" |
| 18 | 18 |
"github.com/moby/moby/v2/daemon/libnetwork/internal/rlkclient" |
| 19 |
- "github.com/moby/moby/v2/daemon/libnetwork/portmapper" |
|
| 20 | 19 |
"github.com/moby/moby/v2/daemon/libnetwork/portmappers/nat" |
| 21 | 20 |
"github.com/moby/moby/v2/daemon/libnetwork/portmappers/routed" |
| 22 |
- "github.com/moby/moby/v2/daemon/libnetwork/types" |
|
| 23 | 21 |
) |
| 24 | 22 |
|
| 25 | 23 |
func registerNetworkDrivers(r driverapi.Registerer, store *datastore.Store, pms *drvregistry.PortMappers, driverConfig func(string) map[string]any) error {
|
| ... | ... |
@@ -60,13 +57,7 @@ func registerPortMappers(ctx context.Context, r *drvregistry.PortMappers, cfg *c |
| 60 | 60 |
} |
| 61 | 61 |
} |
| 62 | 62 |
|
| 63 |
- if err := nat.Register(r, nat.Config{
|
|
| 64 |
- RlkClient: pdc, |
|
| 65 |
- StartProxy: func(pb types.PortBinding, file *os.File) (func() error, error) {
|
|
| 66 |
- return portmapper.StartProxy(pb, cfg.UserlandProxyPath, file) |
|
| 67 |
- }, |
|
| 68 |
- EnableProxy: cfg.EnableUserlandProxy && cfg.UserlandProxyPath != "", |
|
| 69 |
- }); err != nil {
|
|
| 63 |
+ if err := nat.Register(r, nat.Config{RlkClient: pdc}); err != nil {
|
|
| 70 | 64 |
return fmt.Errorf("registering nat portmapper: %w", err)
|
| 71 | 65 |
} |
| 72 | 66 |
|
| ... | ... |
@@ -6,7 +6,6 @@ import ( |
| 6 | 6 |
"fmt" |
| 7 | 7 |
"net" |
| 8 | 8 |
"net/netip" |
| 9 |
- "os" |
|
| 10 | 9 |
"strconv" |
| 11 | 10 |
|
| 12 | 11 |
"github.com/containerd/log" |
| ... | ... |
@@ -23,8 +22,6 @@ type PortDriverClient interface {
|
| 23 | 23 |
AddPort(ctx context.Context, proto string, hostIP, childIP netip.Addr, hostPort int) (func() error, error) |
| 24 | 24 |
} |
| 25 | 25 |
|
| 26 |
-type proxyStarter func(types.PortBinding, *os.File) (func() error, error) |
|
| 27 |
- |
|
| 28 | 26 |
// Register the "nat" port-mapper with libnetwork. |
| 29 | 27 |
func Register(r portmapperapi.Registerer, cfg Config) error {
|
| 30 | 28 |
return r.Register(driverName, NewPortMapper(cfg)) |
| ... | ... |
@@ -32,24 +29,18 @@ func Register(r portmapperapi.Registerer, cfg Config) error {
|
| 32 | 32 |
|
| 33 | 33 |
type PortMapper struct {
|
| 34 | 34 |
// pdc is used to interact with rootlesskit port driver. |
| 35 |
- pdc PortDriverClient |
|
| 36 |
- startProxy proxyStarter |
|
| 37 |
- enableProxy bool |
|
| 35 |
+ pdc PortDriverClient |
|
| 38 | 36 |
} |
| 39 | 37 |
|
| 40 | 38 |
type Config struct {
|
| 41 | 39 |
// RlkClient is called by MapPorts to determine the ChildHostIP and ask |
| 42 | 40 |
// rootlesskit to map ports in its netns. |
| 43 |
- RlkClient PortDriverClient |
|
| 44 |
- StartProxy proxyStarter |
|
| 45 |
- EnableProxy bool |
|
| 41 |
+ RlkClient PortDriverClient |
|
| 46 | 42 |
} |
| 47 | 43 |
|
| 48 | 44 |
func NewPortMapper(cfg Config) PortMapper {
|
| 49 | 45 |
return PortMapper{
|
| 50 |
- pdc: cfg.RlkClient, |
|
| 51 |
- startProxy: cfg.StartProxy, |
|
| 52 |
- enableProxy: cfg.EnableProxy, |
|
| 46 |
+ pdc: cfg.RlkClient, |
|
| 53 | 47 |
} |
| 54 | 48 |
} |
| 55 | 49 |
|
| ... | ... |
@@ -102,42 +93,16 @@ func (pm PortMapper) MapPorts(ctx context.Context, cfg []portmapperapi.PortBindi |
| 102 | 102 |
} |
| 103 | 103 |
pb.PortBinding.HostPort = uint16(allocatedPort) |
| 104 | 104 |
pb.PortBinding.HostPortEnd = pb.HostPort |
| 105 |
+ |
|
| 106 |
+ childHIP, _ := netip.AddrFromSlice(cfg[i].ChildHostIP) |
|
| 107 |
+ pb.NAT = netip.AddrPortFrom(childHIP, pb.PortBinding.HostPort) |
|
| 108 |
+ |
|
| 105 | 109 |
bindings = append(bindings, pb) |
| 106 | 110 |
} |
| 107 | 111 |
|
| 108 | 112 |
if err := configPortDriver(ctx, bindings, pm.pdc); err != nil {
|
| 109 | 113 |
return nil, err |
| 110 | 114 |
} |
| 111 |
- if err := fwn.AddPorts(ctx, mergeChildHostIPs(bindings)); err != nil {
|
|
| 112 |
- return nil, err |
|
| 113 |
- } |
|
| 114 |
- |
|
| 115 |
- // Start userland proxy processes. |
|
| 116 |
- if pm.enableProxy {
|
|
| 117 |
- for i := range bindings {
|
|
| 118 |
- if bindings[i].BoundSocket == nil || bindings[i].RootlesskitUnsupported || bindings[i].StopProxy != nil {
|
|
| 119 |
- continue |
|
| 120 |
- } |
|
| 121 |
- if err := portallocator.DetachSocketFilter(bindings[i].BoundSocket); err != nil {
|
|
| 122 |
- return nil, fmt.Errorf("failed to detach socket filter for port mapping %s: %w", bindings[i].PortBinding, err)
|
|
| 123 |
- } |
|
| 124 |
- var err error |
|
| 125 |
- bindings[i].StopProxy, err = pm.startProxy( |
|
| 126 |
- bindings[i].ChildPortBinding(), bindings[i].BoundSocket, |
|
| 127 |
- ) |
|
| 128 |
- if err != nil {
|
|
| 129 |
- return nil, fmt.Errorf("failed to start userland proxy for port mapping %s: %w",
|
|
| 130 |
- bindings[i].PortBinding, err) |
|
| 131 |
- } |
|
| 132 |
- if err := bindings[i].BoundSocket.Close(); err != nil {
|
|
| 133 |
- log.G(ctx).WithFields(log.Fields{
|
|
| 134 |
- "error": err, |
|
| 135 |
- "mapping": bindings[i].PortBinding, |
|
| 136 |
- }).Warnf("failed to close proxy socket")
|
|
| 137 |
- } |
|
| 138 |
- bindings[i].BoundSocket = nil |
|
| 139 |
- } |
|
| 140 |
- } |
|
| 141 | 115 |
|
| 142 | 116 |
return bindings, nil |
| 143 | 117 |
} |
| ... | ... |
@@ -155,14 +120,6 @@ func (pm PortMapper) UnmapPorts(ctx context.Context, pbs []portmapperapi.PortBin |
| 155 | 155 |
errs = append(errs, err) |
| 156 | 156 |
} |
| 157 | 157 |
} |
| 158 |
- if pb.StopProxy != nil {
|
|
| 159 |
- if err := pb.StopProxy(); err != nil && !errors.Is(err, os.ErrProcessDone) {
|
|
| 160 |
- errs = append(errs, fmt.Errorf("failed to stop userland proxy: %w", err))
|
|
| 161 |
- } |
|
| 162 |
- } |
|
| 163 |
- } |
|
| 164 |
- if err := fwn.DelPorts(ctx, mergeChildHostIPs(pbs)); err != nil {
|
|
| 165 |
- errs = append(errs, err) |
|
| 166 | 158 |
} |
| 167 | 159 |
for _, pb := range pbs {
|
| 168 | 160 |
portallocator.Get().ReleasePort(pb.ChildHostIP, pb.Proto.String(), int(pb.HostPort)) |
| ... | ... |
@@ -180,21 +137,6 @@ func setChildHostIP(pdc PortDriverClient, req portmapperapi.PortBindingReq) port |
| 180 | 180 |
return req |
| 181 | 181 |
} |
| 182 | 182 |
|
| 183 |
-// mergeChildHostIPs take a slice of PortBinding and returns a slice of |
|
| 184 |
-// types.PortBinding, where the HostIP in each of the results has the |
|
| 185 |
-// value of ChildHostIP from the input (if present). |
|
| 186 |
-func mergeChildHostIPs(pbs []portmapperapi.PortBinding) []types.PortBinding {
|
|
| 187 |
- res := make([]types.PortBinding, 0, len(pbs)) |
|
| 188 |
- for _, b := range pbs {
|
|
| 189 |
- pb := b.PortBinding |
|
| 190 |
- if b.ChildHostIP != nil {
|
|
| 191 |
- pb.HostIP = b.ChildHostIP |
|
| 192 |
- } |
|
| 193 |
- res = append(res, pb) |
|
| 194 |
- } |
|
| 195 |
- return res |
|
| 196 |
-} |
|
| 197 |
- |
|
| 198 | 183 |
// configPortDriver passes the port binding's details to rootlesskit, and updates the |
| 199 | 184 |
// port binding with callbacks to remove the rootlesskit config (or marks the binding as |
| 200 | 185 |
// unsupported by rootlesskit). |