mirror of
https://github.com/moby/moby.git
synced 2026-08-07 16:41:50 +00:00
Before commit4f09af6, when allocating host ports for a new port mapping, iptables rules were set up then docker-proxy was started. If the host port was already in-use, docker-proxy exited with an error, and the iptables rules were removed. That could potentially interfere with a non-docker service that was already using the host port for something unrelated. Commit4f09af6swapped that problem for a different one... in order to check that a port was available before creating iptables rules, it attempted to start docker-proxy first. If it failed, it could then try a different host port, without interfering with any other service. The problem with that is docker-proxy would start listening before the iptables rules were in place, so it could accept connections then become unusable because new NAT rules diverted packets directly to the container. This would leave the client with a broken connection, causing at-least a delay while it figured that out and reconnected. This change creates and binds the socket in the daemon, before creating iptables rules. If the bind fails, it may try a different port. When or if the bind succeeds, iptables rules are created, then the daemon calls listen on the socket. If docker-proxy is needed, the socket is handed over to it at that point. In rootless mode, the ports have to be bound to an address in the rootless network namespace (where dockerd is running). DNAT rules now use the same address. If docker-proxy is not needed ("--userland-proxy=false"), the daemon still listens on TCP sockets as the old dummyProxy would have done. This makes the socket show up in "netstat" output. The dummyProxy is no longer needed on Linux. Its job was to bind the host ports if docker-proxy was disabled, but that's now already handled by binding the sockets early. This change doesn't affect SCTP, because it's not currently possible for docker-proxy to convert the file descriptor into an SCTPListener. So, docker-proxy is still started early, and the window for lost connections remains. If the user has an old docker-proxy in their path and it's given a listener docker with '-use-listen-fd', it'll fail because of the unknown option. In this case, the daemon's error message suggests checking $PATH. Signed-off-by: Rob Murray <rob.murray@docker.com>
86 lines
1.8 KiB
Go
86 lines
1.8 KiB
Go
package portmapper
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
|
|
"github.com/ishidawataru/sctp"
|
|
)
|
|
|
|
// ipVersion refers to IP version - v4 or v6
|
|
type ipVersion string
|
|
|
|
const (
|
|
// IPv4 is version 4
|
|
ipv4 ipVersion = "4"
|
|
// IPv4 is version 6
|
|
ipv6 ipVersion = "6"
|
|
)
|
|
|
|
// dummyProxy just listen on some port, it is needed to prevent accidental
|
|
// port allocations on bound port, because without userland proxy we using
|
|
// iptables rules and not net.Listen
|
|
type dummyProxy struct {
|
|
listener io.Closer
|
|
addr net.Addr
|
|
ipVersion ipVersion
|
|
}
|
|
|
|
func newDummyProxy(proto string, hostIP net.IP, hostPort int) (stop func() error, retErr error) {
|
|
// detect version of hostIP to bind only to correct version
|
|
version := ipv4
|
|
if hostIP.To4() == nil {
|
|
version = ipv6
|
|
}
|
|
var addr net.Addr
|
|
switch proto {
|
|
case "tcp":
|
|
addr = &net.TCPAddr{IP: hostIP, Port: hostPort}
|
|
case "udp":
|
|
addr = &net.UDPAddr{IP: hostIP, Port: hostPort}
|
|
case "sctp":
|
|
addr = &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: hostIP}}, Port: hostPort}
|
|
default:
|
|
return nil, fmt.Errorf("Unknown addr type: %s", proto)
|
|
}
|
|
p := &dummyProxy{addr: addr, ipVersion: version}
|
|
if err := p.start(); err != nil {
|
|
return nil, err
|
|
}
|
|
return p.stop, nil
|
|
}
|
|
|
|
func (p *dummyProxy) start() error {
|
|
switch addr := p.addr.(type) {
|
|
case *net.TCPAddr:
|
|
l, err := net.ListenTCP("tcp"+string(p.ipVersion), addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.listener = l
|
|
case *net.UDPAddr:
|
|
l, err := net.ListenUDP("udp"+string(p.ipVersion), addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.listener = l
|
|
case *sctp.SCTPAddr:
|
|
l, err := sctp.ListenSCTP("sctp"+string(p.ipVersion), addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.listener = l
|
|
default:
|
|
return fmt.Errorf("Unknown addr type: %T", p.addr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *dummyProxy) stop() error {
|
|
if p.listener != nil {
|
|
return p.listener.Close()
|
|
}
|
|
return nil
|
|
}
|