[ComputertNetwork/TCP/L4] Layer 4와 TCP

SHark·2025년 2월 4일

ComputerNetwork

목록 보기
3/7
post-thumbnail

전송을 담당하고, 연결의 개념이 생긴 L4입니다. 네트워크 프로그래밍을 하게되면, Socket API를 이용하게 됩니다. Socket API가 L7과 L4간의 통신을 담당하는 API라고 생각하면 좀 와닿을까요?

사실, L4는 OS에게 꽤 많이 의존하는 계층이기도 합니다. 하지만, Socket을 활용하는 단계에서 TCP에 대한 이해도가 없으면 에러처리를 못하고, 온라인 MMORPG 서버와 같이 Session들을 직접적으로 관리하는 서버들은 네트워크 라이브러리도 개발자가 직접 작성하기 때문에, 정확한 이해가 필요합니다. UDP는 따로 다루고, 가장 메인이되는 TCP에 대해서 이번글에서는 쭉 다루도록 하겠습니다.(고봉밥 주의)

지연과 손실

통신을 할 때, 지연과 손실을 수신자 입장에서는 구분하기 힘들다는 느낌이 오시나요?

  • 해당 세그먼트가 먼 길을 돌아오느라 지금 도착을 하지 않았는지
  • 해당 세그먼트가 중간에 없어져서 지금 도착을 하지 않았는지

둘을 수신자 입장에서 본다면, 패킷이 안왔다는 사실은 명확하지만, 이유는 무엇인지 확정을 할 수 없습니다. 따라서, TCP는 위 2가지 상황을 모두 커버할 수 있도록 구현이 되었습니다. 먼 길을 돌아온다면, 송신자는 이미 재전송을 했는데 나중에 똑같은 데이터가 2개 이상 중복되는 현상이 발생할 수 있습니다. 그리고, 중간에 없어졌다면 재전송을 해야합니다.

Reliable Transport

  • 신뢰성있는 통신은 Packet Error, Packet Loss를 대처할 수 있음을 이야기 합니다.
  • TCP는 대표적인 Reliable Protocol(신뢰성 있는 프로토콜) 입니다.

Reliable를 확보한다는 의미

Packet Error와 PacketLoss 2가지에 대해, 대응할 수 있다면 Reliable을 확보했다고 할 수 있습니다. 2가지를 어떻게 대응할지 이야기 해보겠습니다. PacketError는 사실, 쉽고 PacketLoss에 대한 이야기가 주를 이룹니다.

Packet Error

  • Packet Error는 체크섬(CRC)을 활용하여 확인하는 걸 L3,L2에서도 하기 때문에 쉽게 떠올릴 수 있습니다.
  • TCP 또한, 체크섬을 활용하여 PacketError는 검출할 수 있습니다.

RetransMission

  • 라우터의 한계 혹은 TTL 만료 등으로 Packet은 사라지는게 일상입니다.
  • Internet Protocol은 파편화를 통해서, 다양한 경로로 전송이 됩니다. Packet이 1,2,3,4 순으로 보낸다 하더라도, 도착할 때 1,2,3,4로 도착한다는 보장이 없습니다.

이 상황에서 어떻게 , 신뢰성을 확보할까요? 아니 , 우선 Packet이 사라지는 상황을 어떻게 무마시킬 수 있을까요? 당장 제가 생각하는건 2가지 케이스가 있습니다.

  1. 재전송
  2. 사라진 데이터 복구하기

둘 중, 재전송이 더 쉬운 방법인건 명확합니다. 또한, 사라진 데이터를 복구한다면, 복구를 위한 다른 데이터를 추가적으로 넣어야할 텐데, 그게 또 사라진다면 ..? 이라는 재귀적인 물음에 빠지게 됩니다. 따라서, TCP는 기본적으로 재전송을 하게 됩니다.

재전송의 주체는?

그렇다면, 재전송을 하기로 결정했다면 누가 재전송을 할지 정해야겠죠. 이것도 당장 생각나는건 2가지 케이스가 있습니다.

  1. 결론적으로, Internet Protocol 때문에 (Router들 때문에) 사라지니까 Router가 너가 중간에서 알잘딱하게 해라.
  2. 아니다, Host에서 보냈잖아. Router는 경로 처리한다고 바쁘니까, host가 한번 더 보내자.

