coder / coder/websocket

Unsolicited response using NetConn

未关闭
#415 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
needs-investigation
主要语言
Go
星标
5.5k
派生
372
PR 合并指标
30 天内没有已合并 PR

描述

I have the below for communicating via a websocket to a remote docker instance using Docker's Go SDK (`'client'` is the Docker SDK package):

```
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"nhooyr.io/websocket"

...

dockerHost := "http://docker"
wrappedConn := websocket.NetConn(ctx, c, websocket.MessageBinary)

// Custom dial function that returns the wrapped WebSocket connection
customDial := func(ctx context.Context, network, addr string) (net.Conn, error) {
return wrappedConn, nil
}
cli, _ := client.NewClientWithOpts(client.WithDialContext(customDial), client.WithHost(dockerHost))
```

On the receiving end I accept the request and relay to the Docker socket:

```
...
// Connect to Docker Unix Socket with context
dialer := &net.Dialer{}
dockerConn, err := dialer.DialContext(ctx, "unix", "/var/run/docker.sock")
if err != nil {
slog.Error("error connecting to Docker daemon", "error", err)
return
}
defer dockerConn.Close()

// Relay from Docker to WebSocket
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
_, err := io.Copy(websocket.NetConn(ctx, wsConn, websocket.MessageBinary), dockerConn)
if err != nil && ctx.Err() == nil {
slog.Error("error relaying data from Docker socket to WebSocket", "error", err)
}
}()

// Relay from WebSocket to Docker
_, err = io.Copy(dockerConn, websocket.NetConn(ctx, wsConn, websocket.MessageBinary))
if err != nil {
slog.Error("error relaying data from WebSocket to Docker socket", "error", err)
}
```

It works great out the box, brilliant feature, with one exception. When trying to connect to attach to a container, the Docker API hijacks the connection and for some reason this seems to stump the NetConn:

```
execID, err := cli.ContainerExecCreate(ctx, containerID, execConfig)
if err != nil {
panic(err)
}

// Attach to the exec instance
resp, err := cli.ContainerExecAttach(ctx, execID.ID, types.ExecStartCheck{})
if err != nil {
panic(err)
}
defer resp.Close()
```

Error message:

```
2023/10/30 17:45:19 Unsolicited response received on idle HTTP channel starting with "HTTP/1.1 101 UPGRADED\r\nApi-Version: 1.43\r\nConnection"; err=
```

The error varies on each request

```
2023/10/30 17:52:22 Unsolicited response received on idle HTTP channel starting with "HTTP/1.1 101 UPGRADED\r\nApi-Version: 1.43\r\nConnection: Upgrade\r\nContent-Type: application/vnd.docker.multiplexed-stream\r\nDocker-Experimental: false\r\nOstype: linux\r\nServer: Docker/24.0.6 (linux)\r\nUpgrade: tcp\r\n\r\n"; err=
```

```
2023/10/30 17:53:37 Unsolicited response received on idle HTTP channel starting with "HTTP/1.1 101 UPGRADED\r\nApi-Version: 1.43\r\n"; err=
```

If I call cli.ContainerExecAttach it goes through, but if I call cli.ContainerExecCreate and then cli.ContainerExecAttach on the same connection consecutively it errors. Something about cli.ContainerExecAttach specifically which does a hijack and isn't happy unless it is the first request made on the websocket. Other non-hijack consecutive commands go through ok.

Managed to narrow it down to on the docker end to: https://github.com/moby/moby/blob/311b9ff0aa93aa55880e1e5f8871c4fb69583426/client/hijack.go#L86C1-L86C1

Hard to understand why it would conflict with the websocket connection only on second requests.

贡献指南

这个仓库没有索引到贡献指南

调研方向

从 websocket.NetConn 和链接的 Moby client/hijack.go 位置(约第 86 行)开始,然后在一个连接上复现先执行 ContainerExecCreate、再执行 ContainerExecAttach 的序列。将该序列与首次请求的 hijack 以及报告中所示的 non-hijack 请求进行比较;完成条件是确定连接处理冲突,并提供一个有针对性的 regression test 或记录在案的 fix。

由索引模型根据 Issue 内容生成。

评估

技术栈
docker, go
领域
networking
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
停滞
描述清晰度
需要澄清
新手友好度
35/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。