github.com/LazyboyChen7/engine@v17.12.1-ce-rc2+incompatible/client/hijack.go (about) 1 package client 2 3 import ( 4 "bufio" 5 "crypto/tls" 6 "fmt" 7 "net" 8 "net/http" 9 "net/http/httputil" 10 "net/url" 11 "strings" 12 "time" 13 14 "github.com/docker/docker/api/types" 15 "github.com/docker/go-connections/sockets" 16 "github.com/pkg/errors" 17 "golang.org/x/net/context" 18 ) 19 20 // tlsClientCon holds tls information and a dialed connection. 21 type tlsClientCon struct { 22 *tls.Conn 23 rawConn net.Conn 24 } 25 26 func (c *tlsClientCon) CloseWrite() error { 27 // Go standard tls.Conn doesn't provide the CloseWrite() method so we do it 28 // on its underlying connection. 29 if conn, ok := c.rawConn.(types.CloseWriter); ok { 30 return conn.CloseWrite() 31 } 32 return nil 33 } 34 35 // postHijacked sends a POST request and hijacks the connection. 36 func (cli *Client) postHijacked(ctx context.Context, path string, query url.Values, body interface{}, headers map[string][]string) (types.HijackedResponse, error) { 37 bodyEncoded, err := encodeData(body) 38 if err != nil { 39 return types.HijackedResponse{}, err 40 } 41 42 apiPath := cli.getAPIPath(path, query) 43 req, err := http.NewRequest("POST", apiPath, bodyEncoded) 44 if err != nil { 45 return types.HijackedResponse{}, err 46 } 47 req = cli.addHeaders(req, headers) 48 49 conn, err := cli.setupHijackConn(req, "tcp") 50 if err != nil { 51 return types.HijackedResponse{}, err 52 } 53 54 return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err 55 } 56 57 func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) { 58 return tlsDialWithDialer(new(net.Dialer), network, addr, config) 59 } 60 61 // We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in 62 // order to return our custom tlsClientCon struct which holds both the tls.Conn 63 // object _and_ its underlying raw connection. The rationale for this is that 64 // we need to be able to close the write end of the connection when attaching, 65 // which tls.Conn does not provide. 66 func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) { 67 // We want the Timeout and Deadline values from dialer to cover the 68 // whole process: TCP connection and TLS handshake. This means that we 69 // also need to start our own timers now. 70 timeout := dialer.Timeout 71 72 if !dialer.Deadline.IsZero() { 73 deadlineTimeout := time.Until(dialer.Deadline) 74 if timeout == 0 || deadlineTimeout < timeout { 75 timeout = deadlineTimeout 76 } 77 } 78 79 var errChannel chan error 80 81 if timeout != 0 { 82 errChannel = make(chan error, 2) 83 time.AfterFunc(timeout, func() { 84 errChannel <- errors.New("") 85 }) 86 } 87 88 proxyDialer, err := sockets.DialerFromEnvironment(dialer) 89 if err != nil { 90 return nil, err 91 } 92 93 rawConn, err := proxyDialer.Dial(network, addr) 94 if err != nil { 95 return nil, err 96 } 97 // When we set up a TCP connection for hijack, there could be long periods 98 // of inactivity (a long running command with no output) that in certain 99 // network setups may cause ECONNTIMEOUT, leaving the client in an unknown 100 // state. Setting TCP KeepAlive on the socket connection will prohibit 101 // ECONNTIMEOUT unless the socket connection truly is broken 102 if tcpConn, ok := rawConn.(*net.TCPConn); ok { 103 tcpConn.SetKeepAlive(true) 104 tcpConn.SetKeepAlivePeriod(30 * time.Second) 105 } 106 107 colonPos := strings.LastIndex(addr, ":") 108 if colonPos == -1 { 109 colonPos = len(addr) 110 } 111 hostname := addr[:colonPos] 112 113 // If no ServerName is set, infer the ServerName 114 // from the hostname we're connecting to. 115 if config.ServerName == "" { 116 // Make a copy to avoid polluting argument or default. 117 config = tlsConfigClone(config) 118 config.ServerName = hostname 119 } 120 121 conn := tls.Client(rawConn, config) 122 123 if timeout == 0 { 124 err = conn.Handshake() 125 } else { 126 go func() { 127 errChannel <- conn.Handshake() 128 }() 129 130 err = <-errChannel 131 } 132 133 if err != nil { 134 rawConn.Close() 135 return nil, err 136 } 137 138 // This is Docker difference with standard's crypto/tls package: returned a 139 // wrapper which holds both the TLS and raw connections. 140 return &tlsClientCon{conn, rawConn}, nil 141 } 142 143 func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) { 144 if tlsConfig != nil && proto != "unix" && proto != "npipe" { 145 // Notice this isn't Go standard's tls.Dial function 146 return tlsDial(proto, addr, tlsConfig) 147 } 148 if proto == "npipe" { 149 return sockets.DialPipe(addr, 32*time.Second) 150 } 151 return net.Dial(proto, addr) 152 } 153 154 func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, error) { 155 req.Host = cli.addr 156 req.Header.Set("Connection", "Upgrade") 157 req.Header.Set("Upgrade", proto) 158 159 conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport)) 160 if err != nil { 161 return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") 162 } 163 164 // When we set up a TCP connection for hijack, there could be long periods 165 // of inactivity (a long running command with no output) that in certain 166 // network setups may cause ECONNTIMEOUT, leaving the client in an unknown 167 // state. Setting TCP KeepAlive on the socket connection will prohibit 168 // ECONNTIMEOUT unless the socket connection truly is broken 169 if tcpConn, ok := conn.(*net.TCPConn); ok { 170 tcpConn.SetKeepAlive(true) 171 tcpConn.SetKeepAlivePeriod(30 * time.Second) 172 } 173 174 clientconn := httputil.NewClientConn(conn, nil) 175 defer clientconn.Close() 176 177 // Server hijacks the connection, error 'connection closed' expected 178 resp, err := clientconn.Do(req) 179 if err != httputil.ErrPersistEOF { 180 if err != nil { 181 return nil, err 182 } 183 if resp.StatusCode != http.StatusSwitchingProtocols { 184 resp.Body.Close() 185 return nil, fmt.Errorf("unable to upgrade to %s, received %d", proto, resp.StatusCode) 186 } 187 } 188 189 c, br := clientconn.Hijack() 190 if br.Buffered() > 0 { 191 // If there is buffered content, wrap the connection 192 c = &hijackedConn{c, br} 193 } else { 194 br.Reset(nil) 195 } 196 197 return c, nil 198 } 199 200 type hijackedConn struct { 201 net.Conn 202 r *bufio.Reader 203 } 204 205 func (c *hijackedConn) Read(b []byte) (int, error) { 206 return c.r.Read(b) 207 }