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