아롱이 탐험대

Transmission Control Protocol (TCP) (3) 본문

CS/Computer network

Transmission Control Protocol (TCP) (3)

ys_cs17 2022. 4. 2. 15:53
반응형

Flow control (흐름 제어)

지금까지 우리가 살펴본 TCP의 패킷 전달 방식은 Stop & wait 방식이다. 이는 client 측에서 SYN을 보낸 후 서버와 서로 데이터의 전달을 확인했는지 ACK을 보내는 방식이다. 예를 들어 데이터 5개를 보낸다면 5개의 ACK을 받아야 하는 방식이었다.

이는 구현 측면에서 매우 간단하지만, 컴퓨터의 입장으로써는 매우 비효율적인 방법이다.

예를 들어서 설명하자면 서울에서 부산에 택배를 보낸다고 가정하자. 왕복 시간은 10시간이고, 우리는 총 10개의 트럭을 보내야 한다. stop & wait 방식을 적용하면 트럭 1대가 부산을 왕복할 때 동안 다른 트럭들은 대기하고 있는 상태라고 생각하면 된다. 10시간에 1대만 보낸다는 것이다. 이는 매우 비효율적이다. 따라서 트럭을 한꺼번에 많이 보내야 되는데 이 방식은 오늘 알아볼 FLow Control이다.

Flow control방식에서 우리는 window size를 정해야 하는데 이는 실질적으로 가변적이다. window size는 우리 예시에서 한꺼번에 몇 대의 트럭을 보낼 것이냐를 의미한다.

용어의 대한 정리도 다시 필요한데, 보통 데이터 전송 시에는 sender, reciever라고 표현하고, 연결 및 종료에서는 client, server라고 표현한다. (client, server 둘 다 sender, reciever가 될 수 있다.)

[그림 1] send window

[그림 1]을 살펴보자. 현재 send window에는 내가 보낼 data byte들이 존재한다. window size는 내가 byte를 보내고, 해당 byte가 돌아오기 전까지 보낼 수 있는 최대 값이다. 내가 byte를 보냈는데 아직 돌아오지 않은 byte들이 바로 주황색으로 표기된 outstanding byte이다. (ACK이 아직 돌아오지 않았다는 의미) window는 왼쪽에서 오른쪽 방향으로 밀려가면서 새로운 byte를 보내게 된다. 이를 sliding window라고 부른다.

window size에서 핵심적인 내용은 기존 stop & wait를 사용하면 단순하지만 굉장히 비효율적이다. 따라서 byte가 오기 전까지 더 많은 bytes를 보내자는 것이다. window에서 얼마나 보냈고, 어디부터 보내야 되는지는 포인터 변수로 관리한다.

[그림 2] Receive window and allocated buffer

[그림 2]는 원처럼 생긴 recieving buffer를 펼쳐둔 것이다. 주황색 부분은 byte를 할당받은 상태지만 아직 process가 read를 하지 않은 것들이고, application에서 호출을 하면 비어지게 된다. 주황색 부분의 오른쪽은 아직 비어있는 공간이다. sender가 byte를 보내면 해당 부분이 채워진다.

window size를 정하는 방법은 크게 2가지가 있다. 우선 첫 번째 방법은 rwnd이다.

window size에서 rwnd는 reciever buffer의 비어있는 공간이다. rwnd를 통해 비어있는 공간을 상대방에게 알려준다. sender는 한 번에 데이터를 많이 보낸다.

reciever buffer가 만약 전체 사이즈가 K라면, sender는 K 이하로 데이터를 보내야 한다. 처음 SYN+ACK을 주고받고 나 서로 가정하면 sender는 K보다는 많이 보내지 않는다. 왜냐하면 그만큼 reciever의 공간이 없기 때문이다. 따라서 최초 연결할 때는 K만큼 데이터를 보낸다. 여기서 K의 크기를 sender는 어떻게 알까? sender는 TCP의 header의 16 bits의 window size로 상대방 recieving buffer의 크기를 알 수 있다.  ACK을 보낼 때 rwnd size를 알려준다. 이는 window size header에 실려간다.

최초의 빈 공간은 연결 셋업을 진행할 때 SYN+ACK을 보낼 때 알려준다. 

