github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/rootlessport/rootlessport_linux.go (about) 1 // +build linux 2 3 // Package rootlessport provides reexec for RootlessKit-based port forwarder. 4 // 5 // init() contains reexec.Register() for ReexecKey . 6 // 7 // The reexec requires Config to be provided via stdin. 8 // 9 // The reexec writes human-readable error message on stdout on error. 10 // 11 // Debug log is printed on stderr. 12 package rootlessport 13 14 import ( 15 "context" 16 "encoding/json" 17 "fmt" 18 "io" 19 "io/ioutil" 20 "os" 21 "os/exec" 22 "os/signal" 23 24 "github.com/containernetworking/plugins/pkg/ns" 25 "github.com/containers/storage/pkg/reexec" 26 "github.com/cri-o/ocicni/pkg/ocicni" 27 "github.com/pkg/errors" 28 rkport "github.com/rootless-containers/rootlesskit/pkg/port" 29 rkbuiltin "github.com/rootless-containers/rootlesskit/pkg/port/builtin" 30 rkportutil "github.com/rootless-containers/rootlesskit/pkg/port/portutil" 31 "github.com/sirupsen/logrus" 32 "golang.org/x/sys/unix" 33 ) 34 35 const ( 36 // ReexecKey is the reexec key for the parent process. 37 ReexecKey = "containers-rootlessport" 38 // reexecChildKey is used internally for the second reexec 39 reexecChildKey = "containers-rootlessport-child" 40 reexecChildEnvOpaque = "_CONTAINERS_ROOTLESSPORT_CHILD_OPAQUE" 41 ) 42 43 // Config needs to be provided to the process via stdin as a JSON string. 44 // stdin needs to be closed after the message has been written. 45 type Config struct { 46 Mappings []ocicni.PortMapping 47 NetNSPath string 48 ExitFD int 49 ReadyFD int 50 TmpDir string 51 } 52 53 func init() { 54 reexec.Register(ReexecKey, func() { 55 if err := parent(); err != nil { 56 fmt.Println(err) 57 os.Exit(1) 58 } 59 }) 60 reexec.Register(reexecChildKey, func() { 61 if err := child(); err != nil { 62 fmt.Println(err) 63 os.Exit(1) 64 } 65 }) 66 67 } 68 69 func loadConfig(r io.Reader) (*Config, io.ReadCloser, io.WriteCloser, error) { 70 stdin, err := ioutil.ReadAll(r) 71 if err != nil { 72 return nil, nil, nil, err 73 } 74 var cfg Config 75 if err := json.Unmarshal(stdin, &cfg); err != nil { 76 return nil, nil, nil, err 77 } 78 if cfg.NetNSPath == "" { 79 return nil, nil, nil, errors.New("missing NetNSPath") 80 } 81 if cfg.ExitFD <= 0 { 82 return nil, nil, nil, errors.New("missing ExitFD") 83 } 84 exitFile := os.NewFile(uintptr(cfg.ExitFD), "exitfile") 85 if exitFile == nil { 86 return nil, nil, nil, errors.New("invalid ExitFD") 87 } 88 if cfg.ReadyFD <= 0 { 89 return nil, nil, nil, errors.New("missing ReadyFD") 90 } 91 readyFile := os.NewFile(uintptr(cfg.ReadyFD), "readyfile") 92 if readyFile == nil { 93 return nil, nil, nil, errors.New("invalid ReadyFD") 94 } 95 return &cfg, exitFile, readyFile, nil 96 } 97 98 func parent() error { 99 // load config from stdin 100 cfg, exitR, readyW, err := loadConfig(os.Stdin) 101 if err != nil { 102 return err 103 } 104 105 exitC := make(chan os.Signal, 1) 106 defer close(exitC) 107 108 go func() { 109 sigC := make(chan os.Signal, 1) 110 signal.Notify(sigC, unix.SIGPIPE) 111 defer func() { 112 signal.Stop(sigC) 113 close(sigC) 114 }() 115 116 select { 117 case s := <-sigC: 118 if s == unix.SIGPIPE { 119 if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil { 120 unix.Dup2(int(f.Fd()), 1) // nolint:errcheck 121 unix.Dup2(int(f.Fd()), 2) // nolint:errcheck 122 f.Close() 123 } 124 } 125 case <-exitC: 126 } 127 }() 128 129 // create the parent driver 130 stateDir, err := ioutil.TempDir(cfg.TmpDir, "rootlessport") 131 if err != nil { 132 return err 133 } 134 defer os.RemoveAll(stateDir) 135 driver, err := rkbuiltin.NewParentDriver(&logrusWriter{prefix: "parent: "}, stateDir) 136 if err != nil { 137 return err 138 } 139 initComplete := make(chan struct{}) 140 quit := make(chan struct{}) 141 errCh := make(chan error) 142 // start the parent driver. initComplete will be closed when the child connected to the parent. 143 logrus.Infof("starting parent driver") 144 go func() { 145 driverErr := driver.RunParentDriver(initComplete, quit, nil) 146 if driverErr != nil { 147 logrus.WithError(driverErr).Warn("parent driver exited") 148 } 149 errCh <- driverErr 150 close(errCh) 151 }() 152 opaque := driver.OpaqueForChild() 153 logrus.Infof("opaque=%+v", opaque) 154 opaqueJSON, err := json.Marshal(opaque) 155 if err != nil { 156 return err 157 } 158 childQuitR, childQuitW, err := os.Pipe() 159 if err != nil { 160 return err 161 } 162 defer func() { 163 // stop the child 164 logrus.Info("stopping child driver") 165 if err := childQuitW.Close(); err != nil { 166 logrus.WithError(err).Warn("unable to close childQuitW") 167 } 168 }() 169 170 // reexec the child process in the child netns 171 cmd := exec.Command("/proc/self/exe") 172 cmd.Args = []string{reexecChildKey} 173 cmd.Stdin = childQuitR 174 cmd.Stdout = &logrusWriter{prefix: "child"} 175 cmd.Stderr = cmd.Stdout 176 cmd.Env = append(os.Environ(), reexecChildEnvOpaque+"="+string(opaqueJSON)) 177 childNS, err := ns.GetNS(cfg.NetNSPath) 178 if err != nil { 179 return err 180 } 181 if err := childNS.Do(func(_ ns.NetNS) error { 182 logrus.Infof("starting child driver in child netns (%q %v)", cmd.Path, cmd.Args) 183 return cmd.Start() 184 }); err != nil { 185 return err 186 } 187 188 childErrCh := make(chan error) 189 go func() { 190 err := cmd.Wait() 191 childErrCh <- err 192 close(childErrCh) 193 }() 194 195 defer func() { 196 if err := unix.Kill(cmd.Process.Pid, unix.SIGTERM); err != nil { 197 logrus.WithError(err).Warn("kill child process") 198 } 199 }() 200 201 logrus.Info("waiting for initComplete") 202 // wait for the child to connect to the parent 203 outer: 204 for { 205 select { 206 case <-initComplete: 207 logrus.Infof("initComplete is closed; parent and child established the communication channel") 208 break outer 209 case err := <-childErrCh: 210 if err != nil { 211 return err 212 } 213 case err := <-errCh: 214 if err != nil { 215 return err 216 } 217 } 218 } 219 220 defer func() { 221 logrus.Info("stopping parent driver") 222 quit <- struct{}{} 223 if err := <-errCh; err != nil { 224 logrus.WithError(err).Warn("parent driver returned error on exit") 225 } 226 }() 227 228 // let parent expose ports 229 logrus.Infof("exposing ports %v", cfg.Mappings) 230 if err := exposePorts(driver, cfg.Mappings); err != nil { 231 return err 232 } 233 234 // write and close ReadyFD (convention is same as slirp4netns --ready-fd) 235 logrus.Info("ready") 236 if _, err := readyW.Write([]byte("1")); err != nil { 237 return err 238 } 239 if err := readyW.Close(); err != nil { 240 return err 241 } 242 243 // wait for ExitFD to be closed 244 logrus.Info("waiting for exitfd to be closed") 245 if _, err := ioutil.ReadAll(exitR); err != nil { 246 return err 247 } 248 return nil 249 } 250 251 func exposePorts(pm rkport.Manager, portMappings []ocicni.PortMapping) error { 252 ctx := context.TODO() 253 for _, i := range portMappings { 254 hostIP := i.HostIP 255 if hostIP == "" { 256 hostIP = "0.0.0.0" 257 } 258 spec := rkport.Spec{ 259 Proto: i.Protocol, 260 ParentIP: hostIP, 261 ParentPort: int(i.HostPort), 262 ChildPort: int(i.ContainerPort), 263 } 264 if err := rkportutil.ValidatePortSpec(spec, nil); err != nil { 265 return err 266 } 267 if _, err := pm.AddPort(ctx, spec); err != nil { 268 return err 269 } 270 } 271 return nil 272 } 273 274 func child() error { 275 // load the config from the parent 276 var opaque map[string]string 277 if err := json.Unmarshal([]byte(os.Getenv(reexecChildEnvOpaque)), &opaque); err != nil { 278 return err 279 } 280 281 // start the child driver 282 quit := make(chan struct{}) 283 errCh := make(chan error) 284 go func() { 285 d := rkbuiltin.NewChildDriver(os.Stderr) 286 dErr := d.RunChildDriver(opaque, quit) 287 errCh <- dErr 288 }() 289 defer func() { 290 logrus.Info("stopping child driver") 291 quit <- struct{}{} 292 if err := <-errCh; err != nil { 293 logrus.WithError(err).Warn("child driver returned error on exit") 294 } 295 }() 296 297 // wait for stdin to be closed 298 if _, err := ioutil.ReadAll(os.Stdin); err != nil { 299 return err 300 } 301 return nil 302 } 303 304 type logrusWriter struct { 305 prefix string 306 } 307 308 func (w *logrusWriter) Write(p []byte) (int, error) { 309 logrus.Infof("%s%s", w.prefix, string(p)) 310 return len(p), nil 311 }