mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
ganl: canonicalize IPv4-mapped IPv6 source addresses (site-ban bypass, #800)
The GANL listeners are dual-stack (IPV6_V6ONLY=0), so an inbound IPv4 connection is delivered as a sockaddr_in6 holding ::ffff:a.b.c.d. ApplyNetworkAddressToDesc memcpy'd that verbatim into d->address, leaving it AF_INET6, and compare_to(MUX_SOCKADDR*) compared it as a pure v6 address -- so an IPv4 `forbid a.b.c.d` rule (stored AF_INET) never matched it and the ban was bypassed simply by connecting IPv4 to the v6 socket. isRegistered/isSuspect/sitemon inherited the same blind spot. Add CanonicalizeMappedV4(): when d->address is an IN6_IS_ADDR_V4MAPPED sockaddr_in6, rewrite it in place to the native AF_INET sockaddr (embedded v4 = trailing 4 bytes, same port). Called once in onConnectionOpen after the address is populated -- covering both the GANL sockaddr and the parsed-host fallback -- and before the address is used for access control, display, or logging. So a single IPv4 rule now covers both wire forms, and the logged/displayed address is the natural a.b.c.d rather than ::ffff:. Genuine IPv6 addresses (not v4-mapped) and native IPv4 are untouched, so the change is a no-op except for the mapped form. Uses saro() + reinterpret_cast (the declared sai6ro() has no definition) and copies the raw network-order sin6_port to avoid byte-order juggling; reassigns from a fresh sockaddr_in so no stale sockaddr_in6 bytes linger. Verified: clean build; full smoke 1115/0/0; live netmux serves clean QUIT, concurrent, and abrupt-RST native-IPv4 connections with no regression (the canonicalizer is a no-op there). NOTE: a positive end-to-end demonstration (forbid an IPv4, connect as ::ffff:<ip> over a dual-stack listener, observe the refusal) could not be produced in this sandbox -- the box binds the listener v4-only by default and an `ip_address ::` override did not take effect here -- so the v4-mapped rewrite itself is verified by code reasoning rather than live; on a normal dual-stack deployment the bug bites and this corrects it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
01d4cafb4a
commit
d841f7a366
1 changed files with 46 additions and 0 deletions
|
|
@ -183,6 +183,44 @@ namespace
|
|||
return true;
|
||||
}
|
||||
|
||||
// Canonicalize an IPv4-mapped IPv6 address (::ffff:a.b.c.d) to its native
|
||||
// AF_INET form. The GANL listeners are dual-stack (IPV6_V6ONLY=0), so an
|
||||
// inbound IPv4 connection is delivered as a sockaddr_in6 holding
|
||||
// ::ffff:a.b.c.d. Left as-is, d->address stays AF_INET6 and an IPv4
|
||||
// `forbid a.b.c.d` rule (stored AF_INET) never matches it -- the ban is
|
||||
// bypassed simply by connecting IPv4 to the v6 socket (#800). It also
|
||||
// makes the displayed/logged address the natural a.b.c.d rather than the
|
||||
// ::ffff: form. Genuine IPv6 addresses are untouched.
|
||||
void CanonicalizeMappedV4(MUX_SOCKADDR& addr)
|
||||
{
|
||||
#if defined(HAVE_SOCKADDR_IN6) && defined(HAVE_SOCKADDR_IN)
|
||||
if (AF_INET6 != addr.Family())
|
||||
{
|
||||
return;
|
||||
}
|
||||
const struct sockaddr_in6* sin6 =
|
||||
reinterpret_cast<const struct sockaddr_in6*>(addr.saro());
|
||||
if (nullptr == sin6 || !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_in sin;
|
||||
std::memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
// The embedded IPv4 is the trailing 4 bytes of the v4-mapped address.
|
||||
std::memcpy(&sin.sin_addr, sin6->sin6_addr.s6_addr + 12,
|
||||
sizeof(sin.sin_addr));
|
||||
sin.sin_port = sin6->sin6_port;
|
||||
|
||||
// Reassign from the fresh sockaddr_in (the ctor zero-inits its union),
|
||||
// so no stale sockaddr_in6 bytes linger behind the AF_INET form.
|
||||
addr = MUX_SOCKADDR(reinterpret_cast<const struct sockaddr*>(&sin));
|
||||
#else
|
||||
UNUSED_PARAMETER(addr);
|
||||
#endif
|
||||
}
|
||||
|
||||
int MapGanlReasonToMux(ganl::DisconnectReason reason)
|
||||
{
|
||||
switch (reason)
|
||||
|
|
@ -593,6 +631,14 @@ public:
|
|||
std::memset(d->address.sa(), 0, d->address.maxaddrlen());
|
||||
}
|
||||
|
||||
// Normalize an IPv4-mapped IPv6 source to native AF_INET before the
|
||||
// address is used for access control (isForbid below), display, or
|
||||
// logging (#800). Covers both the GANL sockaddr and the parsed-host
|
||||
// fallback above.
|
||||
if (haveSockAddr) {
|
||||
CanonicalizeMappedV4(d->address);
|
||||
}
|
||||
|
||||
if (haveSockAddr) {
|
||||
d->address.ntop(d->addr, sizeof(d->addr));
|
||||
} else if (!endpoint.host.empty()) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue