[HTTP] HTTP/3

서정범·2024년 1월 19일
0

HTTP

목록 보기
10/13
post-thumbnail

목표

HTTP/2에서 발전한 HTTP/3에 대해서 살펴볼 것입니다.

TCP에서 UDP 방식으로 바뀐 이유, 어떠한 문제점들을 개선하였고, 어떤 기능들이 추가되었는지 살펴볼 것입니다.

UDP기반의 신뢰성 전송 프로토콜 QUIC 프로토콜에 대해서도 살펴볼 것이며, HTTP/3은 HTTPS를 어떻게 지원하는지에 대해서도 살펴볼 것입니다.

HTTP/3

HTTP/2.0의 등장과 함께 기존의 프로토콜 데이터 체계를 프레임과 스트림 개념으로 재구축한 결과 기존보다 혁신적으로 성능이 향상되었습니다.

하지만, HTTP/2.0은 여전히 TCP를 사용하고 있었기 때문에 고질적인 문제를 가지고 있었습니다.

TCP는 TCP의 핸드셰이킹 과정, Slow Start, 패킷 손실로 인한 RTT 계산의 어려움, HOLB in TCP 등과 같은 문제점들을 가지고 있습니다.

HTTP/3은 HTTP(HyperText Transfer Protocol)의 세번째 메이저 버전으로, 기존의 HTTP/1.x와 HTTP/2와는 다르게 UDP 기반의 프로토콜 QUIC(Quick UDP Internet Connections)를 사용해서 통신을 합니다.

한국의 경우 지리적으로 좁고, 통신망이 좋기 때문에 빠른 속도의 인터넷을 지원하여 다른 지역들에 비해 해당 기능들의 차이를 크게 느끼기 쉽지 않지만 외국에서는 확실히 느껴진다고 합니다.

우리는 HTTP/2가 가지고 있는 문제들을 살펴보면서 HTTP/3이 어떻게 해결했는지 살펴볼 것이고, 최종적으로 HTTP/3이 가지고 있는 기능들을 정리하면서 마무리 할 것입니다.

앞으로 살펴볼 것들은 다음과 같습니다.

  • TCP와 UDP
  • QUIC
  • 3-way handshake
  • HOLB in TCP
  • 패킷 손실로 인한 전체 대역폭 감소
  • 패킷 손실로 인한 RTT 계산의 복잡성
  • 네트워크 환경 변화로 인한 재연결
  • 보안

TCP와 UDP

HTTP의 사용 목적은 웹에서 데이터를 주고 받기 위한 프로토콜입니다.

데이터를 주고 받기 위해선 신뢰성 있는 데이터 전송 프로토콜이 필요하였고, 이것을 위해서 TCP/IP를 사용하는 것이 일반적이였습니다.

TCP는 ARQ 전송 프로토콜부터 해서, 흐름 제어, 혼잡 제어, 패킷 손실 감지, 순서 번호와 같은 다양한 기능들을 탑재하고 사용의 편의성을 보장해 주었습니다.

다음은 TCP와 UDP의 정리 표입니다.

TCPUDP
연결 방식연결형 서비스비연결형 서비스
패킷 교환가상 회선 방식데이터그램 방식
전송 순서 보장보장함보장하지 않음
신뢰성높음낮음
전송 속도느림빠름

위 표를 보면, 대략 TCP신뢰성이 높고 느리다, UDP신뢰성이 낮고 빠르다 정도로 정리할 수 있는데, 여기서 말하는 신뢰성이란 전송되는 데이터 패킷들의 순서, 패킷 유실 여부 등을 검사하여 송신 측에서 보낸 데이터가 수신 측에 온전하게 전달될 수 있는가를 의미합니다.

그렇다면, 속도가 중요하다고 하지만 가장 중요한 것은 데이터의 무결성일텐데 어떻게UDP를 사용한 것일까라는 의문이 생길 수 있습니다.

여기서 UDP는 신뢰성이 없다라기 보다는 탑재를 하지 않았다는 표현이 더 정확합니다.

TCP대신 UDP를 선택한 이유

앞서 TCP는 많은 기능들을 탑재하고 있다고 했고, 이것은 간단하게 비유하자면 좋은 기능이 다 들어있는 라이브러리로 말할 수 있을 것 같습니다.

이에 반해 UDP은 아주 기본적인 기능들만이 담겨있고, 필요한 기능만 들어있는 라이브러리로 비유할 수 있을 것 같습니다.

