github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/client/hijack.go (about) 1 package client 2 3 import ( 4 "crypto/tls" 5 "errors" 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/docker/pkg/tlsconfig" 16 "github.com/docker/go-connections/sockets" 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 req.Host = cli.addr 50 req.Header.Set("Connection", "Upgrade") 51 req.Header.Set("Upgrade", "tcp") 52 53 conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport)) 54 if err != nil { 55 if strings.Contains(err.Error(), "connection refused") { 56 return types.HijackedResponse{}, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") 57 } 58 return types.HijackedResponse{}, err 59 } 60 61 // When we set up a TCP connection for hijack, there could be long periods 62 // of inactivity (a long running command with no output) that in certain 63 // network setups may cause ECONNTIMEOUT, leaving the client in an unknown 64 // state. Setting TCP KeepAlive on the socket connection will prohibit 65 // ECONNTIMEOUT unless the socket connection truly is broken 66 if tcpConn, ok := conn.(*net.TCPConn); ok { 67 tcpConn.SetKeepAlive(true) 68 tcpConn.SetKeepAlivePeriod(30 * time.Second) 69 } 70 71 clientconn := httputil.NewClientConn(conn, nil) 72 defer clientconn.Close() 73 74 // Server hijacks the connection, error 'connection closed' expected 75 _, err = clientconn.Do(req) 76 77 rwc, br := clientconn.Hijack() 78 79 return types.HijackedResponse{Conn: rwc, Reader: br}, err 80 } 81 82 func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) { 83 return tlsDialWithDialer(new(net.Dialer), network, addr, config) 84 } 85 86 // We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in 87 // order to return our custom tlsClientCon struct which holds both the tls.Conn 88 // object _and_ its underlying raw connection. The rationale for this is that 89 // we need to be able to close the write end of the connection when attaching, 90 // which tls.Conn does not provide. 91 func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) { 92 // We want the Timeout and Deadline values from dialer to cover the 93 // whole process: TCP connection and TLS handshake. This means that we 94 // also need to start our own timers now. 95 timeout := dialer.Timeout 96 97 if !dialer.Deadline.IsZero() { 98 deadlineTimeout := dialer.Deadline.Sub(time.Now()) 99 if timeout == 0 || deadlineTimeout < timeout { 100 timeout = deadlineTimeout 101 } 102 } 103 104 var errChannel chan error 105 106 if timeout != 0 { 107 errChannel = make(chan error, 2) 108 time.AfterFunc(timeout, func() { 109 errChannel <- errors.New("") 110 }) 111 } 112 113 proxyDialer, err := sockets.DialerFromEnvironment(dialer) 114 if err != nil { 115 return nil, err 116 } 117 118 rawConn, err := proxyDialer.Dial(network, addr) 119 if err != nil { 120 return nil, err 121 } 122 // When we set up a TCP connection for hijack, there could be long periods 123 // of inactivity (a long running command with no output) that in certain 124 // network setups may cause ECONNTIMEOUT, leaving the client in an unknown 125 // state. Setting TCP KeepAlive on the socket connection will prohibit 126 // ECONNTIMEOUT unless the socket connection truly is broken 127 if tcpConn, ok := rawConn.(*net.TCPConn); ok { 128 tcpConn.SetKeepAlive(true) 129 tcpConn.SetKeepAlivePeriod(30 * time.Second) 130 } 131 132 colonPos := strings.LastIndex(addr, ":") 133 if colonPos == -1 { 134 colonPos = len(addr) 135 } 136 hostname := addr[:colonPos] 137 138 // If no ServerName is set, infer the ServerName 139 // from the hostname we're connecting to. 140 if config.ServerName == "" { 141 // Make a copy to avoid polluting argument or default. 142 config = tlsconfig.Clone(config) 143 config.ServerName = hostname 144 } 145 146 conn := tls.Client(rawConn, config) 147 148 if timeout == 0 { 149 err = conn.Handshake() 150 } else { 151 go func() { 152 errChannel <- conn.Handshake() 153 }() 154 155 err = <-errChannel 156 } 157 158 if err != nil { 159 rawConn.Close() 160 return nil, err 161 } 162 163 // This is Docker difference with standard's crypto/tls package: returned a 164 // wrapper which holds both the TLS and raw connections. 165 return &tlsClientCon{conn, rawConn}, nil 166 } 167 168 func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) { 169 if tlsConfig != nil && proto != "unix" && proto != "npipe" { 170 // Notice this isn't Go standard's tls.Dial function 171 return tlsDial(proto, addr, tlsConfig) 172 } 173 if proto == "npipe" { 174 return sockets.DialPipe(addr, 32*time.Second) 175 } 176 return net.Dial(proto, addr) 177 }