Add support for port forwarding
Reusing k8s existing implementation. This could be re-written
to do port-forwarding natively rather than relying on socat/nsenter.
package server
import (
+ "bytes"
+ "fmt"
+ "io"
+ "os/exec"
+ "strings"
+
+ "github.com/Sirupsen/logrus"
+ "github.com/kubernetes-incubator/cri-o/oci"
"golang.org/x/net/context"
pb "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
)
// PortForward prepares a streaming endpoint to forward ports from a PodSandbox.
func (s *Server) PortForward(ctx context.Context, req *pb.PortForwardRequest) (*pb.PortForwardResponse, error) {
- return nil, nil
+ logrus.Debugf("PortForwardRequest %+v", req)
+
+ resp, err := s.GetPortForward(req)
+
+ if err != nil {
+ return nil, fmt.Errorf("unable to prepare portforward endpoint")
+ }
+
+ return resp, nil
+}
+
+func (ss streamService) PortForward(podInfraContainerID string, port int32, stream io.ReadWriteCloser) error {
+ c := ss.runtimeServer.state.containers.Get(podInfraContainerID)
+
+ if err := ss.runtimeServer.runtime.UpdateStatus(c); err != nil {
+ return err
+ }
+
+ cState := ss.runtimeServer.runtime.ContainerStatus(c)
+ if !(cState.Status == oci.ContainerStateRunning || cState.Status == oci.ContainerStateCreated) {
+ return fmt.Errorf("container is not created or running")
+ }
+
+ containerPid := cState.Pid
+ socatPath, lookupErr := exec.LookPath("socat")
+ if lookupErr != nil {
+ return fmt.Errorf("unable to do port forwarding: socat not found")
+ }
+
+ args := []string{"-t", fmt.Sprintf("%d", containerPid), "-n", socatPath, "-", fmt.Sprintf("TCP4:localhost:%d", port)}
+
+ nsenterPath, lookupErr := exec.LookPath("nsenter")
+ if lookupErr != nil {
+ return fmt.Errorf("unable to do port forwarding: nsenter not found")
+ }
+
+ commandString := fmt.Sprintf("%s %s", nsenterPath, strings.Join(args, " "))
+ logrus.Debugf("executing port forwarding command: %s", commandString)
+
+ command := exec.Command(nsenterPath, args...)
+ command.Stdout = stream
+
+ stderr := new(bytes.Buffer)
+ command.Stderr = stderr
+
+ // If we use Stdin, command.Run() won't return until the goroutine that's copying
+ // from stream finishes. Unfortunately, if you have a client like telnet connected
+ // via port forwarding, as long as the user's telnet client is connected to the user's
+ // local listener that port forwarding sets up, the telnet session never exits. This
+ // means that even if socat has finished running, command.Run() won't ever return
+ // (because the client still has the connection and stream open).
+ //
+ // The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe
+ // when the command (socat) exits.
+ inPipe, err := command.StdinPipe()
+ if err != nil {
+ return fmt.Errorf("unable to do port forwarding: error creating stdin pipe: %v", err)
+ }
+ go func() {
+ io.Copy(inPipe, stream)
+ inPipe.Close()
+ }()
+
+ if err := command.Run(); err != nil {
+ return fmt.Errorf("%v: %s", err, stderr.String())
+ }
+
+ return nil
}
