k8s.io/client-go@v0.31.1/tools/portforward/tunneling_dialer.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package portforward 18 19 import ( 20 "fmt" 21 "net/http" 22 "net/url" 23 "strings" 24 "time" 25 26 "k8s.io/apimachinery/pkg/util/httpstream" 27 "k8s.io/apimachinery/pkg/util/httpstream/spdy" 28 constants "k8s.io/apimachinery/pkg/util/portforward" 29 restclient "k8s.io/client-go/rest" 30 "k8s.io/client-go/transport/websocket" 31 "k8s.io/klog/v2" 32 ) 33 34 const PingPeriod = 10 * time.Second 35 36 // tunnelingDialer implements "httpstream.Dial" interface 37 type tunnelingDialer struct { 38 url *url.URL 39 transport http.RoundTripper 40 holder websocket.ConnectionHolder 41 } 42 43 // NewTunnelingDialer creates and returns the tunnelingDialer structure which implemements the "httpstream.Dialer" 44 // interface. The dialer can upgrade a websocket request, creating a websocket connection. This function 45 // returns an error if one occurs. 46 func NewSPDYOverWebsocketDialer(url *url.URL, config *restclient.Config) (httpstream.Dialer, error) { 47 transport, holder, err := websocket.RoundTripperFor(config) 48 if err != nil { 49 return nil, err 50 } 51 return &tunnelingDialer{ 52 url: url, 53 transport: transport, 54 holder: holder, 55 }, nil 56 } 57 58 // Dial upgrades to a tunneling streaming connection, returning a SPDY connection 59 // containing a WebSockets connection (which implements "net.Conn"). Also 60 // returns the protocol negotiated, or an error. 61 func (d *tunnelingDialer) Dial(protocols ...string) (httpstream.Connection, string, error) { 62 // There is no passed context, so skip the context when creating request for now. 63 // Websockets requires "GET" method: RFC 6455 Sec. 4.1 (page 17). 64 req, err := http.NewRequest("GET", d.url.String(), nil) 65 if err != nil { 66 return nil, "", err 67 } 68 // Add the spdy tunneling prefix to the requested protocols. The tunneling 69 // handler will know how to negotiate these protocols. 70 tunnelingProtocols := []string{} 71 for _, protocol := range protocols { 72 tunnelingProtocol := constants.WebsocketsSPDYTunnelingPrefix + protocol 73 tunnelingProtocols = append(tunnelingProtocols, tunnelingProtocol) 74 } 75 klog.V(4).Infoln("Before WebSocket Upgrade Connection...") 76 conn, err := websocket.Negotiate(d.transport, d.holder, req, tunnelingProtocols...) 77 if err != nil { 78 return nil, "", err 79 } 80 if conn == nil { 81 return nil, "", fmt.Errorf("negotiated websocket connection is nil") 82 } 83 protocol := conn.Subprotocol() 84 protocol = strings.TrimPrefix(protocol, constants.WebsocketsSPDYTunnelingPrefix) 85 klog.V(4).Infof("negotiated protocol: %s", protocol) 86 87 // Wrap the websocket connection which implements "net.Conn". 88 tConn := NewTunnelingConnection("client", conn) 89 // Create SPDY connection injecting the previously created tunneling connection. 90 spdyConn, err := spdy.NewClientConnectionWithPings(tConn, PingPeriod) 91 92 return spdyConn, protocol, err 93 }