github.com/rootless-containers/rootlesskit/v2@v2.3.4/pkg/port/builtin/parent/parent.go (about) 1 package parent 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "net" 9 "os" 10 "path/filepath" 11 "strconv" 12 "strings" 13 "sync" 14 "syscall" 15 "time" 16 17 "github.com/rootless-containers/rootlesskit/v2/pkg/api" 18 "github.com/rootless-containers/rootlesskit/v2/pkg/port" 19 "github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/msg" 20 "github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/opaque" 21 "github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/tcp" 22 "github.com/rootless-containers/rootlesskit/v2/pkg/port/builtin/parent/udp" 23 "github.com/rootless-containers/rootlesskit/v2/pkg/port/portutil" 24 ) 25 26 // NewDriver for builtin driver. 27 func NewDriver(logWriter io.Writer, stateDir string) (port.ParentDriver, error) { 28 // TODO: consider using socketpair FD instead of socket file 29 socketPath := filepath.Join(stateDir, ".bp.sock") 30 childReadyPipePath := filepath.Join(stateDir, ".bp-ready.pipe") 31 // remove the path just in case the previous rootlesskit instance crashed 32 if err := os.RemoveAll(childReadyPipePath); err != nil { 33 return nil, fmt.Errorf("cannot remove %s: %w", childReadyPipePath, err) 34 } 35 if err := syscall.Mkfifo(childReadyPipePath, 0600); err != nil { 36 return nil, fmt.Errorf("cannot mkfifo %s: %w", childReadyPipePath, err) 37 } 38 d := driver{ 39 logWriter: logWriter, 40 socketPath: socketPath, 41 childReadyPipePath: childReadyPipePath, 42 ports: make(map[int]*port.Status, 0), 43 stoppers: make(map[int]func(context.Context) error, 0), 44 nextID: 1, 45 } 46 return &d, nil 47 } 48 49 type driver struct { 50 logWriter io.Writer 51 socketPath string 52 childReadyPipePath string 53 mu sync.Mutex 54 ports map[int]*port.Status 55 stoppers map[int]func(context.Context) error 56 nextID int 57 } 58 59 func (d *driver) Info(ctx context.Context) (*api.PortDriverInfo, error) { 60 info := &api.PortDriverInfo{ 61 Driver: "builtin", 62 Protos: []string{"tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"}, 63 DisallowLoopbackChildIP: false, 64 } 65 return info, nil 66 } 67 68 func (d *driver) OpaqueForChild() map[string]string { 69 return map[string]string{ 70 opaque.SocketPath: d.socketPath, 71 opaque.ChildReadyPipePath: d.childReadyPipePath, 72 } 73 } 74 75 func (d *driver) RunParentDriver(initComplete chan struct{}, quit <-chan struct{}, _ *port.ChildContext) error { 76 childReadyPipeR, err := os.OpenFile(d.childReadyPipePath, os.O_RDONLY, os.ModeNamedPipe) 77 if err != nil { 78 return err 79 } 80 if _, err = io.ReadAll(childReadyPipeR); err != nil { 81 return err 82 } 83 childReadyPipeR.Close() 84 var dialer net.Dialer 85 conn, err := dialer.Dial("unix", d.socketPath) 86 if err != nil { 87 return err 88 } 89 err = msg.Initiate(conn.(*net.UnixConn)) 90 conn.Close() 91 if err != nil { 92 return err 93 } 94 initComplete <- struct{}{} 95 <-quit 96 return nil 97 } 98 99 func isEPERM(err error) bool { 100 k := "permission denied" 101 // As of Go 1.14, errors.Is(err, syscall.EPERM) does not seem to work for 102 // "listen tcp 0.0.0.0:80: bind: permission denied" error from net.ListenTCP(). 103 return errors.Is(err, syscall.EPERM) || strings.Contains(err.Error(), k) 104 } 105 106 // annotateEPERM annotates origErr for human-readability 107 func annotateEPERM(origErr error, spec port.Spec) error { 108 // Read "net.ipv4.ip_unprivileged_port_start" value (typically 1024) 109 // TODO: what for IPv6? 110 // NOTE: sync.Once should not be used here 111 b, e := os.ReadFile("/proc/sys/net/ipv4/ip_unprivileged_port_start") 112 if e != nil { 113 return origErr 114 } 115 start, e := strconv.Atoi(strings.TrimSpace(string(b))) 116 if e != nil { 117 return origErr 118 } 119 if spec.ParentPort >= start { 120 // origErr is unrelated to ip_unprivileged_port_start 121 return origErr 122 } 123 text := fmt.Sprintf("cannot expose privileged port %d, you can add 'net.ipv4.ip_unprivileged_port_start=%d' to /etc/sysctl.conf (currently %d)", spec.ParentPort, spec.ParentPort, start) 124 if filepath.Base(os.Args[0]) == "rootlesskit" { 125 // NOTE: The following sentence is appended only if Args[0] == "rootlesskit", because it does not apply to Podman (as of Podman v1.9). 126 // Podman launches the parent driver in the child user namespace (but in the parent network namespace), which disables the file capability. 127 text += ", or set CAP_NET_BIND_SERVICE on rootlesskit binary" 128 } 129 text += fmt.Sprintf(", or choose a larger port number (>= %d)", start) 130 return fmt.Errorf(text+": %w", origErr) 131 } 132 133 func (d *driver) AddPort(ctx context.Context, spec port.Spec) (*port.Status, error) { 134 d.mu.Lock() 135 err := portutil.ValidatePortSpec(spec, d.ports) 136 d.mu.Unlock() 137 if err != nil { 138 return nil, err 139 } 140 // NOTE: routineStopCh is close-only channel. Do not send any data. 141 // See commit 4803f18fae1e39d200d98f09e445a97ccd6f5526 `Revert "port/builtin: RemovePort() block until conn is closed"` 142 routineStopCh := make(chan struct{}) 143 routineStoppedCh := make(chan error) 144 routineStop := func(ctx context.Context) error { 145 close(routineStopCh) 146 select { 147 case stoppedResult, stoppedResultOk := <-routineStoppedCh: 148 if stoppedResultOk { 149 return stoppedResult 150 } 151 return errors.New("routineStoppedCh was closed without sending data?") 152 case <-ctx.Done(): 153 return fmt.Errorf("timed out while waiting for routineStoppedCh after closing routineStopCh: %w", err) 154 } 155 } 156 switch spec.Proto { 157 case "tcp", "tcp4", "tcp6": 158 err = tcp.Run(d.socketPath, spec, routineStopCh, routineStoppedCh, d.logWriter) 159 case "udp", "udp4", "udp6": 160 err = udp.Run(d.socketPath, spec, routineStopCh, routineStoppedCh, d.logWriter) 161 default: 162 return nil, fmt.Errorf("unsupported port protocol %s", spec.Proto) 163 } 164 if err != nil { 165 if isEPERM(err) { 166 err = annotateEPERM(err, spec) 167 } 168 return nil, err 169 } 170 d.mu.Lock() 171 id := d.nextID 172 st := port.Status{ 173 ID: id, 174 Spec: spec, 175 } 176 d.ports[id] = &st 177 d.stoppers[id] = routineStop 178 d.nextID++ 179 d.mu.Unlock() 180 return &st, nil 181 } 182 183 func (d *driver) ListPorts(ctx context.Context) ([]port.Status, error) { 184 var ports []port.Status 185 d.mu.Lock() 186 for _, p := range d.ports { 187 ports = append(ports, *p) 188 } 189 d.mu.Unlock() 190 return ports, nil 191 } 192 193 func (d *driver) RemovePort(ctx context.Context, id int) error { 194 d.mu.Lock() 195 defer d.mu.Unlock() 196 stop, ok := d.stoppers[id] 197 if !ok { 198 return fmt.Errorf("unknown id: %d", id) 199 } 200 if _, ok := ctx.Deadline(); !ok { 201 var cancel context.CancelFunc 202 ctx, cancel = context.WithTimeout(ctx, 5*time.Second) 203 defer cancel() 204 } 205 err := stop(ctx) 206 delete(d.stoppers, id) 207 delete(d.ports, id) 208 return err 209 }