github.com/rootless-containers/rootlesskit/v2@v2.3.4/cmd/rootlesskit-docker-proxy/main.go (about) 1 // Package main provides the `rootlesskit-docker-proxy` binary (DEPRECATED) 2 // that was used by Docker prior to v28 for supporting rootless mode. 3 // 4 // The rootlesskit-docker-proxy binary is no longer needed since Docker v28, 5 // as the functionality of rootlesskit-docker-proxy is now provided by dockerd itself. 6 // 7 // https://github.com/moby/moby/pull/48132/commits/dac7ffa3404138a4f291c16586e5a2c68dad4151 8 // 9 // rootlesskit-docker-proxy will be removed in RootlessKit v3. 10 package main 11 12 import ( 13 "context" 14 "errors" 15 "flag" 16 "fmt" 17 "log" 18 "net" 19 "os" 20 "os/exec" 21 "os/signal" 22 "path/filepath" 23 "strconv" 24 "strings" 25 "syscall" 26 27 "github.com/rootless-containers/rootlesskit/v2/pkg/api" 28 "github.com/rootless-containers/rootlesskit/v2/pkg/api/client" 29 "github.com/rootless-containers/rootlesskit/v2/pkg/port" 30 "github.com/sirupsen/logrus" 31 ) 32 33 const ( 34 realProxy = "docker-proxy" 35 ) 36 37 // drop-in replacement for docker-proxy. 38 // needs to be executed in the child namespace. 39 func main() { 40 f := os.NewFile(3, "signal-parent") 41 defer f.Close() 42 if err := xmain(f); err != nil { 43 // success: "0\n" (written by realProxy) 44 // error: "1\n" (written by either rootlesskit-docker-proxy or realProxy) 45 fmt.Fprintf(f, "1\n%s", err) 46 log.Fatal(err) 47 } 48 } 49 50 func isIPv6(ipStr string) bool { 51 ip := net.ParseIP(ipStr) 52 if ip == nil { 53 return false 54 } 55 return ip.To4() == nil 56 } 57 58 func getPortDriverProtos(info *api.Info) (string, map[string]struct{}, error) { 59 if info.PortDriver == nil { 60 return "", nil, errors.New("no port driver is available") 61 } 62 m := make(map[string]struct{}, len(info.PortDriver.Protos)) 63 for _, p := range info.PortDriver.Protos { 64 m[p] = struct{}{} 65 } 66 return info.PortDriver.Driver, m, nil 67 } 68 69 type protocolUnsupportedError struct { 70 apiProto string 71 portDriverName string 72 hostIP string 73 hostPort int 74 } 75 76 func (e *protocolUnsupportedError) Error() string { 77 return fmt.Sprintf("protocol %q is not supported by the RootlessKit port driver %q, discarding request for %q", 78 e.apiProto, 79 e.portDriverName, 80 net.JoinHostPort(e.hostIP, strconv.Itoa(e.hostPort))) 81 } 82 83 func callRootlessKitAPI(c client.Client, info *api.Info, 84 hostIP string, hostPort int, 85 dockerProxyProto, childIP string) (func() error, error) { 86 // dockerProxyProto is like "tcp", but we need to convert it to "tcp4" or "tcp6" explicitly 87 // for libnetwork >= 20201216 88 // 89 // See https://github.com/moby/libnetwork/pull/2604/files#diff-8fa48beed55dd033bf8e4f8c40b31cf69d0b2cc5d4bb53cde8594670ea6c938aR20 90 // See also https://github.com/rootless-containers/rootlesskit/issues/231 91 apiProto := dockerProxyProto 92 if !strings.HasSuffix(apiProto, "4") && !strings.HasSuffix(apiProto, "6") { 93 if isIPv6(hostIP) { 94 apiProto += "6" 95 } else { 96 apiProto += "4" 97 } 98 } 99 portDriverName, apiProtos, err := getPortDriverProtos(info) 100 if err != nil { 101 return nil, err 102 } 103 if _, ok := apiProtos[apiProto]; !ok { 104 // This happens when apiProto="tcp6", portDriverName="slirp4netns", 105 // because "slirp4netns" port driver does not support listening on IPv6 yet. 106 // 107 // Note that "slirp4netns" port driver is not used by default, 108 // even when network driver is set to "slirp4netns". 109 // 110 // Most users are using "builtin" port driver and will not see this warning. 111 err := &protocolUnsupportedError{ 112 apiProto: apiProto, 113 portDriverName: portDriverName, 114 hostIP: hostIP, 115 hostPort: hostPort, 116 } 117 return nil, err 118 } 119 120 pm := c.PortManager() 121 p := port.Spec{ 122 Proto: apiProto, 123 ParentIP: hostIP, 124 ParentPort: hostPort, 125 ChildIP: childIP, 126 ChildPort: hostPort, 127 } 128 st, err := pm.AddPort(context.Background(), p) 129 if err != nil { 130 return nil, fmt.Errorf("error while calling PortManager.AddPort(): %w", err) 131 } 132 deferFunc := func() error { 133 if dErr := pm.RemovePort(context.Background(), st.ID); dErr != nil { 134 return fmt.Errorf("error while calling PortManager.RemovePort(): %w", err) 135 } 136 return nil 137 } 138 return deferFunc, nil 139 } 140 141 func xmain(f *os.File) error { 142 containerIP := flag.String("container-ip", "", "container ip") 143 containerPort := flag.Int("container-port", -1, "container port") 144 hostIP := flag.String("host-ip", "", "host ip") 145 hostPort := flag.Int("host-port", -1, "host port") 146 proto := flag.String("proto", "tcp", "proxy protocol") 147 flag.Parse() 148 149 stateDir := os.Getenv("ROOTLESSKIT_STATE_DIR") 150 if stateDir == "" { 151 return errors.New("$ROOTLESSKIT_STATE_DIR needs to be set") 152 } 153 socketPath := filepath.Join(stateDir, "api.sock") 154 c, err := client.New(socketPath) 155 if err != nil { 156 return fmt.Errorf("error while connecting to RootlessKit API socket: %w", err) 157 } 158 159 info, err := c.Info(context.Background()) 160 if err != nil { 161 return fmt.Errorf("failed to call info API, probably RootlessKit binary is too old (needs to be v0.14.0 or later): %w", err) 162 } 163 164 // info.PortDriver is currently nil for "none" and "implicit", but this may change in future 165 if info.PortDriver == nil || info.PortDriver.Driver == "none" || info.PortDriver.Driver == "implicit" { 166 realProxyExe, err := exec.LookPath(realProxy) 167 if err != nil { 168 return err 169 } 170 return syscall.Exec(realProxyExe, append([]string{realProxy}, os.Args[1:]...), os.Environ()) 171 } 172 173 // use loopback IP as the child IP, when port-driver="builtin" 174 childIP := "127.0.0.1" 175 if isIPv6(*hostIP) { 176 childIP = "::1" 177 } 178 179 if info.PortDriver.DisallowLoopbackChildIP { 180 // i.e., port-driver="slirp4netns" 181 if info.NetworkDriver.ChildIP == nil { 182 return fmt.Errorf("port driver (%q) does not allow loopback child IP, but network driver (%q) has no non-loopback IP", 183 info.PortDriver.Driver, info.NetworkDriver.Driver) 184 } 185 childIP = info.NetworkDriver.ChildIP.String() 186 } 187 188 deferFunc, err := callRootlessKitAPI(c, info, *hostIP, *hostPort, *proto, childIP) 189 if deferFunc != nil { 190 defer func() { 191 if dErr := deferFunc(); dErr != nil { 192 logrus.Warn(dErr) 193 } 194 }() 195 } 196 if err != nil { 197 if _, ok := err.(*protocolUnsupportedError); ok { 198 logrus.Warn(err) 199 // exit without executing realProxy (https://github.com/rootless-containers/rootlesskit/issues/250) 200 fmt.Fprint(f, "0\n") 201 return nil 202 } 203 return err 204 } 205 206 cmd := exec.Command(realProxy, 207 "-container-ip", *containerIP, 208 "-container-port", strconv.Itoa(*containerPort), 209 "-host-ip", childIP, 210 "-host-port", strconv.Itoa(*hostPort), 211 "-proto", *proto) 212 cmd.Stdout = os.Stdout 213 cmd.Stderr = os.Stderr 214 cmd.Env = os.Environ() 215 cmd.ExtraFiles = append(cmd.ExtraFiles, f) 216 cmd.SysProcAttr = &syscall.SysProcAttr{ 217 Pdeathsig: syscall.SIGKILL, 218 } 219 if err := cmd.Start(); err != nil { 220 return fmt.Errorf("error while starting %s: %w", realProxy, err) 221 } 222 223 ch := make(chan os.Signal, 1) 224 signal.Notify(ch, os.Interrupt) 225 <-ch 226 if err := cmd.Process.Kill(); err != nil { 227 return fmt.Errorf("error while killing %s: %w", realProxy, err) 228 } 229 return nil 230 }