mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-11-13 00:14:22 +08:00
3549fe5ad8
* fix(udp): preserve local destinations and unicast reply sources on Linux * fix(udp): select unicast sources for IPv6 multicast replies * fix(udp): retain interface zones for link-local multicast sessions * fix(udp): isolate interface-local multicast sessions * fix(udp): include ingress interfaces in session identity * fix(udp): restrict destination zones to IPv6 * ci(udp): follow rc5 patched Go toolchain requirement --------- Co-authored-by: sagit <36596628+Sagit-chu@users.noreply.github.com>
134 lines
6.5 KiB
Markdown
134 lines
6.5 KiB
Markdown
# Preserve UDP reply source addresses on Linux
|
|
|
|
The Linux UDP listener preserves the destination address of each incoming
|
|
datagram so that replies use the correct local source. This change was developed
|
|
against FLVX `3.0.1-rc4`, commit
|
|
`d9303032cad09b33cd0b7c91704fd671fbfdd078`.
|
|
It also includes the `3.0.1-rc5` runtime update at
|
|
`c794586cc7773e5f967783895d3071538442bbf7`.
|
|
|
|
## Problem
|
|
|
|
A wildcard UDP socket bound to `0.0.0.0:port` or `[::]:port` can receive datagrams
|
|
addressed to several local IP addresses. Previously, the listener retained only
|
|
the client address. A reply sent through `WriteTo` could therefore use a source
|
|
IP chosen by the kernel instead of the IP the client contacted. DNS clients, for
|
|
example, can reject such replies as coming from an unexpected source.
|
|
|
|
The UDP session pool was also keyed only by the client address and port. When
|
|
one client socket contacted multiple local addresses on the same listener port,
|
|
these requests could share a session despite having different destinations.
|
|
|
|
## Changes
|
|
|
|
- Linux wildcard UDP sockets enable IPv4/IPv6 packet information. `recvmsg`
|
|
preserves the datagram's local destination and interface; `sendmsg` uses the
|
|
corresponding source address and interface when replying.
|
|
- IPv4 keeps the original destination (`ipi_addr`) separate from the local
|
|
reply address (`ipi_spec_dst`), so broadcast requests receive unicast replies
|
|
rather than attempting to send with a broadcast source address.
|
|
- IPv6 multicast destinations remain part of session identity, but replies
|
|
leave the source unspecified and retain the received interface index so the
|
|
kernel can select a valid local unicast source.
|
|
- The destination metadata travels through existing metrics, statistics,
|
|
admission and traffic-limiter wrappers. The exposed remote address remains
|
|
the real client address.
|
|
- Sessions are keyed by the local destination endpoint, remote client endpoint
|
|
and received interface index. Identical IPv4 broadcast or multicast endpoints
|
|
on different interfaces therefore retain separate reply control information.
|
|
An accepted session reports its actual local destination.
|
|
Link-local IPv6 unicast and link-local or interface-local multicast
|
|
destinations retain their interface zone, keeping identical multicast groups
|
|
on different interfaces separate even when the client uses the same
|
|
non-link-local IP and source port.
|
|
IPv4 destinations, including link-local unicast and multicast, never receive
|
|
an IPv6 zone; their interface isolation uses the separate index in the key.
|
|
- Explicitly bound UDP sockets retain their existing behavior. Non-Linux
|
|
platforms retain their existing implementation. No configuration keys or
|
|
panel API changes are required.
|
|
|
|
The fix affects the shared UDP listener, including services dynamically created
|
|
after startup. It does not configure listening addresses, NAT, routing or
|
|
firewalls. Multi-homed hosts still need correct source policy routing for their
|
|
uplinks; this patch does not fix SSH/TCP routing or packet loss before ingress.
|
|
|
|
## Build
|
|
|
|
Use Go 1.26.10 or a compatible newer toolchain. From the repository root:
|
|
|
|
```sh
|
|
cd go-gost
|
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath \
|
|
-ldflags '-s -w' -o flux_agent .
|
|
```
|
|
|
|
Use the agent with the normal panel integration and forwarding configuration.
|
|
Selecting a wildcard listener enables destination preservation automatically
|
|
on Linux. The patch does not select a listener address or change the upstream
|
|
connection's outbound interface.
|
|
|
|
## Regression tests
|
|
|
|
The Linux destination tests cover IPv4 wildcard and dual-stack wildcard sockets,
|
|
explicit IPv4/IPv6 bindings, one client source port contacting multiple local
|
|
destinations, repeated requests, session expiry, and active metrics, statistics,
|
|
admission and traffic-limit wrappers. A separate IPv6 case binds the client to
|
|
`::1` and checks the exact remote IP and port for every destination. Broadcast
|
|
tests exercise both IPv4 and dual-stack listeners, retaining the broadcast
|
|
destination while checking that replies use a local unicast address.
|
|
An IPv6 multicast regression sends across an isolated veth pair and checks the
|
|
multicast session destination and the unicast reply, including interface zones.
|
|
A two-interface case uses one fixed ULA client endpoint to contact the same
|
|
link-local or interface-local multicast group on both interfaces and checks
|
|
distinct sessions and their individual reply-interface metadata.
|
|
IPv4 and dual-stack cases likewise use a single fixed client endpoint to send
|
|
to one multicast group or the limited broadcast address on two interfaces,
|
|
checking separate sessions and their reply-interface indices.
|
|
Link-local IPv4 unicast and multicast regressions also check that accepted
|
|
local endpoints have no IPv6 zone, on both IPv4 and dual-stack listeners.
|
|
|
|
Run the standard suites on Linux:
|
|
|
|
```sh
|
|
(cd go-gost && go test ./...)
|
|
(cd go-gost/x && go test ./...)
|
|
```
|
|
|
|
To exercise multiple IPv6 addresses without modifying the host's live network,
|
|
compile the tests first and run them inside a disposable network namespace.
|
|
The following commands require Linux, root access for `unshare`, and `iproute2`:
|
|
|
|
```sh
|
|
cd go-gost/x
|
|
go test -c -race -o /tmp/flvx-udp-destination.test ./listener/udp
|
|
go test -c -race -o /tmp/flvx-udp-packet.test ./internal/net/udp
|
|
sudo unshare --net -- sh -eu -c '
|
|
sysctl -qw net.ipv6.conf.all.disable_ipv6=0
|
|
ip link set lo up
|
|
ip addr add 198.18.0.1/32 dev lo
|
|
ip addr add 169.254.1.2/32 dev lo
|
|
ip -6 addr add fd00:1::1/128 dev lo nodad
|
|
ip -6 addr add fd00:2::1/128 dev lo nodad
|
|
ip -6 addr add fd00:3::1/128 dev lo nodad
|
|
for suffix in "" 2; do
|
|
ip link add "mc-tx$suffix" type veth peer name "mc-rx$suffix"
|
|
ip link set "mc-tx$suffix" addrgenmode none up
|
|
ip link set "mc-rx$suffix" addrgenmode none up
|
|
ip -6 addr add fe80::1/64 dev "mc-tx$suffix" nodad
|
|
ip -6 addr add fe80::2/64 dev "mc-rx$suffix" nodad
|
|
done
|
|
ip addr add 198.18.1.1/24 dev mc-tx
|
|
ip addr add 198.18.2.1/24 dev mc-tx2
|
|
export FLUX_TEST_IPV6_ADDRS=fd00:1::1,fd00:2::1,fd00:3::1
|
|
export FLUX_TEST_IPV4_LINK_LOCAL=169.254.1.2
|
|
export FLUX_TEST_MULTICAST_RX=mc-rx FLUX_TEST_MULTICAST_TX=mc-tx
|
|
export FLUX_TEST_MULTICAST_RX2=mc-rx2 FLUX_TEST_MULTICAST_TX2=mc-tx2
|
|
/tmp/flvx-udp-destination.test -test.v -test.timeout=60s -test.count=5
|
|
exec /tmp/flvx-udp-packet.test -test.v -test.timeout=60s -test.count=5
|
|
'
|
|
```
|
|
|
|
The addresses above are isolated test addresses, not deployment settings.
|
|
The `UDP Destination Regression` GitHub Actions workflow runs these namespace
|
|
tests, the race detector, both agent module suites, and a Linux agent build.
|