Overview
전송계층은 물리적인 연결, 인접 스테이션간의 신뢰성 있는 데이터 전송부터 네트워크 간의 연결 보장과 같은 1~3 계층까지의 구현에 문제가 없음을 가정하고, end-to-end 간의 신뢰성있는 데이터 전송을 보장하는 것을 목표로 한다.
전송계층에선 보내고자하는 메세지를 세그먼트 단위로 쪼개며, 각 세그먼트의 헤더에 시작지와 도착지의 포트 번호를 적어 이를 3계층에 내려준다. 즉, 응용계층에서는 데이터의 크기에 대한 큰 고려 없이 이를 전송계층에 전달하며, 전송계층에서 이를 적절하게 쪼개어 목적지로 보내고, 수신측에서는 반대로 이를 합쳐서 다시 응용계층으로 올려준다.
여기서 end-to-end 간의 데이터 전송이란 호스트상에 존재하는 여러개의 어플리케이션간의 전송을 의미한다. 쉬운 예시로, 현재 노트북에서 여러개의 웹브라우저를 동시에 켜두었다면, 각각의 브라우저는 한 호스트 내에 존재하는 여러개의 end 에 해당하며 각각 포트 번호를 통해 구분되는 것이다.
사실 보다 정확하게는 어플리케이션이 아닌 소켓간의 연결이라고 보아야 한다. 많은 경우에 이는 1대1로 매핑 되어있기 때문에 단순히 어플리케이션간의 통신이라고 표현되지만, 어떤 어플리케이션에 경우엔 여러개의 소켓을 사용할 수 있고, 이 때 각 소켓은 자신만의 포트번호를 가지기 때문에 이는 여러개의 end 가 존재한다고 보아야 한다.
2계층에서 인접한 스테이션간의 신뢰성 있는 데이터 전송을 보장하는데, 왜 4계층에서 다시 신뢰성을 보장해주어야 하는걸까?
그 이유는 중간에 끼어있는 3계층이 best-effort 방식이기 때문이다. 즉, 3계층에서는 데이터 전송을 위해 최대한 노력은 하겠지만, 신뢰성 있는 데이터 전송을 보장해주진 않는다.
극단적으로 라우터가 데이터를 전송받은 후 꺼졌거나, 라우팅 과정에서 무한루프에 빠져 TTL (Time To Live) 이 만료되었거나, 라우터의 input buffer 가 꽉찬 경우와 같이 여러 상황에서 패킷 손실이 발생할 수 있고, 2계층에선 이를 처리하지 못한다.
심지어 2계층에서 신뢰성 있는 데이터 전송을 보장하지 않는 경우(에러 체크 후 에러가 있다면 패킷을 버리는 경우)도 존재하기 때문에, 결국 전송계층에선 다시 한번 신뢰성을 보장할 필요가 있다.
Multipelxing / Demultiplexing
전송계층에선 한 호스트에 도달하는 다양한 세그먼트를 합쳐 이에 해당하는 소켓에게 데이터를 올려 보내는 역할을 수행한다. 반대로 호스트 내에 존재하는 여러 응용계층의 소켓에서 데이터를 전송할 때, 이를 각각 구분하여 여러 세그먼트로 나누어 3계층에 내려 보내는 역할도 수행한다.
각 소켓에 연결된 데이터를 전송하는 과정을 Multiplexing, 받은 데이터를 해당하는 소켓에 올려주는 과정을 Demultiplexing 이라고 부른다.
아직 TCP와 UDP 에 대하여 살펴보지 않았지만, 위 이미지와 관련하여 TCP는 각각의 소켓이 1대1 관계로 이루어져야 하는 반면, UDP는 한개의 포트에 1:N 으로 여러개의 포트가 연결될 수 있다는 점을 유의하면 좋을 것 같다.
UDP
connectionless, 핸드셰이크 과정 없이 데이터를 단순히 전송하고 받는 전송계층의 프로토콜이다.
이는 기본적인 Mux, Demux 기능과, checksum 헤더를 활용한 에러 체크 정도의 기능만을 수행한다(신뢰성 있는 데이터 전송 보장까지는 아니어도 깨진 데이터는 버릴 필요가 있기 때문에 checksum 은 필요하다). UDP는 세그먼트의 순서를 보장하지 않고, 그저 전송받은 순서대로 이를 올려준다.
multimedia 와 같이 데이터 일부가 유실되어도 빠르게 동작하는게 중요한 경우에 주로 사용된다. 예를들어, 스트리밍 방송을 보고 있다고 했을 때 화면이 조금 깨지더라도 빠르게 받아보는 것이 완벽하게 데이터가 도착할 때 까지 로딩을 기다리는 것보다 훨씬 나을 것이다. 이럴 때 UDP가 사용되는 것이 적절하다고 볼 수 있다.
그 외에도 DNS와 같이 데이터 크기가 크지 않지만 자주 활용되는 컨트롤 패킷의 경우 UDP를 활용하기도 한다. 작은 데이터일 수록 데이터가 깨질 확률이 크지 않다.
DNS는 특성상 스트리밍 방송과는 다르게 데이터가 조금이라도 깨지면 안될 것 같은데 UDP를 사용해도 괜찮은걸까?
DNS client, DNS lookup tool 과 같은 이름으로도 알려진 DNS resolver 소프트웨어가 운영체제 내에 존재한다고 한다. DNS resolver 의 역할은 URL형태로 입력한 도메인 네임을 IP 주소로 변환하는 역할, DNS 쿼리를 요청하고 응답을 받아 처리하는 역할, 데이터가 깨졌을 때 이를 다시 요청하는 역할을 수행한다.
즉 DNS 프로토콜에선 UDP 로 인해 데이터가 깨질 경우를 대비하여, 응용계층에서 신뢰성 있는 데이터 전송을 보장한다고 볼 수 있다.
TCP
TCP 는 reliable 한 데이터 전송, 패킷의 순서를 보장한다. 4계층 중 TCP의 실제 세그먼트에 포함할 수 있는 데이터 최대 크기를 의미하는 MSS (maxium segment size)는 1460B 이며, 이는 이더넷 시장 지배에 따른 framgnet 최대 크기 MTU 값 1500B 에서 IP헤더, TCP헤더 총 40B 가 제외된 결과이다. (UDP 헤더는 8B)
또한 이는 flow control 을 통해 window 사이즈를 동적으로 조정하며, connection 을 생성하기 위해 3 way-hanshaking 을 진행한다.
TCP 에서의 ACK 은 해당하는 byte 부터 데이터를 보내달라는 것을 의미한다. 예를 들어, 송신측에서 seq # 30번에서 20byte 패킷을 보냈다면, 수신측엔 51번 ACK 을 돌려보낸다. (즉 seq number, ACK 은 모두 byte 번호이다)
ACK 과 seq 번호는 함께 전송이 가능하다. 즉, 데이터 전송과 함께 다음 요청을 위한 ACK 넘버를 끼워 보낼 수 있고, 이를 piggy-back 이라고 한다.
헤더의 U/A/P/R/S/F 는 0과 1의 값으로 해당하는 영역이 의미있는 정보인지를 나타낸다 (1이면 의미있음, 0이면 해당 영역은 무시). 그 A/R/S/F 는 각각 ACK / RST / SYN / FIN 를 의미한다.
receive window 는 수신측에서 버퍼에 받을 수 있는 bytes 크기를 나타낸다 (트래픽에 따라 동적으로 변하는 값이다). 이 정보를 통해 송신측에서는 패킷을 보내는 속도를 늘리거나 줄이거나 하는 식으로 제어하는데, 이를 flow control 이라고 한다.
TCP 는 timeout 을 어떻게 설정할까?
적어도 데이터가 갔다가 돌아오는 RTT 만큼은 기다려야 한다. RTT 는 수시로 샘플 패킷을 보내는 방식으로 측정한다. 매번 값이 다를 수 있기 때문에 평균적인 값을 계산한다.
계산식을 보면 최근에 구한 RTT 값에 더 높은 비중을 두는 것을 확인할 수 있다. 그렇다고 평균적인 값을 timeout 으로 설정 된다면, worst case 들이 고려되지 않는다. 따라서 deviation 이동 평균값을 지속적으로 업데이트 하며, 이를 safety margin 값으로 활용하여 timeout 간격을 결정한다.
Reliable Data Transfer
TCP 는 세그먼트를 pipelining 하는 기법인 GBN (Go-Back-N), SR (Selected Repeat) 기법을 적절히 조합하여 활용하여 RDT를 보장한다. 이를 제대로 이해하기 위해선 stop-and-wait 기법 부터 pipelining 기법까지의 발전 과정을 살펴보는게 좋을 것 같다.
rdt 3.0
rdt 3.0 은 stop-and-wait 방식으로 0과 1 두가지 sqeuence number 만을 사용하여 동작한다.
전송하는 패킷에 타이머를 유지하여, timout 을 통해 packet loss, ACK loss 상황을 모두 대처할 수 있다. 다만, (d) 와 같이 timeout 이 너무 빠르게 끝난다면 제대로 전송되어온 ACK 도 버리게 될 수 있으니 적당한 시간 확보가 중요하다.
rdt 3.0 의 경우 결국 한번에 하나의 패킷만 전송할 수 있기 때문에 매우 비효율적이다. 이에 따라 연속으로 패킷을 보내는 pipelining 기법이 고안되었다.
GBN
GBN 의 특징은 송신측에만 윈도우가 존재하며 타이머를 가장 오래된 패킷 하나에만 유지한다는 점이다.
SR에 비하여 타이머를 하나만 유지해도 된다는 것은 장점이지만, 그에 따라 수신측에선 기다리고 있는 패킷을 받지 못하였다면, 그 뒤에 패킷을 다 버리게 되는 현상이 발생한다. 이에 따라 송신측도 타임아웃이 발생하면 base 패킷부터 모든 패킷을 재전송해야 한다.
송신측에선 패킷이 하나가 잘 도착했다는 ACK 신호를 받으면, 윈도우를 한칸 옮겨 맨 끝의 패킷을 보내게 된다.
SR
SR 에선 수신측도 버퍼를 가지며, 순서가 꼬이거나 중간 몇개의 패킷이 깨지더라도 모든 패킷을 버리지 않고 잘 온 것들을 버퍼에 저장해 둔다. (다만 이를 바로 applcation layer 에 올려선 안된다. 순서를 보장한 뒤에 올려주어야 한다)
제일 오래된 패킷에만 타이머를 유지하는 Go-back-N 에 비해, Selective Repeat 송신측에선 보낸 패킷 각각의 timer 를 유지해야 한다.
이는 base 패킷에 대한 ACK 을 받았을 때 송신측 윈도우를 옆으로 한칸 이동시켜 새로운 패킷을 전송한다.
SR 에는 한가지 딜레마가 존재하는데, seq 번호가 window size 와 비슷할 경우에 (b) 와 같은 duplciate data accepted 현상이 발생할 수 있다. 그렇기 때문에 seq number 는 windows size 2배 이상의 수만큼 확보되어 있어야 한다. TCP 에서 window size 는 동적으로 증가할 수 있기 때문에 사전에 충분히 큰 seq number 를 확보해 둔다. 이는 헤더에서 관리되는데, 그에 대한 영역을 충분히 크게 잡아둔다고 생각하면 될 것이다.
TCP 의 rdt
TCP 는 GBN 과 SR 의 장점을 적절히 조합하여 사용한다. SR과 같이 수신 윈도우를 갖고 있지만, 타이머는 하나만 유지한다. TCP에 수신측에서 보내는 ACK 은 해당하는 패킷을 받았다는 것을 의미하는 것이 아닌, 현재까지 연속으로 잘 도착한 패킷의 끝의 다음 바이트를 의미한다.
송신측은 타미어가 timeout 이 되거나, 3번 연속 같은 ACK 이 온다면 패킷을 다시 보내게 된다. 이 때 보내는 패킷은 윈도우의 base 패킷 하나이다.
ACK=100 이 안와도, cumulative ACK 약속에 따라 송신측에선 해당 패킷 전송이 잘 된 것을 알 수 있다.
그런데 왜 3번 같은 ACK 이 오면 패킷을 재전송할까? 이는 TCP fast restransmit 을 위함이다. TCP 에선 timeout 이 발생할 때마다 timeout 시간을 더 늘리는 방식으로 동작한다. 이는 충분한 timeout 을 확보하여 ACK 이 전달되기 전에 timeout 이 발생하는 것을 방지하기 위함인데, 이러한 특성이 오히려 전송 패킷이 lost 되었을 경우에는 비효율적이다. 이를 어느정도 개선하기 위해 duplicated ACK 이 3번이나 왔다면 패킷 전송이 실패했다고 생각하고 재전송을 시작하는 것이다.
TCP flow control
TCP는 rdt 외에도 2가지 주요한 기능을 수행하는데 하나는 flow control, 나머지 하나는 congestion control 이다. 두가지 control 모두 sender 측의 전송 속도를 조절하지만 목적은 다르다. flow control 은 receiver 측의 buffer 가 넘치지 않도록 하기 위함이라면, congestion control 은 네트워크 전반적인 트래픽 양을 조절하기 위해서 동작한다.
flow control, congestion control 모두 송신측 윈도우 크기을 조절하는데, 결과적으로는 두 control 에 의하여 결정된 윈도우 크기중에 최소값으로 송신측 윈도우 크기가 결정된다.
먼저 flow control 에 대하여 알아보자.
어플리케이션에서 socket 을 생성하면 receiver 버퍼와 sender 버퍼가 생성된다(시스템 마다 다르지만, 보통 전송 버퍼보다 수신 버퍼가 크며 각각 약 4K, 8K 정도가 될 수 있다). TCP 를 통해 전달된 데이터가 application 에 올려보내지게 되는데, applicatoin 에서 데이터를 가져가는 속도와 TCP가 데이터를 올려주는 속도가 다를 수 있다. 만약 application 에서 이를 가져가는 속도가 더 느리다면 buffer 는 가득차 데이터가 드랍될 것이다.
이를 방지하기 위해 TCP는 flow control 기능을 수행하는데, 이를 위해 수신측은 receiver buffer 의 사이즈가 얼마나 남았는지 송신측에게 알려주고, 송신측은 이에 따라 데이터 전송 속도를 줄이거나 늘린다.
위 이미지는 receive 버퍼에 상태를 보여준다. buffered data 는 아직 application 이 가져가지 않은 데이터이며, 이에 따라 남는 free buffer space 를 receive window 라고 부른다. 수신측은 이에 대한 크기를 송신측에게 알려주는 것이다.
sender 측은 수신측의 윈도우 크기보다 작게 설정한다. 1대1로 연결되기 때문에 window size 보다 넘치게 데이터가 도달하는 일은 없을 것이다.
커넥션 관리
TCP 는 커넥션 기반이다. 커넥션이 생성되어야 데이터를 주고 받을 수 있다. 처음 커넥션에 맺어지는 과정에서 서로에게 데이터의 시작 seq 번호, 수신 버퍼의 사이즈를 알려준다.
처음 커넥션 맺어질 때 송신측은 SYN, 수신측은 SYN 과 ACK, 다시한번 송신측이 ACK 을 보내는 3가지 과정을 거친다. 이 과정에서 seq 번호도 함께 보내는 것을 확인할 수 있다.
위 과정을 통해 클라이언트와 서버측 소켓 모두 establish 상태가 된다.
커넥션을 끝내는 과정은 다음과 같다.
이를 4 way handhsake 과정이라고 부른다.
congestion
너무 많은 source 에서 데이터를 너무 빠르게 보내면 전체 네트워크가 혼잡해지는 문제가 발생하는데, 이를 congestion 이라고 한다. congestion 이 발생하면 delay 가 길어지고, 패킷 로스가 발생한다.
패킷 로스나 duplicates 가 발생하지 않는 이상적인 경우에는 실제 보내는 데이터와 목적지에 도달한 데이터의 비율 그래프가 다음과 같을 것이다.
하지만, 현실에서는 패킷 로스가 발생하기 때문에 다음과 같은 그래프가 그려질 것이다.
추가적으로, timout 이후에 ACK 이 도달하는 상황과 같이 duplicates 도 발생하기 때문에 그래프는 다음과 같다.
멀티홉이라는 점을 고려하면 문제는 더욱 심각해진다.
위 그림에 Host A 에서 Host C 로 데이터를 보내는 빨간색 경로의 데이터 전송량이 위쪽 라우터를 대부분 차지한다고 생각해 보자. 이에 따라 위쪽 라우터를 지나 데이터를 전송하던 파란색 경로의 패킷 전송이 실패하게 되고, 왼쪽의 라우터 입장에서는 자신은 문제가 없었으나 패킷을 다시 전송해야 하는 부담이 증가된다. 이렇게 되면 왼쪽 라우터를 지나 데이터를 전송하던 핑크색 경로의 패킷 전송 또한 마찬가지로 영향을 받는다. 이렇게 한 라우터에서의 혼잡 현상이 여러 라우터에게 영향을 미칠 수 있는 것이다.
결과적으로 위 그래프와 같이 어느 시점이후에는 데이터 전송량이 오히려 감소되게 되는 것이다.
TCP congestion control
보내는 속도를 결정한다는 것은 윈도우 사이즈를 결정하는 것이다
TCP congestion control 의 기본 방침은 위와 같이 윈도우 사이즈를 천천히 1씩 증가시키다가, timeout 이 발생하면 크게 2배씩 감소시키는 것이다. 하지만 실제로는 이를 보다 개선하기 위해서 Slow Start 와 같은 기법을 사용한다.
ssthresh (slow start threshold) 까지는 2배씩 빠르게 증가시킨 후, 이후에는 1씩 증가시키는 방식이다. ACK 이 올 때마다 cwnd (congestion window, 송신측 윈도우) 를 1씩 증가시키는 방식으로 구현된다.
TCP Tahoe (예전 버전) 에서는 timeout 이 발생하면 cwnd 를 1로 줄인다. 이 때 ssthresh 타임아웃이 발생했을 때의 cwnd 크기의 반으로 줄인다.
TCP Reno (현재 버전) 에서는 duplicated ACK (3번의 같은 ACK) 이 왔을 때 congestion 때문에 발생한 timeout 인지 packet loss 인지 반반이기 때문에 1로 떨어트리기엔 조심스럽다고 판단하여, cwnd 를 1로 줄이는 것이 아닌 반으로 줄이게 된다. ssthresh 도 마찬가지로 반으로 줄인다. 쉽게, TCP Tahoe 에서 Slow Start 과정을 생략한 것이라고 볼 수 있으며, 이를 Fast Recovery 라고 부른다.
즉, timeout 에 대해선 TCP Tahoe 나 TCP Reno 나 같은 방식으로 동작하지만, duplcated ACK 에 대해서는 TCP Reno 는 cwnd 를 반으로 줄이는 방식으로 동작한다. (TCP Tahoe 시절엔 duplicated ACK 이 없었다)
TCP congestion control FSM
congestion avoidance 상태에서 cwnd 는 모든 ACK 이 왔을 때 MSS 만큼 증가해야 하기 때문에 cwnd = cwnd + MSS * (MSS/cwnd) 와 같은 방식으로 표현되어 있는 것이다.
fast recovery 상태로 넘어갈 때 retransmit missing segment 동작이 수행되는데, 이후 여전히 duplicated ACK 이 올 수 있다. 그 뜻은 congestion 이 아닐 확률이 높다는 것이므로 exponentially 하게 증가시킨다. 사실 이 경우는 거의 없다고 생각해야 한다.
'네트워크' 카테고리의 다른 글
[OSI 7 Layer] Application Layer (1) | 2023.06.02 |
---|---|
HTTP/1, HTTP/2 & HTTP/3 (0) | 2023.04.20 |
[OSI 7 Layer] Network Layer (0) | 2023.04.19 |
[OSI 7 Layer] Link Layer (0) | 2023.04.19 |
[OSI 7 Layer] Overview & Physical Layer (0) | 2023.04.17 |