litespeedtech / openlitespeed

Our high-performance, lightweight, open source HTTP server
https://openlitespeed.org
GNU General Public License v3.0
1.16k stars 189 forks source link

IPv4 $_SERVER['SERVER_ADDR'] is IPv6 encoded when QUIC/HTTP3 is enabled #333

Closed Znuff closed 1 year ago

Znuff commented 2 years ago

How to reproduce:

  1. Enable HTTP3/QUIC on a VHost
  2. create a php file, let's say h.php that dumps $_SERVER, like:
    <?php print(json_encode($_SERVER, JSON_PRETTY_PRINT)); ?>
  3. Access the file at https://vhost.domain.com/h.php, with a browser that support QUIC/HTTP3, preferably in Incognito/Private mode, OVER IPv4
  4. Notice the values of:
    "SERVER_ADDR": "A.B.C.D",
    "X_SPDY": "HTTP2",
    "SSL_PROTOCOL": "TLSv1.3",
  5. Refresh page, so it reconnects over HTTP3/QUIC (so it picks the alt-svc header from previous request)
  6. Notice the values of:
    "SERVER_ADDR": "::ffff:A.B.C.D",
    "X_SPDY": "HTTP3",
    "SSL_PROTOCOL": "QUIC",

Notice that, obviously, when accessing the server over IPv6, this SERVER_ADDR will reflect the proper IPv6 address.

Disabling IPv6 on the machine -- via sysctl net.ipv6.conf.all.disable_ipv6=0 - and restarting lsws, does not seem to change the way the IPv4 address is encapsulated.

The only thing that reports the correct SERVER_ADDRESS is turning off QUIC/HTTP3.

I've tested on a LiteSpeed Enterprise version, to compare, and I wasn't able to reproduce.

This has been an issue for us for over a year now, but we just recently tracked it down to QUIC/HTTP3. It was easily reproduced on Openlitespeed 1.7.16.

To my uninformed eye, it seems that the UDP socket in src/quic/quicengine.cpp is only called with AF_INET6, but I'm no C++ expert.

This is an issue with web apps like WHMCS and others that depend on a remote Licensing server, because the REMOTE_ADDR will no longer match what the licenser will have whitelisted.

Furthermore, it's terrible to debug, because for some browsers this will seemingly work -- browsers like Safari with no QUIC/HTTP3 support will connect over HTTP2, thus the license will suddenly be valid.

litespeedtech commented 2 years ago

Hi,

Thanks the bug report, you can the follow patch to address this.

diff --git a/src/quic/quicengine.cpp b/src/quic/quicengine.cpp
index 370da9780..7a0325018 100644
--- a/src/quic/quicengine.cpp
+++ b/src/quic/quicengine.cpp
@@ -415,7 +415,18 @@ lsquic_stream_ctx_t *QuicEngine::onNewStream(void *stream_if_ctx,
                                     pConnInfo->m_pServerAddrInfo->getAddr());
     pConnInfo->m_pCrypto = pStream;
     pConnInfo->m_pClientInfo = pClientInfo;
-    pConnInfo->m_pServerAddrInfo = ServerAddrRegistry::getInstance().get(
+    if ((AF_INET6 == pLocal->sa_family) &&
+        (IN6_IS_ADDR_V4MAPPED(&((sockaddr_in6 *)pLocal)->sin6_addr)))
+    {
+        char serverAddr[28] = {0};
+        struct sockaddr *pAddr = (sockaddr *)serverAddr;
+        pAddr->sa_family = AF_INET;
+        memmove(&((sockaddr_in *)pAddr)->sin_addr.s_addr, &((char *)pLocal)[20], 4);
+        pConnInfo->m_pServerAddrInfo = ServerAddrRegistry::getInstance().get(
+            pLocal, pUdpListener->getTcpPeer());
+    }
+    else
+        pConnInfo->m_pServerAddrInfo = ServerAddrRegistry::getInstance().get(
              pLocal, pUdpListener->getTcpPeer());
     pConnInfo->m_remotePort = GSockAddr::getPort(pPeer);
     //pStream->setConnInfo(getStream()->getConnInfo());