여기서 잠깐, 프로그램에서 write()가 block 되는 경우는 아직 설명하지 않았다. write()를 호출하면 전송할 data를 array에 저장하는데, 우리 눈에는 실제로 보내는 것처럼 보인다. write()를 엄밀히 말하면 app단에서 sending buffer에 data를 두는 함수이다. write()는 데이터를 보내는 함수가 아니다. 근데 flow control처럼 처음에 보낼 데이터가 많으면 sending buffer를 꽉 채우거나, 언젠가는 sending buffer가 꽉 찰 상황이 존재한다. write()는 sending buffer에 더 이상 data를 두지 못할 때 block이 된다.

[그림 3] rwnd sample

[그림 4]에서 server의 recieving buffer의 크기는 5000, client의 recieving buffer 크기는 10000인 것을 rwnd를 통해 알 수 있다.

window size가 어떻게 변화하는지 살펴보자. 아까와 같은 예시로 서울에서 부산으로 트럭을 보낸다고 가정하자. 연결 셋업을 통해 부산에 남은 공간을 알아낸다. (12개라고 가정) 그러면 서울에서는 12개의 트럭을 보내려 할 것이다. 부산에 첫 번째 트럭이 도착하면 이에 대한 응답을 보내준다. (12개 중 1개의 대한 응답) 이때 2번을 달라는 의미로 ACK:2와 rwnd: 11를 같이 보낸다. (아직 부산에는 빈 공간이 11만큼 있고, 2번 데이터를 받아야 된다는 것을 의미)

sender 입장에서 1번을 먼저 보내면 window를 오른쪽으로 한 칸 옮긴다. 그리고 rwnd:11을 받았으니 2부터 시작해 크기가 11인 window를 짠다.

reciever가 2번을 받을 경우를 보자. 이번엔 ACK:3과 rwnd:10을 보낸다. 그러면 이를 받은 sender는 window를 한 칸 옮기고, 남은 공간과 rwnd를 확인한다. 지금 보내도 버퍼가 차있으니 보내지 않고 대기한다.

reciever 측에서 app단에서 buffer에 있는 data를 빨리 가져가면 reciever buffer의 공간은 빠르게 넓어진다.

[그림 4] Flow control feedback

[그림 4]를 보면 1번에 대한 속도는 data가 ACK이 와야 sending buffer 쪽의 공간이 생긴다. ACK이 오기 전까지는 sending buffer는 채워둔다. 전달하는 data를 상대가 못 받을 수 있기 때문에 ACK으로 data 도착 확인을 받고 sending buffer에서 보낸 data buffer를 없앤다. 2번은 3번보다 크면 안 된다. reciever buffer의 빈 공간들은 4번을 통해 간다. 2번은 4번의 사이즈와 같다.

따지고 보면 2번의 전송 속도가 1번이 된다. 1번 속도는 3번 속도가 된다. 이를 톱니바퀴 구조로 생각하면 된다. 어떠한 app에서 무엇을 쓰냐에 따라 각 속도들이 달라진다. 영상 처리와 같이 무거운 프로그램이면 3번에서 딜레이가 생길 수 있다. 아무튼 2번은 3번이 얼마나 빠르냐에 따라서 달라진다.

결론은 flow control은 window size에 따라 보내는 사이즈를 다르게 한다. reciever가 받는 속도에 따라 속도가 달라진다. 따라서 시간당 보내는 속도가 달라진다는 의미는 transfer wait가 달라진다는 것이다.

[그림 5] An example of flow control

구체적인 예시인 [그림 5]를 보자. 처음 seq number: 100으로 SYN을 요청하고, 이에 대한 SYN+ACK을 받는다. 이때 server의 recieving buffer 공간인 rwnd: 800도 같이 보낸다. 이때 client는 window size를 800으로 세팅한다. 이는 한꺼번에 800만큼 데이터를 보낼 수 있다는 의미이다. 그다음을 보면 sender는 200 byte만큼의 data를 전송한다. data가 200만 있기 때문이다. 아무튼 101번부터 300까지 보낸다. 이를 server가 받으면 남은 공간은 600이 된다. 이를 rwnd: 600을 통해 알려준다. 이때 sender는 window를 600으로 만든다. client는 data가 300밖에 없어 301~600을 보낸다. 그리고 app단에서 100을 읽었다고 가정하면 server recieving buffer는 공간이 400만큼 남게 된다. 이를 다시 ack:601과 rwnd: 400을 통해 알려준다. 그러면 sender는 다시 window size를 400으로 세팅한다.

8번의 경우에는 app단에서 recieving buffer를 읽고 난 후 rwnd로 남은 공간을 갱신해서 알려주는데 이는 일반적으로는 하지 않는다. 아무튼 rwnd: 600을 받은 sender는 window size를 600으로 다시 세팅한다.

 