TCP는 워낙 많은 기능들을 가지고 있다보니, 여기에 추가적인 기능을 더 넣을 공간이 없었고 무엇보다 TCP 자체를 수정하기에는 어려움이 있었습니다.

그렇다면 새로운 프로토콜을 만들어서 사용하면 되지 않나라는 의문이 생길 수 있습니다. 왜 굳이 UDP를 사용한 것이냐라는 생각이 들 수 있는 것입니다.

새 프로토콜의 배포는 쉽지 않은데, 사용자와 서버 사이에 있는 TCP와 UDP만 허용하는 방화벽, NAT, 라우터 등의 설정에 따라 차단될 수 있기 때문입니다. 이를 프로토콜 고착화(ossification)라고 합니다.

게다가 네트워크 스택의 전송 프로토콜 계층에서 뭔가를 바꾼다는 것은 새로운 운영체제 커널은 갱신하고 프로토콜을 구현해 배포하는 것은 상당한 노력이 필요한 과정입니다.

따라서, UDP를 선택하게 되었고 UDP를 커스트마이징하면서 탄생하게 된 것이 QUIC 프로토콜입니다.

QUIC

HTTP/3의 가장 큰 특징은 QUIC 프로토콜을 사용하여 통신을 하는 프로토콜이라는 점입니다. Quick UDP Internet Connections 이름에서 알 수 있듯이 말그대로 UDP를 사용해서 빠르게 인터넷 연결을 하는 프로토콜입니다.

HTTP/2의 기반이 되는 SPDY는 사장되었지만, HTTP/3의 기반이 되는 QUIC은 RFC9000으로 표준화되어 있다는 점도 다릅니다.

위의 TCP/IP 4 Layers에서 볼 수 있듯이 HTTP/3의 계층 구조는 특이합니다.

왜냐하면 QUIC은 TCP + TLS + HTTP의 기능들을 모두 구현한 프로토콜이기 때문입니다. TCP의 프로토콜의 무결성 보장 알고리즘과 SSL이 이식됨으로써 높은 성능과 동시에 신뢰성을 충족시켰다고 보면 됩니다. 위의 그림을 보면 계층 위치가 비스듬히 걸쳐 있는데 그것이 이와 같은 이유 때문입니다.

쉽게 말하자면, Application 계층의 HTTP/3은 QUIC을 동작시키기 위해 있는 것이라고 보면 되고, QUIC은 UDP 기반으로 만들어졌기 때문에 Transport 계층의 UDP 위에서 동작한다고 보면 됩니다.

3-way handshake

기존에 TCP는 Connection을 맺기 위해서 3-way handshake 과정을 요구했습니다.

이것은 1RTT가 소모 되었으며, 만약 TLS 1.2의 기능도 사용한다면 이것은 3RTT로 늘어날 것입니다.

다음은 TLS의 버전 1.2와 1.3을 사용할 때의 Connection 과정과 QUIC의 Connection 과정을 볼 수 있습니다.

TLS는 세션 키를 이용하여 데이터의 암호화를 수행해야 하고, 세션 키를 생성하기 위해서 필요한 데이터를 주고 받기 위한 과정이 필요로 합니다.

이 과정에서 TLS 1.2는 2RTT가 소모되고 TLS 1.3은 1RTT가 소모됩니다.

QUIC 또한 암호화 작업을 위해서 세션 키를 생성하는 작업이 필요로 하는데 기본 방식은 TLS 1.3과 유사합니다.

클라이언트가 키 교환 매개변수(공개키)를 보내고 서버는 그것을 이용해서 프리마스터 시크릿을 생성후 공개키로 암호화 하여 클라이언트에게 보내면 이를 이용해서 세션 키를 생성할 수 있습니다.

추가적으로, QUIC은 제로 RTT의 기능도 제공을 합니다.

QUIC에서는 0-RTT 기능을 통해 클라이언트가 이전 세션의 키를 재사용하여 서버에게 초기 데이터를 빠르게 전송할 수 있습니다. 이는 세션 키가 협상되기 전에도 사용 가능합니다. 즉, 캐싱 기능을 활용하는 것입니다.

HOLB in TCP

HOLB(Head-Of-Line Blocking)은 HTTP/1.1에서부터 꾸준히 등장했던 문제입니다.

먼저, HTTP/1.1에서는 지속 커넥션 파이프라이닝을 사용하면서 패킷은 순차적으로 처리해야 하기 때문에 앞의 패킷의 처리가 늦어지면 뒤의 패킷을 처리하지 못하는 것으로 인해 발생했습니다.