제일 큰 이유는, Router가 이걸 담당하게 된다면, 네트워크 장비의 전송효율이 매우매우 떨어진다는 단점이 있습니다.

Router가 재전송을 담당한다?

물론, 해줄 수도 있습니다. Router에서 재전송을 해준다면, Router마다 어떤 패킷을 내가 어디로 보냈는지에 대한 정보를 다 관리해야하고, 상대방이 받았는지 받지 않았는지에 대한 확인도 해주면 됩니다.

하지만, Router는 초당 수만개의 Packet들이 우르르르르 지나가는데, 수만개의 패킷들이 폭포처럼 쏟아지는 상황에서 하나하나에 대한 컨트롤을 하게되면, Router장비는 엄청난 고성능장비가 될 수 밖에 없고, 어쩌면 컴퓨터보다 더 비싸고 중요한 장치가 될 수 밖에 없습니다.

여기서, 제가 네트워크 개요에서 말했던게 있습니다. 똑똑한 호스트와 단순한 네트워크. 네트워크 장비는 최대한 단순하게 동작해야합니다. 또, 설치했을 때, 복구가 쉬워야합니다.

따라서, 네트워크의 이념에도 맞지 않았고, 굉장히 비효율적이었기 때문에 재전송의 담당은 Host가 맡게 되었습니다.

재전송시 문제점

재전송을 했다고 해서, 신뢰성이 확보될까요? 재전송만 보내고, 제대로 갔겠지~하는 것은 책임감이 너무 없지 않나요? 아니, 애초에 전송을 했다면, 상대방이 받았다면 받았다는 확인작업이 있다면 전송작업 자체가 신뢰성이 있게 됩니다. 따라서, TCP는 수신자가 패킷을 수신했는지의 여부를 확인하도록 되어있습니다. 이 방법이 Ack입니다.

ACK는 특이한 방법은 아닙니다. 우리가 일상생활에서 많이 하는 짓이기도 합니다. 전화를 할 때, 상대방의 말을 듣고있다는 표시로 혹은 어어라는 응답을 하게 됩니다.

또, 재전송을 했을 때 큰 문제점이 하나 더 있습니다. 바로, 중복재전송을 구분하지 못한다 입니다. 예를들어, 정신나갈거같아 정신나갈거같아와 같이 똑같은 단어를 2개 붙여서 상대방에게 전송을 했다고 했을 때, 2번째 정신나날거같아가 없어졌다고 칩시다.

수신자 입장에서는 똑같은 내용이 중간에 사라졌다고 Sender가 판단해서 재전송된 결과인지, 진짜 똑같은 글자가 2개가 왔는 것인지 모르게 됩니다.

보내는 데이터에 Numbering(순서를 매김)을 하면, 이 문제는 쉽게해결됩니다. 그래서, TCP는 SeqNumber를 처음에 맞추고, 통신하는 데이터의 크기에 따라서 seqNum을 점점 늘려가는 방식으로 동작하게 됩니다.

TCP Header

SeqNumber

  • 4byte(32bit)로 이루어진 값입니다.
  • 첫 시작값은 제 3자가 알아내지 못하도록 랜덤한 아주 큰값을 OS에서 그때그때 할당해줍니다.
    • 보안상 목적. 제 3자가 첫 시작값을 알아낼 수 있다면, 제 3자가 Ack를 주면서 통신을 가로챌 수 있음. TCP Header를 조작해서 IP,PORT값을 지금 송신하는 상대방껄 넣고 ACK 번호 맞추는걸 시도하면 중간 중간에 이상한 데이터를 보내게 할 수 있음.
  • SeqNumber를 교환하는 과정이 사실 3way HandShaking입니다.

ACK 매커니즘

  • 4byte(32bit)로 이루어진 값입니다.
  • 상대방이 보낸 데이터를 모두 받았다는 의미의 값입니다. 혹은, 상대방이 보내야할 데이터의 순서를 의미하기 합니다.
    • 예를들어, Seq:0 시작에, 1400byte를 보냈다면 0번부터 1399번까지의 byte들이 전송되었다는 의미입니다. 따라서, Ack는 1400을 요구합니다. (1400번째 데이터를 보내줘! / 난 1400byte 받았어!)

