cloudflare / cloudflare/cloudflared

🐛 bidirectional stream pipe immediate closing may causes errors in specific scenarios

Open
#926 0 comments 0 reactions 0 assignees View on GitHub
Priority: Normal Type: Feature Request
Dominant language
Go
Stars
15.6k
Forks
1.4k
PR merge metrics
No merged PRs in 30d

Description

**Describe the bug**

Cloudflared's current stream relay behavior is, when one side (origin or tunnel) closes, the other side closes immediately:

https://github.com/cloudflare/cloudflared/blob/be64362fdb2a2da481f8e0414f75de3db2ccdf32/stream/stream.go#L43-L51

It's generally okay with most tunnel scenarios, but with some specific network clients/implementations, it causes errors.

For example, Client A connects to Server B through the cloudflared tunnel using a TCP connection. Client A calls `CloseWrite` to make the connection read-only, and expecting to continue reading from Server B. However, the stream terminates immediately after calling `CloseWrite`, preventing the expected behavior.

**To Reproduce**

1. Use provided client.go and server.go code samples
2. Run server.go with a specified port using `os.Args[1]`
3. Run client.go with a greeting message as an argument
4. Expected output on local server: `read from remote: hello`
5. Actual output: `read from remote: EOF`

client.go

```go
func main() {
if len(os.Args) <= 1 {
fmt.Println("please enter some greeting message")
return
}

conn, err := net.Dial("tcp", "your-ip:port")
if err != nil {
fmt.Println("dial:", err.Error())
return
}
defer conn.Close()

if _, err := conn.Write([]byte(os.Args[1])); err != nil {
fmt.Println("write greeting message to remote:", err.Error())
return
}

if err := conn.(*net.TCPConn).CloseWrite(); err != nil {
fmt.Println("close write:", err.Error())
return
}

ret := make([]byte, 1024)
nr, err := conn.Read(ret)
if err != nil {
fmt.Println("read from remote:", err.Error())
if nr > 0 {
fmt.Println("read from remote:", string(ret[:nr]))
}
return
}
fmt.Println("read from remote:", string(ret[:nr]))
}
```

server.go

```go
func main() {
if len(os.Args) <= 1 {
fmt.Println("please set server port using os.Args[1]")
return
}
lis, err := net.Listen("tcp", ":"+os.Args[1])
if err != nil {
fmt.Println("listen:", err.Error())
return
}
fmt.Println("server listen on :" + os.Args[1])
for {
conn, err := lis.Accept()
if err != nil {
fmt.Println("accept:", err.Error())
continue
}
go handleConn(conn)
}
}

func handleConn(conn net.Conn) {
defer conn.Close()

for {
b := make([]byte, 1024)
nr, err := conn.Read(b)
if err != nil {
fmt.Println("read:", err.Error())
if nr > 0 {
fmt.Println("read err, bug got:", b[:nr])
}
return
}
fmt.Println("read:", string(b[:nr]))
time.Sleep(10 * time.Second)
if _, err := conn.Write(b[:nr]); err != nil {
fmt.Println("write to remote:", err.Error())
}
}
}
```

So I wonder if there's a more graceful way to handle closing situations in cloudflared stream relay to prevent errors in the described scenario.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.