// ServePortForward handles a port forwarding request. A single request is // kept alive as long as the client is still alive and the connection has not // been timed out due to idleness. This function handles multiple forwarded // connections; i.e., multiple `curl http://localhost:8888/` requests will be // handled by a single invocation of ServePortForward. func ServePortForward(w http.ResponseWriter, req *http.Request, portForwarder PortForwarder, podName string, uid types.UID, idleTimeout time.Duration, streamCreationTimeout time.Duration) { streamChan := make(chan httpstream.Stream, 1) glog.V(5).Infof("Upgrading port forward response") upgrader := spdy.NewResponseUpgrader() conn := upgrader.UpgradeResponse(w, req, portForwardStreamReceived(streamChan)) if conn == nil { return } defer conn.Close() glog.V(5).Infof("(conn=%p) setting port forwarding streaming connection idle timeout to %v", conn, idleTimeout) conn.SetIdleTimeout(idleTimeout) h := &portForwardStreamHandler{ conn: conn, streamChan: streamChan, streamPairs: make(map[string]*portForwardStreamPair), streamCreationTimeout: streamCreationTimeout, pod: podName, uid: uid, forwarder: portForwarder, } h.run() }
// ServePortForward handles a port forwarding request. A single request is // kept alive as long as the client is still alive and the connection has not // been timed out due to idleness. This function handles multiple forwarded // connections; i.e., multiple `curl http://localhost:8888/` requests will be // handled by a single invocation of ServePortForward. func ServePortForward(w http.ResponseWriter, req *http.Request, portForwarder PortForwarder, podName string, uid types.UID, idleTimeout time.Duration, streamCreationTimeout time.Duration) { supportedPortForwardProtocols := []string{portforward.PortForwardProtocolV1Name} _, err := httpstream.Handshake(req, w, supportedPortForwardProtocols, portforward.PortForwardProtocolV1Name) // negotiated protocol isn't currently used server side, but could be in the future if err != nil { // Handshake writes the error to the client util.HandleError(err) return } streamChan := make(chan httpstream.Stream, 1) glog.V(5).Infof("Upgrading port forward response") upgrader := spdy.NewResponseUpgrader() conn := upgrader.UpgradeResponse(w, req, portForwardStreamReceived(streamChan)) if conn == nil { return } defer conn.Close() glog.V(5).Infof("(conn=%p) setting port forwarding streaming connection idle timeout to %v", conn, idleTimeout) conn.SetIdleTimeout(idleTimeout) h := &portForwardStreamHandler{ conn: conn, streamChan: streamChan, streamPairs: make(map[string]*portForwardStreamPair), streamCreationTimeout: streamCreationTimeout, pod: podName, uid: uid, forwarder: portForwarder, } h.run() }
func createStreams(req *http.Request, w http.ResponseWriter, supportedStreamProtocols []string, idleTimeout, streamCreationTimeout time.Duration) (*context, bool) { opts, err := newOptions(req) if err != nil { runtime.HandleError(err) w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, err.Error()) return nil, false } if wsstream.IsWebSocketRequest(req) { return createWebSocketStreams(req, w, opts, idleTimeout) } protocol, err := httpstream.Handshake(req, w, supportedStreamProtocols) if err != nil { w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, err.Error()) return nil, false } streamCh := make(chan streamAndReply) upgrader := spdy.NewResponseUpgrader() conn := upgrader.UpgradeResponse(w, req, func(stream httpstream.Stream, replySent <-chan struct{}) error { streamCh <- streamAndReply{Stream: stream, replySent: replySent} return nil }) // from this point on, we can no longer call methods on response if conn == nil { // The upgrader is responsible for notifying the client of any errors that // occurred during upgrading. All we can do is return here at this point // if we weren't successful in upgrading. return nil, false } conn.SetIdleTimeout(idleTimeout) var handler protocolHandler switch protocol { case StreamProtocolV2Name: handler = &v2ProtocolHandler{} case "": glog.V(4).Infof("Client did not request protocol negotiaion. Falling back to %q", StreamProtocolV1Name) fallthrough case StreamProtocolV1Name: handler = &v1ProtocolHandler{} } expired := time.NewTimer(streamCreationTimeout) ctx, err := handler.waitForStreams(streamCh, opts.expectedStreams, expired.C) if err != nil { runtime.HandleError(err) return nil, false } ctx.conn = conn ctx.tty = opts.tty return ctx, true }
func fakeExecServer(t *testing.T, i int, stdinData, stdoutData, stderrData, errorData string, tty bool) http.HandlerFunc { // error + stdin + stdout expectedStreams := 3 if !tty { // stderr expectedStreams++ } return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { streamCh := make(chan httpstream.Stream) upgrader := spdy.NewResponseUpgrader() conn := upgrader.UpgradeResponse(w, req, func(stream httpstream.Stream) error { streamCh <- stream return nil }) // from this point on, we can no longer call methods on w if conn == nil { // The upgrader is responsible for notifying the client of any errors that // occurred during upgrading. All we can do is return here at this point // if we weren't successful in upgrading. return } defer conn.Close() var errorStream, stdinStream, stdoutStream, stderrStream httpstream.Stream receivedStreams := 0 WaitForStreams: for { select { case stream := <-streamCh: streamType := stream.Headers().Get(api.StreamType) switch streamType { case api.StreamTypeError: errorStream = stream receivedStreams++ case api.StreamTypeStdin: stdinStream = stream stdinStream.Close() receivedStreams++ case api.StreamTypeStdout: stdoutStream = stream receivedStreams++ case api.StreamTypeStderr: stderrStream = stream receivedStreams++ default: t.Errorf("%d: unexpected stream type: %q", i, streamType) } defer stream.Reset() if receivedStreams == expectedStreams { break WaitForStreams } } } if len(errorData) > 0 { fmt.Fprint(errorStream, errorData) errorStream.Close() } if len(stdoutData) > 0 { fmt.Fprint(stdoutStream, stdoutData) stdoutStream.Close() } if len(stderrData) > 0 { fmt.Fprint(stderrStream, stderrData) stderrStream.Close() } if len(stdinData) > 0 { data, err := ioutil.ReadAll(stdinStream) if err != nil { t.Errorf("%d: error reading stdin stream: %v", i, err) } if e, a := stdinData, string(data); e != a { t.Errorf("%d: stdin: expected %q, got %q", i, e, a) } } }) }
func fakeExecServer(t *testing.T, i int, stdinData, stdoutData, stderrData, errorData string, tty bool, messageCount int) http.HandlerFunc { // error + stdin + stdout expectedStreams := 3 if !tty { // stderr expectedStreams++ } return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { protocol, err := httpstream.Handshake(req, w, []string{StreamProtocolV2Name}, StreamProtocolV1Name) if err != nil { t.Fatal(err) } if protocol != StreamProtocolV2Name { t.Fatalf("unexpected protocol: %s", protocol) } streamCh := make(chan streamAndReply) upgrader := spdy.NewResponseUpgrader() conn := upgrader.UpgradeResponse(w, req, func(stream httpstream.Stream, replySent <-chan struct{}) error { streamCh <- streamAndReply{Stream: stream, replySent: replySent} return nil }) // from this point on, we can no longer call methods on w if conn == nil { // The upgrader is responsible for notifying the client of any errors that // occurred during upgrading. All we can do is return here at this point // if we weren't successful in upgrading. return } defer conn.Close() var errorStream, stdinStream, stdoutStream, stderrStream httpstream.Stream receivedStreams := 0 replyChan := make(chan struct{}) stop := make(chan struct{}) defer close(stop) WaitForStreams: for { select { case stream := <-streamCh: streamType := stream.Headers().Get(api.StreamType) switch streamType { case api.StreamTypeError: errorStream = stream go waitStreamReply(stream.replySent, replyChan, stop) case api.StreamTypeStdin: stdinStream = stream go waitStreamReply(stream.replySent, replyChan, stop) case api.StreamTypeStdout: stdoutStream = stream go waitStreamReply(stream.replySent, replyChan, stop) case api.StreamTypeStderr: stderrStream = stream go waitStreamReply(stream.replySent, replyChan, stop) default: t.Errorf("%d: unexpected stream type: %q", i, streamType) } if receivedStreams == expectedStreams { break WaitForStreams } case <-replyChan: receivedStreams++ if receivedStreams == expectedStreams { break WaitForStreams } } } if len(errorData) > 0 { n, err := fmt.Fprint(errorStream, errorData) if err != nil { t.Errorf("%d: error writing to errorStream: %v", i, err) } if e, a := len(errorData), n; e != a { t.Errorf("%d: expected to write %d bytes to errorStream, but only wrote %d", i, e, a) } errorStream.Close() } if len(stdoutData) > 0 { for j := 0; j < messageCount; j++ { n, err := fmt.Fprint(stdoutStream, stdoutData) if err != nil { t.Errorf("%d: error writing to stdoutStream: %v", i, err) } if e, a := len(stdoutData), n; e != a { t.Errorf("%d: expected to write %d bytes to stdoutStream, but only wrote %d", i, e, a) } } stdoutStream.Close() } if len(stderrData) > 0 { for j := 0; j < messageCount; j++ { n, err := fmt.Fprint(stderrStream, stderrData) if err != nil { t.Errorf("%d: error writing to stderrStream: %v", i, err) } if e, a := len(stderrData), n; e != a { t.Errorf("%d: expected to write %d bytes to stderrStream, but only wrote %d", i, e, a) } } stderrStream.Close() } if len(stdinData) > 0 { data := make([]byte, len(stdinData)) for j := 0; j < messageCount; j++ { n, err := io.ReadFull(stdinStream, data) if err != nil { t.Errorf("%d: error reading stdin stream: %v", i, err) } if e, a := len(stdinData), n; e != a { t.Errorf("%d: expected to read %d bytes from stdinStream, but only read %d", i, e, a) } if e, a := stdinData, string(data); e != a { t.Errorf("%d: stdin: expected %q, got %q", i, e, a) } } stdinStream.Close() } }) }
func (s *Server) createStreams(request *restful.Request, response *restful.Response) (io.Reader, io.WriteCloser, io.WriteCloser, io.WriteCloser, Closer, bool, bool) { tty := request.QueryParameter(api.ExecTTYParam) == "1" stdin := request.QueryParameter(api.ExecStdinParam) == "1" stdout := request.QueryParameter(api.ExecStdoutParam) == "1" stderr := request.QueryParameter(api.ExecStderrParam) == "1" if tty && stderr { // TODO: make this an error before we reach this method glog.V(4).Infof("Access to exec with tty and stderr is not supported, bypassing stderr") stderr = false } // count the streams client asked for, starting with 1 expectedStreams := 1 if stdin { expectedStreams++ } if stdout { expectedStreams++ } if stderr { expectedStreams++ } if expectedStreams == 1 { response.WriteError(http.StatusBadRequest, fmt.Errorf("you must specify at least 1 of stdin, stdout, stderr")) return nil, nil, nil, nil, nil, false, false } if wsstream.IsWebSocketRequest(request.Request) { // open the requested channels, and always open the error channel channels := append(standardShellChannels(stdin, stdout, stderr), wsstream.WriteChannel) conn := wsstream.NewConn(channels...) conn.SetIdleTimeout(s.host.StreamingConnectionIdleTimeout()) streams, err := conn.Open(httplog.Unlogged(response.ResponseWriter), request.Request) if err != nil { glog.Errorf("Unable to upgrade websocket connection: %v", err) return nil, nil, nil, nil, nil, false, false } // Send an empty message to the lowest writable channel to notify the client the connection is established // TODO: make generic to SDPY and WebSockets and do it outside of this method? switch { case stdout: streams[1].Write([]byte{}) case stderr: streams[2].Write([]byte{}) default: streams[3].Write([]byte{}) } return streams[0], streams[1], streams[2], streams[3], conn, tty, true } supportedStreamProtocols := []string{remotecommand.StreamProtocolV2Name, remotecommand.StreamProtocolV1Name} _, err := httpstream.Handshake(request.Request, response.ResponseWriter, supportedStreamProtocols, remotecommand.StreamProtocolV1Name) // negotiated protocol isn't used server side at the moment, but could be in the future if err != nil { return nil, nil, nil, nil, nil, false, false } streamCh := make(chan httpstream.Stream) upgrader := spdy.NewResponseUpgrader() conn := upgrader.UpgradeResponse(response.ResponseWriter, request.Request, func(stream httpstream.Stream) error { streamCh <- stream return nil }) // from this point on, we can no longer call methods on response if conn == nil { // The upgrader is responsible for notifying the client of any errors that // occurred during upgrading. All we can do is return here at this point // if we weren't successful in upgrading. return nil, nil, nil, nil, nil, false, false } conn.SetIdleTimeout(s.host.StreamingConnectionIdleTimeout()) // TODO make it configurable? expired := time.NewTimer(defaultStreamCreationTimeout) var errorStream, stdinStream, stdoutStream, stderrStream httpstream.Stream receivedStreams := 0 WaitForStreams: for { select { case stream := <-streamCh: streamType := stream.Headers().Get(api.StreamType) switch streamType { case api.StreamTypeError: errorStream = stream receivedStreams++ case api.StreamTypeStdin: stdinStream = stream receivedStreams++ case api.StreamTypeStdout: stdoutStream = stream receivedStreams++ case api.StreamTypeStderr: stderrStream = stream receivedStreams++ default: glog.Errorf("Unexpected stream type: '%s'", streamType) } if receivedStreams == expectedStreams { break WaitForStreams } case <-expired.C: // TODO find a way to return the error to the user. Maybe use a separate // stream to report errors? glog.Error("Timed out waiting for client to create streams") return nil, nil, nil, nil, nil, false, false } } return stdinStream, stdoutStream, stderrStream, errorStream, conn, tty, true }
func (s *Server) handlePortForward(w http.ResponseWriter, req *http.Request) { u, err := url.ParseRequestURI(req.RequestURI) if err != nil { s.error(w, err) return } podNamespace, podID, uid, err := parsePodCoordinates(u.Path) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } pod, ok := s.host.GetPodByName(podNamespace, podID) if !ok { http.Error(w, "Pod does not exist", http.StatusNotFound) return } streamChan := make(chan httpstream.Stream, 1) upgrader := spdy.NewResponseUpgrader() conn := upgrader.UpgradeResponse(w, req, func(stream httpstream.Stream) error { portString := stream.Headers().Get(api.PortHeader) port, err := strconv.ParseUint(portString, 10, 16) if err != nil { return fmt.Errorf("Unable to parse '%s' as a port: %v", portString, err) } if port < 1 { return fmt.Errorf("Port '%d' must be greater than 0", port) } streamChan <- stream return nil }) if conn == nil { return } defer conn.Close() conn.SetIdleTimeout(s.host.StreamingConnectionIdleTimeout()) var dataStreamLock sync.Mutex dataStreamChans := make(map[string]chan httpstream.Stream) Loop: for { select { case <-conn.CloseChan(): break Loop case stream := <-streamChan: streamType := stream.Headers().Get(api.StreamType) port := stream.Headers().Get(api.PortHeader) dataStreamLock.Lock() switch streamType { case "error": ch := make(chan httpstream.Stream) dataStreamChans[port] = ch go waitForPortForwardDataStreamAndRun(kubecontainer.GetPodFullName(pod), uid, stream, ch, s.host) case "data": ch, ok := dataStreamChans[port] if ok { ch <- stream delete(dataStreamChans, port) } else { glog.Errorf("Unable to locate data stream channel for port %s", port) } default: glog.Errorf("streamType header must be 'error' or 'data', got: '%s'", streamType) stream.Reset() } dataStreamLock.Unlock() } } }
func (s *Server) createStreams(w http.ResponseWriter, req *http.Request) (io.Reader, io.WriteCloser, io.WriteCloser, io.WriteCloser, httpstream.Connection, bool, bool) { req.ParseForm() // start at 1 for error stream expectedStreams := 1 if req.FormValue(api.ExecStdinParam) == "1" { expectedStreams++ } if req.FormValue(api.ExecStdoutParam) == "1" { expectedStreams++ } tty := req.FormValue(api.ExecTTYParam) == "1" if !tty && req.FormValue(api.ExecStderrParam) == "1" { expectedStreams++ } if expectedStreams == 1 { http.Error(w, "You must specify at least 1 of stdin, stdout, stderr", http.StatusBadRequest) return nil, nil, nil, nil, nil, false, false } streamCh := make(chan httpstream.Stream) upgrader := spdy.NewResponseUpgrader() conn := upgrader.UpgradeResponse(w, req, func(stream httpstream.Stream) error { streamCh <- stream return nil }) // from this point on, we can no longer call methods on w if conn == nil { // The upgrader is responsible for notifying the client of any errors that // occurred during upgrading. All we can do is return here at this point // if we weren't successful in upgrading. return nil, nil, nil, nil, nil, false, false } conn.SetIdleTimeout(s.host.StreamingConnectionIdleTimeout()) // TODO make it configurable? expired := time.NewTimer(streamCreationTimeout) var errorStream, stdinStream, stdoutStream, stderrStream httpstream.Stream receivedStreams := 0 WaitForStreams: for { select { case stream := <-streamCh: streamType := stream.Headers().Get(api.StreamType) switch streamType { case api.StreamTypeError: errorStream = stream receivedStreams++ case api.StreamTypeStdin: stdinStream = stream receivedStreams++ case api.StreamTypeStdout: stdoutStream = stream receivedStreams++ case api.StreamTypeStderr: stderrStream = stream receivedStreams++ default: glog.Errorf("Unexpected stream type: '%s'", streamType) } if receivedStreams == expectedStreams { break WaitForStreams } case <-expired.C: // TODO find a way to return the error to the user. Maybe use a separate // stream to report errors? glog.Error("Timed out waiting for client to create streams") return nil, nil, nil, nil, nil, false, false } } return stdinStream, stdoutStream, stderrStream, errorStream, conn, tty, true }
func (s *Server) createStreams(request *restful.Request, response *restful.Response) (io.Reader, io.WriteCloser, io.WriteCloser, io.WriteCloser, httpstream.Connection, bool, bool) { // start at 1 for error stream expectedStreams := 1 if request.QueryParameter(api.ExecStdinParam) == "1" { expectedStreams++ } if request.QueryParameter(api.ExecStdoutParam) == "1" { expectedStreams++ } tty := request.QueryParameter(api.ExecTTYParam) == "1" if !tty && request.QueryParameter(api.ExecStderrParam) == "1" { expectedStreams++ } if expectedStreams == 1 { response.WriteError(http.StatusBadRequest, fmt.Errorf("you must specify at least 1 of stdin, stdout, stderr")) return nil, nil, nil, nil, nil, false, false } supportedStreamProtocols := []string{remotecommand.StreamProtocolV2Name, remotecommand.StreamProtocolV1Name} _, err := httpstream.Handshake(request.Request, response.ResponseWriter, supportedStreamProtocols, remotecommand.StreamProtocolV1Name) // negotiated protocol isn't used server side at the moment, but could be in the future if err != nil { return nil, nil, nil, nil, nil, false, false } streamCh := make(chan httpstream.Stream) upgrader := spdy.NewResponseUpgrader() conn := upgrader.UpgradeResponse(response.ResponseWriter, request.Request, func(stream httpstream.Stream) error { streamCh <- stream return nil }) // from this point on, we can no longer call methods on response if conn == nil { // The upgrader is responsible for notifying the client of any errors that // occurred during upgrading. All we can do is return here at this point // if we weren't successful in upgrading. return nil, nil, nil, nil, nil, false, false } conn.SetIdleTimeout(s.host.StreamingConnectionIdleTimeout()) // TODO make it configurable? expired := time.NewTimer(defaultStreamCreationTimeout) var errorStream, stdinStream, stdoutStream, stderrStream httpstream.Stream receivedStreams := 0 WaitForStreams: for { select { case stream := <-streamCh: streamType := stream.Headers().Get(api.StreamType) switch streamType { case api.StreamTypeError: errorStream = stream receivedStreams++ case api.StreamTypeStdin: stdinStream = stream receivedStreams++ case api.StreamTypeStdout: stdoutStream = stream receivedStreams++ case api.StreamTypeStderr: stderrStream = stream receivedStreams++ default: glog.Errorf("Unexpected stream type: '%s'", streamType) } if receivedStreams == expectedStreams { break WaitForStreams } case <-expired.C: // TODO find a way to return the error to the user. Maybe use a separate // stream to report errors? glog.Error("Timed out waiting for client to create streams") return nil, nil, nil, nil, nil, false, false } } return stdinStream, stdoutStream, stderrStream, errorStream, conn, tty, true }