Ack가 유실된다면?

TCP는 양방향 통신이기 때문에, 송/수신자 입장을 모두 고려해야합니다. 자신이 보내는 패킷(송신자) 입장에서 해당 패킷이 사라진다면 재전송을 한다고 정해놨는데, Ack 또한 똑같이 IP를 따라서 전송되기 때문에 ACK도 유실될 수 있습니다.

기본동작은 "송신자"가 책임지고 재전송한다이기 때문에, Ack가 중간에 유실된다면 전송에 실패했다고 생각하고 송신자 측에서 재전송을 하게 됩니다. ACK가 유실된다면, 수신자 측에서 재전송을 하지 않습니다.

현실적으로는 대부분의 TCP는 구현할 때 여러개의 패킷을 TCP는 동시에 보내고, ACK 또한 여러개의 Ack가 동시에 들어오기 때문에 가장 최신의 ACK를 기준으로 계산합니다. 예를들어, 10개의 패킷을 1byte씩 보냈다면, Ack가 1~10까지 돌아올겁니다. 이때, Ack 3번이 사라졌다고해도 뒤에 ack 4,5,6,7,8~10까지 송신자측으로 수신이 된다면 10까지 무사히 받은걸로 인식해도 문제가 없습니다.

Ack의 순서가 뒤바껴서 온다면?

10개의 패킷을 1byte씩 보냈다면, Ack가 1~10까지 돌아올겁니다. 이때, Ack 10번이 사라졌다고해도 뒤에 ack 4,5보다 송신자측으로 수신 된다면 10까지 무사히 받은걸로 인식합니다.

가장 큰 ACK를 인정하고, 나머지는 다 무시합니다.

Delayed Ack

위 그림같이, 연속적인 세그먼트를 받는다면 1개의 세그먼트당 1개의 ack를 정직하게 준다면 ack로 인한 통신도 꽤 많이 발생하기 때문에 Ack횟수를 줄이기 위해 제안된 개념입니다. (RFC 1122)

  • 수신자가 Delayed Ack Timer가 만료되지 않은 상태에서 다른 세그먼트를 받으면 ACK 바로 전송
  • 수신자가 세그먼트를 받고, Delayed Ack Timer가 만료된다면, ACK 전송

Fast-Retransmission

타이머는 TCP 입장에서보면 굉장히 느립니다. 왜냐하면, 타이머는 기본적으로 이정도가 지났는데도 안와?라는 입장에서 타임아웃이 발생하는 것이기 때문에, 이 타임아웃을 모두 기다리는건 비효율적이라고 느낄 수 있습니다.

ACK는 중간에 송신되는 Packet이 유실되었다면, 자신이 받은 곳까지의 세그먼트번호를 보내게됩니다. 따라서, TCP를 구현하는 사람들이 똑같은 ACK가 중복적으로 오는 상황이라면, 패킷 Loss 됬다고 보고, 재전송을 하는게 맞지않아? 라는 아이디어 입니다.

단, 이때 발신자에 도착한 중복 ACK는 패킷 LOSS 뿐만 아니라, 패킷의 전달이 지연됐을 때도 ACK가 중복적으로 올 수 있기 때문에 무작정 중복 ACK가 감지됐다고 재전송을 하지 않습니다. 보통 3~5번정도 똑같은 Ack가 감지되면 송신자 측에서 재전송하도록 구현이 되어있습니다.

Selective Ack

RFC2018에서 제안된 개념으로, 꽤 최신사양이며 지원을 하는 구현체도 있고, 안하는 구현체도 있습니다.(대부분 지원함)

Selective Ack는 재전송 과정에서 너가 받은 데이터는 내가 재전송하기 싫어. 비효율적이잖아? 대신, 너가 못받은것만 보내줄게! 라는 아이디어에서 나온 매커니즘입니다.

연결성립 과정에서 SACK permitted라는 필드를 통해 송/수신자에서 서로 합의합니다. 송수신자 모두 SACK지원이 가능하다면, Sack를 사용하게 됩니다.

