github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/cmd/rootlessport/main.go (about) 1 //go:build linux 2 // +build linux 3 4 package main 5 6 import ( 7 "context" 8 "encoding/json" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "net" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "strings" 17 18 "github.com/containernetworking/plugins/pkg/ns" 19 "github.com/containers/common/libnetwork/types" 20 "github.com/hanks177/podman/v4/pkg/rootlessport" 21 "github.com/pkg/errors" 22 rkport "github.com/rootless-containers/rootlesskit/pkg/port" 23 rkbuiltin "github.com/rootless-containers/rootlesskit/pkg/port/builtin" 24 rkportutil "github.com/rootless-containers/rootlesskit/pkg/port/portutil" 25 "github.com/sirupsen/logrus" 26 "golang.org/x/sys/unix" 27 ) 28 29 const ( 30 // ReexecChildKey is used internally for the second reexec 31 ReexecChildKey = "rootlessport-child" 32 reexecChildEnvOpaque = "_CONTAINERS_ROOTLESSPORT_CHILD_OPAQUE" 33 ) 34 35 func main() { 36 if len(os.Args) > 1 { 37 fmt.Fprintln(os.Stderr, `too many arguments, rootlessport expects a json config via STDIN`) 38 os.Exit(1) 39 } 40 var err error 41 if os.Args[0] == ReexecChildKey { 42 err = child() 43 } else { 44 err = parent() 45 } 46 if err != nil { 47 fmt.Println(err) 48 os.Exit(1) 49 } 50 } 51 52 func loadConfig(r io.Reader) (*rootlessport.Config, io.ReadCloser, io.WriteCloser, error) { 53 stdin, err := ioutil.ReadAll(r) 54 if err != nil { 55 return nil, nil, nil, err 56 } 57 var cfg rootlessport.Config 58 if err := json.Unmarshal(stdin, &cfg); err != nil { 59 return nil, nil, nil, err 60 } 61 if cfg.NetNSPath == "" { 62 return nil, nil, nil, errors.New("missing NetNSPath") 63 } 64 if cfg.ExitFD <= 0 { 65 return nil, nil, nil, errors.New("missing ExitFD") 66 } 67 exitFile := os.NewFile(uintptr(cfg.ExitFD), "exitfile") 68 if exitFile == nil { 69 return nil, nil, nil, errors.New("invalid ExitFD") 70 } 71 if cfg.ReadyFD <= 0 { 72 return nil, nil, nil, errors.New("missing ReadyFD") 73 } 74 readyFile := os.NewFile(uintptr(cfg.ReadyFD), "readyfile") 75 if readyFile == nil { 76 return nil, nil, nil, errors.New("invalid ReadyFD") 77 } 78 return &cfg, exitFile, readyFile, nil 79 } 80 81 func parent() error { 82 // load config from stdin 83 cfg, exitR, readyW, err := loadConfig(os.Stdin) 84 if err != nil { 85 return err 86 } 87 88 socketDir := filepath.Join(cfg.TmpDir, "rp") 89 err = os.MkdirAll(socketDir, 0700) 90 if err != nil { 91 return err 92 } 93 94 // create the parent driver 95 stateDir, err := ioutil.TempDir(cfg.TmpDir, "rootlessport") 96 if err != nil { 97 return err 98 } 99 defer os.RemoveAll(stateDir) 100 driver, err := rkbuiltin.NewParentDriver(&logrusWriter{prefix: "parent: "}, stateDir) 101 if err != nil { 102 return err 103 } 104 initComplete := make(chan struct{}) 105 quit := make(chan struct{}) 106 errCh := make(chan error) 107 // start the parent driver. initComplete will be closed when the child connected to the parent. 108 logrus.Infof("Starting parent driver") 109 go func() { 110 driverErr := driver.RunParentDriver(initComplete, quit, nil) 111 if driverErr != nil { 112 logrus.WithError(driverErr).Warn("Parent driver exited") 113 } 114 errCh <- driverErr 115 close(errCh) 116 }() 117 opaque := driver.OpaqueForChild() 118 logrus.Infof("opaque=%+v", opaque) 119 opaqueJSON, err := json.Marshal(opaque) 120 if err != nil { 121 return err 122 } 123 childQuitR, childQuitW, err := os.Pipe() 124 if err != nil { 125 return err 126 } 127 defer func() { 128 // stop the child 129 logrus.Info("Stopping child driver") 130 if err := childQuitW.Close(); err != nil { 131 logrus.WithError(err).Warn("Unable to close childQuitW") 132 } 133 }() 134 135 // reexec the child process in the child netns 136 cmd := exec.Command("/proc/self/exe") 137 cmd.Args = []string{ReexecChildKey} 138 cmd.Stdin = childQuitR 139 cmd.Stdout = &logrusWriter{prefix: "child"} 140 cmd.Stderr = cmd.Stdout 141 cmd.Env = append(os.Environ(), reexecChildEnvOpaque+"="+string(opaqueJSON)) 142 childNS, err := ns.GetNS(cfg.NetNSPath) 143 if err != nil { 144 return err 145 } 146 if err := childNS.Do(func(_ ns.NetNS) error { 147 logrus.Infof("Starting child driver in child netns (%q %v)", cmd.Path, cmd.Args) 148 return cmd.Start() 149 }); err != nil { 150 return err 151 } 152 153 childErrCh := make(chan error) 154 go func() { 155 err := cmd.Wait() 156 childErrCh <- err 157 close(childErrCh) 158 }() 159 160 defer func() { 161 if err := unix.Kill(cmd.Process.Pid, unix.SIGTERM); err != nil { 162 logrus.WithError(err).Warn("Kill child process") 163 } 164 }() 165 166 logrus.Info("Waiting for initComplete") 167 // wait for the child to connect to the parent 168 outer: 169 for { 170 select { 171 case <-initComplete: 172 logrus.Infof("initComplete is closed; parent and child established the communication channel") 173 break outer 174 case err := <-childErrCh: 175 if err != nil { 176 return err 177 } 178 case err := <-errCh: 179 if err != nil { 180 return err 181 } 182 } 183 } 184 185 defer func() { 186 logrus.Info("Stopping parent driver") 187 quit <- struct{}{} 188 if err := <-errCh; err != nil { 189 logrus.WithError(err).Warn("Parent driver returned error on exit") 190 } 191 }() 192 193 // let parent expose ports 194 logrus.Infof("Exposing ports %v", cfg.Mappings) 195 if err := exposePorts(driver, cfg.Mappings, cfg.ChildIP); err != nil { 196 return err 197 } 198 199 // we only need to have a socket to reload ports when we run under rootless cni 200 if cfg.RootlessCNI { 201 socketfile := filepath.Join(socketDir, cfg.ContainerID) 202 // make sure to remove the file if it exists to prevent EADDRINUSE 203 _ = os.Remove(socketfile) 204 // workaround to bypass the 108 char socket path limit 205 // open the fd and use the path to the fd as bind argument 206 fd, err := unix.Open(socketDir, unix.O_PATH, 0) 207 if err != nil { 208 return err 209 } 210 socket, err := net.ListenUnix("unixpacket", &net.UnixAddr{Name: fmt.Sprintf("/proc/self/fd/%d/%s", fd, cfg.ContainerID), Net: "unixpacket"}) 211 if err != nil { 212 return err 213 } 214 err = unix.Close(fd) 215 // remove the socket file on exit 216 defer os.Remove(socketfile) 217 if err != nil { 218 logrus.Warnf("Failed to close the socketDir fd: %v", err) 219 } 220 defer socket.Close() 221 go serve(socket, driver) 222 } 223 224 logrus.Info("Ready") 225 226 // https://github.com/containers/podman/issues/11248 227 // Copy /dev/null to stdout and stderr to prevent SIGPIPE errors 228 if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil { 229 unix.Dup2(int(f.Fd()), 1) // nolint:errcheck 230 unix.Dup2(int(f.Fd()), 2) // nolint:errcheck 231 f.Close() 232 } 233 // write and close ReadyFD (convention is same as slirp4netns --ready-fd) 234 if _, err := readyW.Write([]byte("1")); err != nil { 235 return err 236 } 237 if err := readyW.Close(); err != nil { 238 return err 239 } 240 241 // wait for ExitFD to be closed 242 logrus.Info("Waiting for exitfd to be closed") 243 if _, err := ioutil.ReadAll(exitR); err != nil { 244 return err 245 } 246 return nil 247 } 248 249 func serve(listener net.Listener, pm rkport.Manager) { 250 for { 251 conn, err := listener.Accept() 252 if err != nil { 253 // we cannot log this error, stderr is already closed 254 continue 255 } 256 ctx := context.TODO() 257 err = handler(ctx, conn, pm) 258 if err != nil { 259 _, _ = conn.Write([]byte(err.Error())) 260 } else { 261 _, _ = conn.Write([]byte("OK")) 262 } 263 conn.Close() 264 } 265 } 266 267 func handler(ctx context.Context, conn io.Reader, pm rkport.Manager) error { 268 var childIP string 269 dec := json.NewDecoder(conn) 270 err := dec.Decode(&childIP) 271 if err != nil { 272 return errors.Wrap(err, "rootless port failed to decode ports") 273 } 274 portStatus, err := pm.ListPorts(ctx) 275 if err != nil { 276 return errors.Wrap(err, "rootless port failed to list ports") 277 } 278 for _, status := range portStatus { 279 err = pm.RemovePort(ctx, status.ID) 280 if err != nil { 281 return errors.Wrap(err, "rootless port failed to remove port") 282 } 283 } 284 // add the ports with the new child IP 285 for _, status := range portStatus { 286 // set the new child IP 287 status.Spec.ChildIP = childIP 288 _, err = pm.AddPort(ctx, status.Spec) 289 if err != nil { 290 return errors.Wrap(err, "rootless port failed to add port") 291 } 292 } 293 return nil 294 } 295 296 func exposePorts(pm rkport.Manager, portMappings []types.PortMapping, childIP string) error { 297 ctx := context.TODO() 298 for _, port := range portMappings { 299 protocols := strings.Split(port.Protocol, ",") 300 for _, protocol := range protocols { 301 hostIP := port.HostIP 302 if hostIP == "" { 303 hostIP = "0.0.0.0" 304 } 305 for i := uint16(0); i < port.Range; i++ { 306 spec := rkport.Spec{ 307 Proto: protocol, 308 ParentIP: hostIP, 309 ParentPort: int(port.HostPort + i), 310 ChildPort: int(port.ContainerPort + i), 311 ChildIP: childIP, 312 } 313 314 for _, spec = range splitDualStackSpecIfWsl(spec) { 315 if err := validateAndAddPort(ctx, pm, spec); err != nil { 316 return err 317 } 318 } 319 } 320 } 321 } 322 return nil 323 } 324 325 func validateAndAddPort(ctx context.Context, pm rkport.Manager, spec rkport.Spec) error { 326 if err := rkportutil.ValidatePortSpec(spec, nil); err != nil { 327 return err 328 } 329 if _, err := pm.AddPort(ctx, spec); err != nil { 330 return err 331 } 332 333 return nil 334 } 335 336 func child() error { 337 // load the config from the parent 338 var opaque map[string]string 339 if err := json.Unmarshal([]byte(os.Getenv(reexecChildEnvOpaque)), &opaque); err != nil { 340 return err 341 } 342 343 // start the child driver 344 quit := make(chan struct{}) 345 errCh := make(chan error) 346 go func() { 347 d := rkbuiltin.NewChildDriver(os.Stderr) 348 dErr := d.RunChildDriver(opaque, quit) 349 errCh <- dErr 350 }() 351 defer func() { 352 logrus.Info("Stopping child driver") 353 quit <- struct{}{} 354 if err := <-errCh; err != nil { 355 logrus.WithError(err).Warn("Child driver returned error on exit") 356 } 357 }() 358 359 // wait for stdin to be closed 360 if _, err := ioutil.ReadAll(os.Stdin); err != nil { 361 return err 362 } 363 return nil 364 } 365 366 type logrusWriter struct { 367 prefix string 368 } 369 370 func (w *logrusWriter) Write(p []byte) (int, error) { 371 logrus.Infof("%s%s", w.prefix, string(p)) 372 return len(p), nil 373 }