HTTP/2에서는 하나의 Connection안에서 여러 스트림을 구축하면서 데이터 패킷들의 동시 전송이 가능했지만 결국 하나의 Connection 안에서 여러 스트림으로 전송된 후 수신측에서는 해당 패킷들을 순서대로 처리해야되기 때문에 HOLB가 발생했었습니다.

이것은 TCP 처리 방식으로 인해 발생하는 문제로, 이전 패킷들이 "손실" 되어서 이후 패킷들이 도착하더라도 버퍼에 저장하고 있고 이전 패킷이 도착할 때까지 처리하지 못하는 문제를 가지고 있었습니다.

UDP를 사용하면서 이러한 문제를 해결하였고, QUIC은 아예 스트림 자체를 독립적으로 여러개 나누어서 처리하도록 하였습니다. 이를 독립 스트림이라고 합니다.

패킷 손실로 인한 전체 대역폭 감소

위에서 QUIC 프로토콜은 "독립 스트림"의 형태로 패킷들을 전송하고 처리한다고 했습니다.

HTTP/2의 멀티플렉싱의 경우에는 하나의 Connection안에서 여러 스트림이 존재하고 단 하나의 스트림에서 "패킷 손실"이 발생해도 이것은 하나의 Connection안에서 "패킷 손실"로 간주하였기 때문에, Slow Start 현상이 발생하고 전체 대역폭은 감소하게 되었습니다.

QUIC 프로토콜은 "독립 스트림"을 사용하게 되면서 이러한 문제점을 해결했습니다.

패킷 손실로 인한 RTT 계산의 복잡성

QUIC도 TCP와 마찬가지로 전송하는 패킷에 대한 흐름 제어를 해야합니다. 왜냐면 QUIC이든 TCP든 결국 본질적으로 ARQ 방식을 사용하는 프로토콜이기 때문입니다.

통신 과정에서 발생한 에러를 어떻게 처리할 것인가를 이야기 하는 것인데, ARQ방식은 에러가 발생하면 재전송을 통해 에러를 복구하는 방식을 말하는 것입니다.

TCP는 여러 ARQ 방식 중에서 Stop and Wait ARQ 방식을 사용하고 있습니다. 이 방식은 송신 측이 패킷을 보낸 후 타이머를 사용하여 시간을 재고, 일정 시간이 경과해도 수신 측이 적절한 답변을 주지 않는다면(타임아웃) 패킷이 손실된 것으로 간주하고 해당 패킷을 다시 보내는 방식입니다.

우선 2017년 구글에서 발표한 QUIC Loss Detection and Congestion Control에 따르면, QUIC은 기본적으로 TCP와 유사한 방법으로 패킷 손실을 탐지하나, 몇 가지 개선 사항을 추가한 것으로 보입니다.

TCP에서 패킷 손실 감지에 대한 대표적인 문제는 송신 측이 패킷을 수신측으로 보내고 난 후 얼마나 기다려줄인가, 즉 타임 아웃을 언제 낼 것인가를 동적으로 계산해야 한다는 것입니다. 이때 이 시간을 RTO(Retransmission Time Out)라고 하는데, 이때 필요한 데이터가 바로 RTT(Round Trip Time)의 샘플들 입니다.

한번 패킷을 보낸 후 잘 받았다는 응답을 받을 때 걸렸던 시간을 측정해서 동적으로 타임 아웃을 정하는 것입니다. 즉, RTT 샘플을 측정하기 위해서는 반드시 송신 측으로부터 ACK를 받아야하는데, 정상적인 상황에서는 딱히 문제가 없으나 타임 아웃이 발생해서 패킷 손실일 일어나게 되면 RTT 계산이 애매해집니다.

패킷 전송 -> 타임 아웃 -> 패킷 재전송 -> ACK 수신
(첫번째 패킷의 ACK인지, 재전송된 패킷의 ACK인지 알 수 없다.)

이때 이 ACK가 어느 패킷에 대한 응답인지 알기 위해서는 타임스탬프를 패킷에 찍어주는 등 별도의 방법을 또 사용해야하고, 또 이를 위한 패킷 검사를 따로 해줘야 합니다. 이를 재전송 모호성(Retransmission Ambiguity)이라고 합니다.

이 문제를 해결하기 위해서 QUIC는 헤더에 별도의 패킷 번호 공간을 부여했습니다. 이 패킷 번호는 패킷의 전송 순서 자체만을 나타내며, 재전송시 동일한 번호가 전송되는 시퀀스 번호와는 다르게 매 전송마다 monotonic하게 패킷 번호가 증가하기 때문에, 패킷의 전송 순서를 명확하게 파악할 수 있습니다.

