|
@@ -1,11 +1,9 @@
|
|
|
package client
|
|
|
|
|
|
import (
|
|
|
- "bytes"
|
|
|
+ "bufio"
|
|
|
"crypto/tls"
|
|
|
- "errors"
|
|
|
"fmt"
|
|
|
- "io/ioutil"
|
|
|
"net"
|
|
|
"net/http"
|
|
|
"net/http/httputil"
|
|
@@ -16,6 +14,7 @@ import (
|
|
|
"github.com/docker/docker/api/types"
|
|
|
"github.com/docker/docker/pkg/tlsconfig"
|
|
|
"github.com/docker/go-connections/sockets"
|
|
|
+ "github.com/pkg/errors"
|
|
|
"golang.org/x/net/context"
|
|
|
)
|
|
|
|
|
@@ -48,49 +47,12 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu
|
|
|
}
|
|
|
req = cli.addHeaders(req, headers)
|
|
|
|
|
|
- req.Host = cli.addr
|
|
|
- req.Header.Set("Connection", "Upgrade")
|
|
|
- req.Header.Set("Upgrade", "tcp")
|
|
|
-
|
|
|
- conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
|
|
|
+ conn, err := cli.setupHijackConn(req, "tcp")
|
|
|
if err != nil {
|
|
|
- if strings.Contains(err.Error(), "connection refused") {
|
|
|
- return types.HijackedResponse{}, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
|
|
- }
|
|
|
return types.HijackedResponse{}, err
|
|
|
}
|
|
|
|
|
|
- // When we set up a TCP connection for hijack, there could be long periods
|
|
|
- // of inactivity (a long running command with no output) that in certain
|
|
|
- // network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
|
|
- // state. Setting TCP KeepAlive on the socket connection will prohibit
|
|
|
- // ECONNTIMEOUT unless the socket connection truly is broken
|
|
|
- if tcpConn, ok := conn.(*net.TCPConn); ok {
|
|
|
- tcpConn.SetKeepAlive(true)
|
|
|
- tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
|
|
- }
|
|
|
-
|
|
|
- clientconn := httputil.NewClientConn(conn, nil)
|
|
|
- defer clientconn.Close()
|
|
|
-
|
|
|
- // Server hijacks the connection, error 'connection closed' expected
|
|
|
- resp, err := clientconn.Do(req)
|
|
|
- if err != nil {
|
|
|
- return types.HijackedResponse{}, err
|
|
|
- }
|
|
|
-
|
|
|
- defer resp.Body.Close()
|
|
|
- switch resp.StatusCode {
|
|
|
- case http.StatusOK, http.StatusSwitchingProtocols:
|
|
|
- rwc, br := clientconn.Hijack()
|
|
|
- return types.HijackedResponse{Conn: rwc, Reader: br}, err
|
|
|
- }
|
|
|
-
|
|
|
- errbody, err := ioutil.ReadAll(resp.Body)
|
|
|
- if err != nil {
|
|
|
- return types.HijackedResponse{}, err
|
|
|
- }
|
|
|
- return types.HijackedResponse{}, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(errbody))
|
|
|
+ return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err
|
|
|
}
|
|
|
|
|
|
func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
|
|
@@ -189,3 +151,56 @@ func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
|
|
|
}
|
|
|
return net.Dial(proto, addr)
|
|
|
}
|
|
|
+
|
|
|
+func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, error) {
|
|
|
+ req.Host = cli.addr
|
|
|
+ req.Header.Set("Connection", "Upgrade")
|
|
|
+ req.Header.Set("Upgrade", proto)
|
|
|
+
|
|
|
+ conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
|
|
+ }
|
|
|
+
|
|
|
+ // When we set up a TCP connection for hijack, there could be long periods
|
|
|
+ // of inactivity (a long running command with no output) that in certain
|
|
|
+ // network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
|
|
+ // state. Setting TCP KeepAlive on the socket connection will prohibit
|
|
|
+ // ECONNTIMEOUT unless the socket connection truly is broken
|
|
|
+ if tcpConn, ok := conn.(*net.TCPConn); ok {
|
|
|
+ tcpConn.SetKeepAlive(true)
|
|
|
+ tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
|
|
+ }
|
|
|
+
|
|
|
+ clientconn := httputil.NewClientConn(conn, nil)
|
|
|
+ defer clientconn.Close()
|
|
|
+
|
|
|
+ // Server hijacks the connection, error 'connection closed' expected
|
|
|
+ resp, err := clientconn.Do(req)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if resp.StatusCode != http.StatusSwitchingProtocols {
|
|
|
+ resp.Body.Close()
|
|
|
+ return nil, fmt.Errorf("unable to upgrade to %s, received %d", proto, resp.StatusCode)
|
|
|
+ }
|
|
|
+
|
|
|
+ c, br := clientconn.Hijack()
|
|
|
+ if br.Buffered() > 0 {
|
|
|
+ // If there is buffered content, wrap the connection
|
|
|
+ c = &hijackedConn{c, br}
|
|
|
+ } else {
|
|
|
+ br.Reset(nil)
|
|
|
+ }
|
|
|
+
|
|
|
+ return c, nil
|
|
|
+}
|
|
|
+
|
|
|
+type hijackedConn struct {
|
|
|
+ net.Conn
|
|
|
+ r *bufio.Reader
|
|
|
+}
|
|
|
+
|
|
|
+func (c *hijackedConn) Read(b []byte) (int, error) {
|
|
|
+ return c.r.Read(b)
|
|
|
+}
|