3. Tunneling UDP su HTTP
- Tunneling UDP su HTTP
Per consentire la negoziazione di un tunnel per UDP su HTTP, questo documento definisce il token di aggiornamento HTTP "connect-udp". I tunnel UDP risultanti utilizzano il protocollo Capsule (vedere la Sezione 3.2 di [HTTP-DGRAM]) con datagrammi HTTP nel formato definito nella Sezione 5.
Per avviare un tunnel UDP associato a un singolo flusso HTTP, un client emette una richiesta contenente il token di aggiornamento "connect-udp". La destinazione del tunnel è indicata dal client al proxy UDP tramite le variabili "target_host" e "target_port" del modello URI; vedere la Sezione 2.
"target_host" supporta l'utilizzo di nomi DNS, letterali IPv6 e letterali IPv4. Si noti che gli identificatori di zona di indirizzamento con ambito IPv6 non sono supportati. Utilizzando i termini IPv6address, IPv4address, reg-name e port da [URI], le variabili "target_host" e "target_port" DEVONO aderire al formato nella Figura 2, utilizzando la notazione da [ABNF]. Inoltre:
-
entrambe le variabili "target_host" e "target_port" NON DEVONO essere vuote.
-
se "target_host" contiene un letterale IPv6, i due punti (":") DEVONO essere codificati in percentuale. Ad esempio, se l'host di destinazione è "2001:db8::42", verrà codificato nell'URI come "2001%3Adb8%3A%3A42".
-
"target_port" DEVE rappresentare un numero intero compreso tra 1 e 65535 inclusi.
target_host = IPv6address / IPv4address / reg-name target_port = port
Figura 2: Formato variabile modello URI
Quando invia la sua richiesta di proxying UDP, il client DEVE eseguire l'espansione del modello URI per determinare il percorso e la query della sua richiesta.
Se la richiesta ha esito positivo, il proxy UDP si impegna a convertire i datagrammi HTTP ricevuti in pacchetti UDP e viceversa, fino alla chiusura del tunnel.
In virtù della definizione del protocollo Capsule (vedere la Sezione 3.2 di [HTTP-DGRAM]), le richieste di proxying UDP non trasportano alcun contenuto del messaggio. Allo stesso modo, anche le risposte di proxying UDP riuscite non trasportano alcun contenuto del messaggio.
3.1. Gestione del proxy UDP
Dopo aver ricevuto una richiesta di proxying UDP:
-
se il destinatario è configurato per utilizzare un altro proxy HTTP, agirà come intermediario inoltrando la richiesta a un altro server HTTP. Si noti che tali intermediari potrebbero dover ricodificare la richiesta se la inoltrano utilizzando una versione di HTTP diversa da quella utilizzata per riceverla, poiché la codifica della richiesta differisce per versione (vedere di seguito).
-
altrimenti, il destinatario agirà come proxy UDP. Estrae le variabili "target_host" e "target_port" dall'URI che ha ricostruito dalle intestazioni della richiesta, decodifica la loro codifica percentuale e stabilisce un tunnel aprendo direttamente un socket UDP verso la destinazione richiesta.
A differenza di TCP, UDP è senza connessione. Il proxy UDP che apre il socket UDP non ha modo di sapere se la destinazione è raggiungibile. Pertanto, deve rispondere alla richiesta senza attendere un pacchetto dalla destinazione. Tuttavia, se "target_host" è un nome DNS, il proxy UDP DEVE eseguire la risoluzione DNS prima di rispondere alla richiesta HTTP. Se si verificano errori durante questo processo, il proxy UDP DEVE rifiutare la richiesta e DOVREBBE inviare i dettagli utilizzando un campo di intestazione Proxy-Status appropriato [PROXY-STATUS]. Ad esempio, se la risoluzione DNS restituisce un errore, il proxy può utilizzare il tipo di errore proxy dns_error dalla Sezione 2.3.2 di [PROXY-STATUS].
I proxy UDP possono utilizzare socket UDP connessi se il loro sistema operativo li supporta, in quanto ciò consente al proxy UDP di fare affidamento sul kernel per inviargli solo pacchetti UDP che corrispondono alla tupla a 5 corretta. Se il proxy UDP utilizza un socket non connesso, DEVE convalidare l'indirizzo IP di origine e la porta di origine UDP sui pacchetti ricevuti per garantire che corrispondano alla richiesta del client. I pacchetti che non corrispondono DEVONO essere scartati dal proxy UDP.
La durata del socket è legata al flusso della richiesta. Il proxy UDP DEVE mantenere aperto il socket mentre il flusso della richiesta è aperto. Se un proxy UDP riceve notifica dal suo sistema operativo che il suo socket non è più utilizzabile, DEVE chiudere il flusso della richiesta. Ad esempio, ciò può accadere quando viene ricevuto un messaggio ICMP Destination Unreachable; vedere la Sezione 3.1 di [ICMP6]. I proxy UDP POSSONO scegliere di chiudere i socket a causa di un periodo di inattività, ma DEVONO chiudere il flusso della richiesta quando chiudono il socket. I proxy UDP che chiudono i socket dopo un periodo di inattività NON DOVREBBERO utilizzare un periodo inferiore a due minuti; vedere la Sezione 4.3 di [BEHAVE].
Una risposta riuscita (come definito nelle Sezioni 3.3 e 3.5) indica che il proxy UDP ha aperto un socket verso la destinazione richiesta ed è disposto a eseguire il proxying dei payload UDP. Qualsiasi risposta diversa da una risposta riuscita indica che la richiesta non è riuscita; pertanto, il client DEVE interrompere la richiesta.
I proxy UDP NON DEVONO introdurre la frammentazione a livello IP durante l'inoltro di datagrammi HTTP su un socket UDP; i datagrammi eccessivamente grandi vengono scartati silenziosamente. In IPv4, il bit Don't Fragment (DF) DEVE essere impostato, se possibile, per impedire la frammentazione sul percorso. Le estensioni future POSSONO rimuovere questi requisiti.
Gli implementatori di proxy UDP trarranno vantaggio dalla lettura delle linee guida in [UDP-USAGE].
3.2. Richiesta HTTP/1.1
Quando si utilizza HTTP/1.1 [HTTP/1.1], una richiesta di proxying UDP soddisferà i seguenti requisiti:
-
il metodo DEVE essere "GET".
-
la richiesta DEVE includere un singolo campo di intestazione Host contenente l'origine del proxy UDP.
-
la richiesta DEVE includere un campo di intestazione Connection con valore "Upgrade" (si noti che questo requisito non fa distinzione tra maiuscole e minuscole secondo la Sezione 7.6.1 di [HTTP]).
-
la richiesta DEVE includere un campo di intestazione Upgrade con valore "connect-udp".
Una richiesta di proxying UDP che non è conforme a queste restrizioni è malformata. Il destinatario di tale richiesta malformata DEVE rispondere con un errore e DOVREBBE utilizzare il codice di stato 400 (Bad Request).
Ad esempio, se il client è configurato con il modello URI "https://example.org/.well-known/masque/udp/\{target_host\}/\{target_port\}/" e desidera aprire un tunnel di proxying UDP verso la destinazione 192.0.2.6:443, potrebbe inviare la seguente richiesta:
GET https://example.org/.well-known/masque/udp/192.0.2.6/443/ HTTP/1.1 Host: example.org Connection: Upgrade Upgrade: connect-udp Capsule-Protocol: ?1
Figura 3: Esempio di richiesta HTTP/1.1
In HTTP/1.1, questo protocollo utilizza il metodo GET per imitare la progettazione del protocollo WebSocket [WEBSOCKET].
3.3. Risposta HTTP/1.1
Il proxy UDP DEVE indicare una risposta riuscita rispondendo con i seguenti requisiti:
-
il codice di stato HTTP sulla risposta DEVE essere 101 (Switching Protocols).
-
la risposta DEVE includere un campo di intestazione Connection con valore "Upgrade" (si noti che questo requisito non fa distinzione tra maiuscole e minuscole secondo la Sezione 7.6.1 di [HTTP]).
-
la risposta DEVE includere un singolo campo di intestazione Upgrade con valore "connect-udp".
-
la risposta DEVE soddisfare i requisiti delle risposte HTTP che avviano il protocollo Capsule; vedere la Sezione 3.2 di [HTTP-DGRAM].
Se uno qualsiasi di questi requisiti non è soddisfatto, il client DEVE trattare questo tentativo di proxying come fallito e interrompere la connessione.
Ad esempio, il proxy UDP potrebbe rispondere con:
HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: connect-udp Capsule-Protocol: ?1
Figura 4: Esempio di risposta HTTP/1.1
3.4. Richieste HTTP/2 e HTTP/3
Quando si utilizza HTTP/2 [HTTP/2] o HTTP/3 [HTTP/3], le richieste di proxying UDP utilizzano HTTP Extended CONNECT. Ciò richiede che i server inviino un'impostazione HTTP come specificato in [EXT-CONNECT2] e [EXT-CONNECT3] e che le richieste utilizzino campi di pseudo-intestazione HTTP con i seguenti requisiti:
-
Il campo di pseudo-intestazione :method DEVE essere "CONNECT".
-
Il campo di pseudo-intestazione :protocol DEVE essere "connect-udp".
-
Il campo di pseudo-intestazione :authority DEVE contenere l'autorità del proxy UDP.
-
I campi di pseudo-intestazione :path e :scheme NON DEVONO essere vuoti. I loro valori DEVONO contenere lo schema e il percorso dal modello URI dopo che il processo di espansione del modello URI è stato completato.
Una richiesta di proxying UDP che non è conforme a queste restrizioni è malformata (vedere la Sezione 8.1.1 di [HTTP/2] e la Sezione 4.1.2 di [HTTP/3]).
Ad esempio, se il client è configurato con il modello URI "https://example.org/.well-known/masque/udp/\{target_host\}/\{target_port\}/" e desidera aprire un tunnel di proxying UDP verso la destinazione 192.0.2.6:443, potrebbe inviare la seguente richiesta:
HEADERS :method = CONNECT :protocol = connect-udp :scheme = https :path = /.well-known/masque/udp/192.0.2.6/443/ :authority = example.org capsule-protocol = ?1
Figura 5: Esempio di richiesta HTTP/2
3.5. Risposte HTTP/2 e HTTP/3
Il proxy UDP DEVE indicare una risposta riuscita rispondendo con i seguenti requisiti:
-
il codice di stato HTTP sulla risposta DEVE essere nell'intervallo 2xx (Successful).
-
la risposta DEVE soddisfare i requisiti delle risposte HTTP che avviano il protocollo Capsule; vedere la Sezione 3.2 di [HTTP-DGRAM].
Se uno qualsiasi di questi requisiti non è soddisfatto, il client DEVE trattare questo tentativo di proxying come fallito e interrompere la richiesta.
Ad esempio, il proxy UDP potrebbe rispondere con:
HEADERS :status = 200 capsule-protocol = ?1
Figura 6: Esempio di risposta HTTP/2