Silly Window Syndrome

'A'라는 문자 하나를 보낸다고 가정하자. 이를 보내기 위해서는 TCP header가 필요하다. header는 20 byte, data는 1byte, IP도 20 byte, 프레임으로 가면 MAC header도 필요하다. 우선 이것만 가정해도 1byte를 보내기 위해서는 많은 byte가 필요하다. 이러한 문제점이 송신 측에서 발생하는 syndrome이고, 보내는 데이터에 비해 header의 크기가 훨씬 큰 경우를 silly window syndrome이라고 한다.

이를 해결하고자 nagle 알고리즘을 사용한다.

[그림 6] Nagle example

'A' 문자를 보내고 서버로부터 ACK이 올 때를 생각해보자. nagle 알고리즘에서는 첫 번째 데이터는 그냥 보내고, 보낼 데이터가 MSS 만큼 쌓이면 상대한테 보낸다. MSS보다 작을 경우네는 보낸 데이터에 대한 ACK을 기다린다. ACK이 오기 전까지는 대기시켜두고, ACK이 오면 데이터를 전송한다.

MSS는 Maximum Segment Size의 약자이고, 이는 정의된 크기이다.

Nagel 알고리즘은 on/off 할 수 있는데, 항상 작동시키는 것이 좋은 것은 아니다. 바로바로 전송이 필요한 애플리케이션에서는 꺼야하고 그렇지 않은 경우에는 켜 둬야 한다. 애플리케이션마다 상이하다.

 

수신 측에서 발생하는 신드롬도 존재하는데 이것은 다음과 같다.

buffer가 가득 차있는데 app단에서는 1 byte만 읽은 경우와 같이, 수신 app에서 너무 조금만 읽을 때를 의미한다.

이를 해결하는 첫번째 방법은 Clark 방법이다. 이는 ACK을 보내고 rwnd를 0으로 보내준다. 이때 버퍼의 반이 비거나 MSS보다 빈 공간이 많으면 제대로 된 rwnd를 보낸다. 이는 시스템마다 방식이 다르다.

두 번째 방법은 확인 응답의 지연이다. 이는 ACK을 보내지 않고 기다리는 방법이다. 그러면 송신 측에서는 window size 만큼만 보낸 후 보내지 않는다. 일정 크기 이상의 빈 공간이 있을 때 ack을 보내주면서 다시 재개한다.

중단 속도는 Clark이 더 빠르다. clark 방법은 바로 송신 측에서 안 보내게 하고, 확인 응답의 지연은 서서히 멈추게 한다.

 

SYN Flooding

 

[그림 7] SYN flooding example

Client1이 SYN을 보내면 server는 SYN+ACK을 보낸 후 Client1을 연결 요청 대기 큐에 넣는다. 중간에 Client2가 SYN을 보내면 Client2도 연결 요청 대기 큐에 넣는다. Client 3도 똑같다.

여기서 Client1이 ACK을 보내면 accept 후 새로운 소켓을 통해 server와 client는 communication 한다. Client1이 종료된 후 Client 2를 accept 하고 이것도 Client 1과 똑같이 communication 한다.

하지만 Client는 자기 주소가 아니라 다른 주소로 SYN을 보낼 수 있다. 택배도 받는 사람만 제대로 입력하면 보내는 사람은 상관이 없는 것처럼 말이다. raw socket을 사용하면 header를 커스텀할 수 있다. 이를 통해 보내는 주소를 임의적으로 바꿀 수 있다. 그러면 서버는 바뀐 주소로 SYN+ACK을 보내준다. 그러면 Client로부터 ACK이 오지 않아 계속 대기하게 된다. 만약 여기서 다른 주소로 SYN을 보내면 서버는 그 주소로 SYN+ACK을 보낸다. 이때도 ACK은 서버로 오지 않는다.

이런 식으로 악의적으로 계속 임의의 주소로 SYN을 보내주면 연결 요청 대기 큐에 계속 넣게 되고, 이 큐가 꽉 차게 된다면 Client가 연결 요청을 하면 자리가 없어지게 된다. 따라서 SYN+ACK도 못 보내주게 된다. 이러한 상황을 SYN flooding이라고 한다. 이를 통해 여러 컴퓨터가 분산적으로 하게 된다면 이는 Dos가 된다.

 

Reference

TCP/IP Protocol Suite (McGraw-Hill Forouzan Networking) 4th Edition

반응형
Comments