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