When the proxy forwards a UDP datagram to a backend that isn't listening,
the kernel queues an ICMP port-unreachable error on the connected socket.
This error is returned on the *next* Write() call, not the one that
triggered it. As a result, a subsequent write carrying new data silently
fails and the datagram is never sent to the backend.
The replyLoop already handles this for reads (goto again on ECONNREFUSED).
Apply the same treatment to writes in Run() and retry the write when the
error is ECONNREFUSED.
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
| ... | ... |
@@ -199,6 +199,13 @@ func (proxy *UDPProxy) Run() {
|
| 199 | 199 |
for i := 0; i != read; {
|
| 200 | 200 |
written, err := cte.conn.Write(readBuf[i:read]) |
| 201 | 201 |
if err != nil {
|
| 202 |
+ if opErr, ok := err.(*net.OpError); ok && errors.Is(opErr.Err, syscall.ECONNREFUSED) {
|
|
| 203 |
+ // A previous write to the backend may have resulted in an |
|
| 204 |
+ // ICMP port-unreachable. The kernel queues this as an error |
|
| 205 |
+ // on the socket, which is returned on the next Write. Retry |
|
| 206 |
+ // so the current datagram is not silently dropped. |
|
| 207 |
+ continue |
|
| 208 |
+ } |
|
| 202 | 209 |
log.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
|
| 203 | 210 |
break |
| 204 | 211 |
} |