SACK는 자신이 받은 블록의 왼쪽과 오른쪽을 표시해줌으로써, 상대방에게 나의 hole(못받은 부분)을 보내도록 유도합니다. Sack로 인해서, 못받은 것만 보내도록 유도를 했으나 성능적인 측면에서는 큰 효과를 보지는 못하고 한번의 RTT(한번의 왕복 통신)에서 2개 이상의 Hole을 채울 수 있다는 점이 크게 작용한다고 합니다.

PipeLining

TCP는 전송시, 하나를 전송하고 ACK를 받고, 하나를 전송하고 ACK를 받고.. 이런 식으로 티키타카(?)식으로 동작하지 않습니다. 매우 비효율적이고, 네트워크를 효율적으로 쓰지 못하기 때문에, TCP는 여러개의 Sequnce를 쫙 보내고, Ack도 쭉 받는 식으로 구현이 됩니다.

이러한 방법을 PipeLining이라고도 하고, 구현하는 방법이 Sliding Window라는 자료구조를 통해 구현하기 때문에 Sliding Window라고도 합니다. 그림을 보면, 더 쉽게 이해되기 때문에 그림으로 설명하겠습니다.

WindowSize가 4라고 했을 때, 해당 윈도우 크기만큼 세그먼트들을 보내고, ACK가 오는것에 따라서 Window를 이동시키는 원리로 여러개를 보내고, Ack를 반영하는 방식으로 TCP는 동작하게 됩니다.

Window Scale

TCP Header를 보면, Window Size라고 서로의 윈도우 사이즈를 교환하게 됩니다. 이는 상대방의 처리속도에 맞게 통신을 하도록 하기위한 Flow Control의 일종입니다. 만약, 수신측이 송신측보다 속도가 느리다면, 송신측은 계속해서 무리한 트래픽을 줄 것이고, 이는 수신측에서 보면 공격이나 다름이 없습니다.

WindowSize는 16bit로, 최대크기가 64K입니다. 하지만, GB 통신 시대에 해당 크기는 매우 작기 때문에, 대부분의 통신은 Option에 Window Scale이라는 옵션이 들어갑니다. Window Option은 Shift연산을 해서, 2^n연산을 하도록 되어있습니다. 예를들어, 3이면 x8을 하게됩니다. SYN하는 과정에서 Option을 교환하게 됩니다.

RTO (Retransmission TimeOut)

ACK가 유실된 상태에서도, 송신자가 책임지고 전송을 해준다고 했습니다. 이때, ACK를 받지 못한다면 송신자 측에서 무한히 기다릴 순 없기 때문에 TCP는 기본적으로 타이머를 기반으로 한 재전송 메커니즘도 보유하고 있습니다.

전송을 시작할 때, 타이머를 재기시작해서, 전송에 성공했다면(Ack를 받았다면) 타이머를 폐기하고 윈도우를 움직이고, 다시 타이머를 셋팅합니다. RTO를 그럼 , 어떤 기준으로 정하는게 좋을지에 대한 토론이 이루어져야 하는데 이는 쉬운 문제가 아니기 때문에, 현재 사용하는 방식만 언급하고 가겠습니다. 깊게 들어가면 연구분야기 때문에..

따라서, 현재 사용하는 방식을 보자면 처음 대기시간을 정한다음(보통, RTT의 평균보다 조금 더 큰값. RTT를 OS가 계속추적하도록 구현해놓음), 재전송시에 대기시간을 점차 더 늘려가면서(RTO를 더 크게하면서) 전송을하게 됩니다.

예를들어, 처음대기시간이 2초라면, 다음 재전송하고는 4초를 기다리고, 또 재전송하면 8초동안 기다려보고..이런식으로 대기시간을 점차 늘려가면서 재전송을 하게됩니다. 네트워크가 느리다고 판단해서, 좀 더 기다려주는 느낌입니다.

TCP의 전반적인 느낌과 흐름에 대해서 소개하는 글입니다. 다 이해가 가실수도, 이해가 가지 않을 수도 있습니다. 다음편에는 TCP의 연결수립과 연결해제와 해당 상황 속에서 일어나는 상황들에 대해 생각해보도록 하겠습니다.

0개의 댓글