TCP의 경우 타임스탬프를 사용할 수 있는 상황이라면 타임스탬프를 통해 패킷의 전송 순서를 파악할 수 있지만, 만약 사용할 수 없는 경우 시퀀스 번호에 기반하여 암묵적으로 전송 순서를 추론할 수 밖에 없습니다.
QUIC는 이런 불필요한 과정을 패킷마다 고유한 패킷 번호를 통해 타파함으로써 패킷 손실 감지에 걸리는 시간을 단축할 수 있습니다.

이 외에도 QUIC는 대략 5가지 정도의 기법을 사용하여 이 패킷 손실 감지에 걸리는 시간을 단축시켰는데, 자세한 내용은 QUIC Loss Detection and Congestion Control의 3.1 Relevant Differences Between QUIC and TCP 챕터를 통해 확인할 수 있습니다.

네트워크 환경 변화로 인한 재연결

TCP는 Connection을 맺기 위해 4가지 고유한 값을 가지고 있어야 합니다. 그것은 송신자 IP, 수신자 IP, 송신자 Port 번호, 수신자 Port 번호입니다.

이것을 통해서 Connection을 확인하고 데이터는 이 값들을 이용해서 각 호스트에게 전달됩니다.

하지만, 네트워크 환경이 변화된다면 IP 주소가 변경될 수 있고 이러한 상황에서 TCP는 새로운 Connection을 맺어야 되는 상황이 발생합니다.

결국 3-way handshake과정이 다시 이루어져야 하고, 이러한 과정으로 인해 레이턴시가 발생합니다.

요즘에는 모바일로 인터넷을 사용하는 경우가 많기 때문에 Wi-Fi에서 셀룰러로 전환되거나 그 반대의 경우, 혹은 다른 Wi-Fi로 변경되는 경우가 잦기 때문에 이 문제가 더 눈에 띕니다.

반면 QUIC은 Connection ID를 사용하여 서버와 연결합니다. Connection ID는 랜덤한 값일 뿐, 클라이언트 IP와 전혀 무관한 데이터이기 때문에 클라이언트 IP가 변경되더라도 기존의 연결을 계속 유지할 수 있습니다. 이로 인해서 TCP에서 발생하는 추가 레이턴시를 피할 수 있습니다.

보안

HTTP/3와 그 기반 기술인 QUIC은 TLS 암호화를 기본적으로 사용합니다.

물론 UDP와 TLS의 결합 기술로 DTLS라는 기술도 있지만, 'TCP 재구현'이 목표 중 하나인 QUIC과는 지향하는 바가 다릅니다.

이처럼 기본적으로 QUIC 내에 TLS가 포함되어 있기 때문에 TCP와 다르게 헤더 영역도 같이 암호화됩니다.

기존에 암호화되지 않던 영역까지 암호화를 함으로써 보안을 더욱 강화했습니다.

정리

HTTP/3과 이전 버전의 HTTP들을 비교해가며 HTTP/3의 기능들의 필요성과 동작을 확인할 수 있었습니다.

최종적으로 HTTP/3의 기능들만 정리하자면 다음과 같이 정리를 할 수 있습니다.

  • QUIC 프로토콜 사용: UDP 기반의 QUIC 프로토콜을 사용함으로써 TCP로 인한 문제점들을 해결할 수 있었습니다.
  • "독립 스트림" 도입: 독립적으로 스트림을 구축하면서 패킷 손실에 대한 전체 지연을 막고, HOLB을 피할 수 있었습니다.
  • 연결 레이턴시 감소: QUIC 프로토콜은 기존의 TCP 와 SSL/TLS를 사용하면서 생기는 초기 연결 지연을 크게 줄였고, 캐싱 기능도 도입하여 경우에 따라 0RTT로 데이터를 전송할 수 있습니다.
  • Connection ID 도입: QUIC 프로토콜은 헤더에 전송 순서를 담을 수 있는 영역을 별도로 만들어서, 이것을 이용하여 RTT 계산을 더욱 빠르게 수행하여 이로 인한 지연을 줄일 수 있었습니다.
  • 보안 강화: QUIC 프로토콜은 내부적으로 TLS 기능을 탑재하면서 데이터의 본문만 암호화가 되었던 TLS와 다르게 헤더까지 암호화에 포함시킬 수 있어 보안을 더욱 강화시켰습니다.

참고한 사이트

profile
개발정리블로그

0개의 댓글