[Tranport Layer 03] TCP의 연결 설정

    Flow Control

    흐름제어란 receiver가 1대 1 차원에서 overflow되어 buffer에 넘치게 받은 정보를 받지 않도록 하며 receiver가 자신이 overflow되지 않을 만큼만 sender에게 알려주고, sender가 흘러넘치지 않은 정도의 정보로만 data를 전송하는 것을 목적으로 한다.

    receiver의 application이 갑자기 다른 일을 하느라 receiver의 buffer 빈 공간이 안만들어질 수 있다. 또는 receiver가 데이터를 받는 속도가 application이 data를 읽어가는 속도보다 빠를때 buffer가 가득 차는 상황이 발생할 수 있다. 

    이러한 정보는 recieve window라는 필드를 통해서 free space정보를 전송한다. (=rwnd field)

    RcvBuffer는 차있는 buffer와 차있지 않은 free buffer space를 모두 가르킨다. (default 값은 4K byte정도)

    TCP Connection management

    TCP는 데이터를 전송하기 이전에 연결설정이 되어야하는데 이 과정을 handshake라고 말한다. 

    - 상대방의 IP 주소와 port number를 확인하면서 connection을 수립하는데 동의하고 연결 parameter(시작하는 seq number)를 전달해준다. 

     

    연결설정할때 2way handshake로는 부족한 이유는 다음과 같다. 

    - 내가 상대편에게 연결설정을 하자고 메세지를 보냈을때 delay가 traffic 환경에 따라 가벼적이라서 언제 올지 모르는 이슈가 있다. 

    - 연결 설정을 하자고 요청했다가 답이 오지 않아 재전송을 했는데 그 이전의 연결설정 요청 데이터가 도착한다면 서버쪽만 열려있는 복잡한 상황이 발생한다. 

    - 연결 설정을 하자고 요청했다가 답이 오지 않아 재전송을 했고 그 이전의 연결설정 요청 데이터가 도착 + 데이터 재전송도 발생한 결과  서버쪽이 열려있고 중복된 데이터가 재전송된 경우 더 심각한 상황이 발생한다. 

    : 서버 한쪽만 열려있는 상황이 발생할 수 있고, 연결이 끊긴 이후에도 데이터를 받을 수 있는 환경이 되어버릴 수 있다. 따라서 TCP는 3 way handshake가 필요하다. 

     

    TCP 3-way handshake

    Client가 seq numb으로 init seq numb=x과 SYN bit = 1(너와 연결을 신청한다는 의미)을 보낸다. 

    이를 받은 server는 바로 Establish상태로 바뀌지 않는다. 서버 입장에서도 seq numb을 y로 지정해서 보내고, SYN bit=1과 Ack bit =1, ACK num = x+1을 셋팅해서 ACK이자 data를 보낸다. 

    이를 Client가 받으면 Establish 상태로 바뀌고, Client도 Ackbit=1로 설정하고, ACKnumb=y+1로 지정해서 보낸다. 이때 Client가 data를 실어보내는 것이 가능하다. 

    이를 server가 받으면 Establish상태로 변한다. 

     

    TCP Connection을 close하고싶을때 Client/server(보통 Client)가 FIN(정상종료)=1로 설정하여 보내어 종료한다. 이를 받은 상대방은 FIN과 ACK를 보내준다. 

     

    Congestion Control

    network가 감당할 수 없을 정도로 수많은 data가 쏟아지는 경우. 

    router의 buffer에 delay가 길어지기때문에 발생한다. 이가 극심해지면 packet loss가 발생한다. 

    flow control과 유사해보이지만 다르다.

    - flow control에서는 receiver와 sender의 buffer 상태만 1대1로 체크하면 되지만 congestion control은 보내는 데이터가 거쳐가는 통로에 문제가 발생하는 경우라서 1대1 개념이 아니다. 

    요즘에는 대역폭이 상당히 커져서 congestion control과 관련된 문제들이 덜 심각해졌다. 

    TCP는 어떻게 대처할까?

    Congestion Control이 발생할때 TCP의 현재 동작방식은  explicit하게 피드백을 받아서 움직이는 방식은 아니다. (가능은 하지만 사용x : ECN _ explicit congestion notification )

    delay가 발생/ data loss가 발생했음을 보고 Congestion level을 추측한다. 

     

     

    TCP의 Congestion Control

    router는 network core의 장치이기때문에 delay, loss가 발생했음을 확실하게 알수있는 주체이다. 하지만 router가 알려주는 정보를 사용하여 congestion control을 하지 않는다. router에게 부담을 주고싶지 않기 때문이다. 대신 sender가 평소보다 오랜 시간이 걸려서 도착지에 도착을 한다던지, loss때문에 재전송을 해야하는 상황을 통해 혼잡이 발생했음을 추측한다. 

     

    1. AIMD

    대역폭의 상당부분이 아무것도 하지 않으면 보내는 비율을 증가시킨다. (Additive Increase : 천천히 비율 증가)

    data loss, data delay가 발생한다. (Multiplicative Decrease: 절반만큼 줄인다. )

    그래프에서 y축은 한 RTT당 mss(maximum segment size)

    AIMD의 목표는 문제가 발생하지 않으면서도 max로 메세지를 보낼 수 있는 비율을 찾는것. 

    AIMD는 Slow start, congestion avoidance의 조합을 통해 네트워크 혼잡을 효율적으로 관리하고, 네트워크 자원의 사용을 최적화

     

    TCP Tahoe 버전 : loss가 발생하면 무조건 1MSS(1 segment)로 보냈다. 

    TCP의 Reno 버전 : 3번째 duplicate ACK가 발생한 상황 (gap에 해당하는 메세지가 사라진 상황)에서는 network가 완전히 막혀있는 상황은 아니라고 생각하여 1MSS 로 줄이면 다시 보내는 비율이 적정해질때까지 너무 오래걸린다. 따라서 절반 비율일때부터 시작하자. 

     

    Congestion Control은 1대1이 아닌 TCP 모두에 관련되어있다. 즉, 혼잡이 발생하면 나를 포함한 TCP sender들 모두가 영향을 받는다. 

    - 하지만 모두 동시에 혼잡제어를 실행하지 않는다.(분산 시스템) 각자 자기가 느끼는 시점부터 Congestion Control 실행.

    - AIMD를 여러 노드들이 각자 타이밍에 맞추어 진행하였더니 결국 모든 TCP sender에게 적용이 된다.  

     

    TCP congestion control

    TCP sender는 LastByteSent - LastByteAcked 가 cwnd를 넘지 않게 제한한다. 

    이때 cwnd의 값은 OS가 네트워크 상황에 따라 조정할 수 있다. cwnd값은 TCP sender가 TCP receiver에게 보낼 수 있는 최대 data 개수이다. 하나의 RTT동안에 cwnd만큼을 보낼 수 있고, TCP rate = cwnd (bytes/sec) / RTT로 표현될 수 있다. 

     

    2. Slow Start

    또 하나의 Congestion Control 기법중 하나가 Slow start 이다. 하지만 전혀 천천히 시작 아님주의.

    맨 처음 시작단계에서 cwnd를 1MSS로 셋팅하지만 RTT가 늘어갈수록 cwnd를 2배씩 급격하게 늘어간다. 

    2배씩 증가하다보니까 문제가 발생한다. 

     

    3. Congestion Avoidance

    Slow Start로 빠르게 2배씩 늘려나갔지만 어떤 시점 (= ssthresh ) 부터는 천천히 증가하는 (= linear하게 증가) congestion avoidance를 적용해준다. 

     

    4. TCP CUBIC ( = 3차함수)

    네트워크 혼잡은 어쩔 수 없다. 혼잡의 상황을 경험하기 전까지 최대로 보내는 것이 핵심이다. 

    congestion이 발생하는 시점이 거의 바뀌지 않는다고 가정하면 문제가 발생하기전까지는 linear하게 증가시키지 말고 3차함수로 공격적으로 증가시키자. ( = 문제가 발생하는 지점 전까지 congestion window를 급격하게 증가시키자. )

     

    k를 congestion 상황이 발생하는 시점의 포인트라고 가정했을때 지금 시점이 k와의 거리가 멀면 급격하게 증가, k와의 거리가 가까우면 천천히 증가. 

     

    TCP Congestion Control의 목표는 router에서 발생하는 bottleneck link지점을 통과하면서 문제가 없을 정도(loss가 없을정도)로 보내면서 최대치를 보내는것이 목표이다. bottle neck link를 기준으로 하기때문에 source가 보내는 비율을 늘려봤자 성능에 도움이 되지 않고 RTT만 늘어난다. 

     

    Delay-based TCP congestion control 

    구글에 제안한 것으로, RTT를 결국은 congestion level을 계산해보자는 아이디어이다.

    인터넷 표준은 아니다. loss를 발생시키지 않으면서 최대치로 데이터를 보내기위해 매 RTT마다 측정하여 반영하겠다. 

     

    1. minimun RTT를 계산한다. (= 네트워크 혼잡이 없을시 바람직한 RTT)

    2. cwnd / minimum RTT (= 네트워크 혼잡이 없을시 달성할 수 있는 최적의 throughput)을 계산하여 현 상황과 비교한다.

     

    실제 측정된 현 throughput을 최적의 throughput과 비교해서 매우 비슷하면, 네트워크 상황이 좋다는 의미이기에 cwnd를 linear하게 증가시킨다. 

    현 throughput이 최적의 throughput과 멀리 떨어져있다면 네트워크 상황이 심각하다는 의미이기에 cwnd를 linear하게 감소시킨다. 

     

    Explicit Congestion Notification

    위에서 알아본 것은 추측한 것으로 implicit congestion 이었다. 

    congestion이 발생했음을 가장 먼저 알 수 있는 주체는 router이다. 

    router(L3 layer)가 ip header의 ToS field에 혼잡 상황을 마킹하여 알려준다. 

    network 계층과 transport 계층의 긴밀한 협력이 필요하다.

     

    IP header에 ECN을 10/01 로 설정한 데이터를 router가 혼잡을 경험해 ECN을 11로 변경시키고(network layer), 이렇게 전달받은 destination은 source에게 TCP field에 마킹하여(transport layer) 보내는 비율을 조절해달라고 요청을 해야한다(ACK 내에 있는 ECE bit를 1로 설정하여 보낸다.)

     

    * ECE bit는 Explicit congestion experienced

     

    TCP Fairness

    만약 K개의 TCP 연결이 같은 bottleneck link를 사용해 R 의 bandwidth를 함께 쓸때 각각은 R/K만큼의 속도를 가진다. 

    사실 Fair한 상황을 만들기위해서라면 RTT가 같아야하며, 세션의개수도 같아야한다는 조건이 들어간다.

    하나의 bottleneck에서 두개의 연결이 있다고 가정했을 때 하나의 연결에 대한 throughput을 x축으로, 하나의 연결에 대한 throughput을 y축으로 나타냈을때 이는 y=x의 그래프에 해당하는 점에 수렴해야한다. 이때 AIMD로 늘어났다가 줄어들었다가 하면서 y=x에 수렴한다.

     

    모든 네트워크가 공평해야할까?

    no. 그럴필요없다.

    그냥 UDP를 사용하거나, Congestion control을 빼고 만든 TCP를 쓰면 된다. 

     

    또는 내가 TCP connection을 더 많이 열어서 내가 사용할 수 있는 대역폭을 더 크게 가져올 수 있다. 

    ex. 이미 9개의 TCP connection이 있는 상황에서 내가 1개만 열면 1/10의 대역폭을 차지할 수 있지만, 내가 11개의 TCP connection을 열게되면 11/20의 대역폭을 내가 차지할 수 있다. 

     

    QUIC : Quick UDP Internet Connections

    TCP, UDP는 40년도 넘은 transport 프로토콜이다. 

    새로운 환경변화가 있을 시에 어떻게 적용해나갔는지? 

    - 대역폭이 커졌고 sender와 receiver의 길이가 굉장히 길어진 상황이 있어, in-flight 패킷이 많아진 상황 : 연속해서 뭔가를 보내는 상황이므로 누락된 패킷을 알게되었을때까지 너무 오랜 시간이 걸려 3번째 duplicate packet을 받았을때 재전송을 한 규칙 -> 2번째로 duplicate를 받을때 재전송

    - 무선 네트워크 상항, TCP가 끊겼다가 새로운 TCP와 연결된 상황  : 아주 심각하지는 않아서 pass

    - 아주 긴 pipe라면 RTT가 굉장히 커진 상황이라 ACK가 돌아오는데 엄청 오래걸리는 상황 : congestion window를 크게 만들겠다. 

    - Data center network의 경우 traffic의 latency를 짧게해야하는 상황 :  미리 연결설정을 해놓아 overhead를 줄일 수 있다. 

    - 네트워크 관리 프로토콜로 네트워크의 장치가 정상적으로 작동하는지 모니터링해야하는 상황 : TCP에서 우선순위를 고려할 수 있도록 변경

     

    QUIC는 application protocol이다. 

    HTTPS 통신을 하면 가장 먼저 TCP/IP 프로토콜의 3 way handshake 과정을 통해 ISN번호를 교환한 다음, TLS 레이어에서 공개키 암호화 알고리즘을 이용해 서로가 사용할 대칭키를 만든다. 네트워크 대역폭은 키울 수 있지만, 빛의 속도는 상수이기 때문에 네트워크 왕복 시간(RTT)은 더 빨라질 수 없다. 우리는 불필요한 라운드-트립을 줄일 필요가 있다.

     

    우리는 TCP/TLS프로토콜에서 발생하는 2번의 라운드-트립을 한번으로 줄이고 싶다는 강한 욕망이 든다. QUIC은 UDP 프로토콜 위에서 구현되었는데 사실 말이 UDP지 그 위에다 TCP 프로토콜의 흐름 제어, 오류 제어, 혼잡 제어와 SPDY의 스트림 멀티플렉싱 기능이 모두 구현되어 있다. TCP 프로토콜은 OS 커널에 들어있기 때문에 이를 직접 변경하면 모든 네트워크 장비들이 OS 업데이트를 해야 한다. 구글 스스로도 안드로이드 파편화 문제를 통해 이게 말이 안된다는 것은 잘 알고 있기 때문에 UDP 위에서 새로운 프로토콜을 구현했다.

     

    2번의 RTT로 가능했던 것들이 QUIC에서는 OneRTT에 가능했다. QUIC에서는 처음의 연결설정시에 어떤 알고리즘을 통해 암호화를 하고, 암호화에 필요한 parameter도 함께 알려주는 방식으로 한번의 handshake를 통해 구현할 수 있었다.

     

    또한 QUIC은 각각의 데이터에 대해서 Encrypt와 RDT를 수행함에 따라 HOL Blocking을 없앨 수 있다. 

     

    Head-of-line(HOL) blocking 이란 TCP 전송 계층에서 패킷 손실이 발생하면서 뒤에 있는 요청들이 밀리는 현상을 말한다. 수신자는 데이터 순서를 보장하기 위해서 TCP Buffer에 패킷을 채우고 재조립한다. 만약 앞에서 패킷 일부가 누락되면 순서를 보장하기 위해 뒤에 오는 패킷들 전체가 버퍼에서 대기하고 있어야 한다. 이는 커널에서 이루어지는 일이기 때문에 애플리케이션에서는 마치 통신이 지연된 것 처럼 보이는데 이런 현상을 Head-of-line blocking이라고 부른다.

     

    QUIC에서는 하나의 커넥션에서 패킷의 성격을 스트림이라는 논리적인 단위로 분리해서 패킷을 전송하는 것으로 이 문제를 완화한다. 동일하게 패킷 순서를 보장하지만, 패킷 손실에 의한 HOL Delay는 한 스트림으로 제한된다.

    예를 들어, HTTP/2와 같은 상위 프로토콜이 TCP 위에서 작동하고 있을 때, 여러 요청이 순차적으로 전송된다고 가정해봅시다. 첫 번째 요청의 패킷이 손실되면, 뒤에 오는 모든 요청들은 첫 번째 요청의 패킷이 재전송될 때까지 대기하게 된다.

    반면에 QUIC에서는 각 HTTP 요청이 독립적인 스트림으로 전송됩니다. 따라서 첫 번째 요청의 패킷이 손실되더라도, 두 번째, 세 번째 요청의 패킷들은 즉시 처리될 수 있습니다. 첫 번째 스트림에서 패킷 손실이 발생해도, 나머지 스트림들은 영향을 받지 않고 계속 전송됩니다.

     

     


    Reference

    https://medium.com/rate-labs/quic-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-%EA%B5%AC%EA%B8%80-%EB%98%90-%EB%84%88%EC%95%BC-932befde91a1

    댓글