apache / apache/dubbo

[Bug] Triple HTTP/3 handshake fails on multi-homed hosts: 127.0.0.1 rewritten and QUIC responses from wrong source

Closed
#16,460 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
41.6k
Forks
26.4k
Avg merge
15h 13m
Merged PRs (30d)
4

Description

### Pre-check

- [X] I am sure that all the content I provide is in English.
- [X] I had searched in the [issues](https://github.com/apache/dubbo/issues?q=is%3Aissue) and found no similar issues.

### Apache Dubbo Component

Java SDK (apache/dubbo)

### Dubbo Version

3.3.6, OpenJDK 17, Linux x86_64 (GitHub Actions runner, ubuntu-24.04: `eth0` 10.1.0.215, `docker0` 172.18.0.1)

### Steps to reproduce this issue

Triple HTTP/3 server + client on the same machine, point-to-point, loopback target. Single maven module, deps: `org.apache.dubbo:dubbo`, `dubbo-rpc-triple`, `dubbo-remoting-http3` (all 3.3.6) + `protobuf-java` 3.25.5.

greet.proto:

```proto
syntax = "proto3";
package greet;
option java_package = "repro.greet";
option java_multiple_files = true;

message GreetRequest { string name = 1; }
message GreetResponse { string greeting = 1; }
service GreetService { rpc Greet(GreetRequest) returns (GreetResponse) {} }
```

Server: h3 enabled, negotiation off, TLS via the certs from dubbo-go-samples/http3/x509 (`dubbo.protocol.triple.http3.enabled=true`, `negotiation=false`, `dubbo.protocol.port=20000`, ssl server paths).

Client:

```java
public static void main(String[] args) throws Exception {
System.setProperty("dubbo.protocol.triple.http3.enabled", "true");
System.setProperty("dubbo.protocol.triple.http3.negotiation", "false");
System.setProperty("dubbo.ssl.client-key-cert-chain-path", "x509/server2_cert.pem");
System.setProperty("dubbo.ssl.client-private-key-path", "x509/server2_key_pkcs8.pem");
System.setProperty("dubbo.ssl.client-trust-cert-collection-path", "x509/server_ca_cert.pem");

System.out.println("filterLocalHost(127.0.0.1) -> " + NetUtils.filterLocalHost("127.0.0.1"));

ReferenceConfig referenceConfig = new ReferenceConfig<>();
referenceConfig.setInterface(GreetService.class);
referenceConfig.setUrl("tri://127.0.0.1:20000?connect.timeout=10000");
referenceConfig.setProtocol("tri");

DubboBootstrap.getInstance().reference(referenceConfig).start();
GreetService greetService = referenceConfig.get();
GreetResponse response = greetService.greet(GreetRequest.newBuilder().setName("repro").build());
System.out.println("SUCCESS! " + response.getGreeting());
}
```

On a multi-homed host the first line already prints the rewritten address, e.g. `filterLocalHost(127.0.0.1) -> 172.18.0.1`.

### Expected behavior

Client connects to 127.0.0.1:20000, handshake completes.

### Actual behavior

On a multi-homed host the connect always times out, and the target in the error is not the configured one:

```
RemotingException: client(url: tri://127.0.0.1:20000/greet.GreetService?...) failed to connect to server /172.18.0.1:20000 client-side timeout 10000ms
```

The client dials 172.18.0.1, not 127.0.0.1. tcpdump during the handshake:

```
IP 10.1.0.215.34293 > 172.18.0.1.20000: UDP, length 1200 <- client Initial
IP 10.1.0.215.20000 > 10.1.0.215.34293: UDP, length 88 <- server response, wrong source
```

The server answers from 10.1.0.215:20000 instead of 172.18.0.1:20000. The client drops handshake responses from a different address than the dialed destination and keeps retransmitting until the timeout. The same client completes the QUIC handshake against a dubbo-go triple http3 server (quic-go, bound 0.0.0.0) on the same runner.

Two code paths combine:

1. `AbstractClient.getConnectAddress()` (dubbo-remoting-api) wraps the URL host with `NetUtils.filterLocalHost(...)`. `NetUtils.isInvalidLocalHost()` treats `127.*` as invalid, so the host is replaced with `NetUtils.getLocalHost()` (172.18.0.1, the docker0 address).

2. `NettyHttp3Server` binds the QUIC transport on a wildcard `NioDatagramChannel` (`Start NettyHttp3Server bind /0.0.0.0:20000`) and responses carry the kernel-selected source (10.1.0.215, the interface towards the client) instead of the local destination address the client packet was sent to (172.18.0.1).

### Anything else

Works on single-homed hosts (macOS): rewritten address and response source coincide, handshake succeeds. Fails the same way with connect.timeout raised to 10s. Full CI run with tcpdump capture: https://github.com/apache/dubbo-go-samples/actions/runs/34701734332

### Are you willing to submit a pull request to fix on your own?

- [X] Yes I am willing to submit a pull request on my own!

Contributor guide

Open the contributing guide

Research direction

Start by inspecting AbstractClient.getConnectAddress(), NetUtils.filterLocalHost(), and NetUtils.isInvalidLocalHost() in dubbo-remoting-api, then trace NettyHttp3Server's wildcard NioDatagramChannel binding. Reproduce with the provided multi-homed HTTP/3 setup and tcpdump capture. Done means the client retains the configured destination and the QUIC handshake succeeds without source-address mismatch.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.