티스토리 뷰

프로그래밍/Network

Transport Layer (3)

국윤창 2017. 3. 13. 21:55

11. Buffer Sizes and Limitations

특정한 limit들은 IP datagram 크기에 영향을 준다. 이러한 limit들에 대한 설명을 하고 그 다음에 application에서 전송되는 data에 어떻게 영향을 주는지에 대해 알아보도록 하자. 

1> IPv4 datagram의 최고 크기는 IPv4 헤더까지 합쳐서 65,535 바이트이다. 아래 그림 1의 total length 필드 때문에 길이가 65,535바이트 인 것이다.

[그림 1] Format of the IPv4 header


2> IPv6 datagram의 최고 크기는 40바이트의 IPv6 헤더까지 합쳐서 65,575 바이트이다. 이 길이는 아래 그림 2의 16-bit의 payload length때문에 그렇다. IPv6의 payload length 필드는 IPv6의 헤더 크기를 포함하지 않는 반면, IPv4 total length는 헤더 사이즈까지 포함한다.

[그림 2] Format of the IPv6 header


IPv6는 jumbo payload 옵션을 가지고 있는데, 이것은 payload length 필드를 32-bit로 확장해주지만 65,535이상의 최대 전송 단위(maximum transmission unit / MTU)를 제공해주는 datalink에서만 지원한다. (이러한 옵션은 내재된 MTU가 없는 HIPPI같은 host-to-host interconnect를 위해 존재한다.)


3> 많은 네트워크들은 하드웨어에 따른 MTU를 가지고 있다. 예를들어 Ethernet의 MTU는 1,500 바이트이다. Point-to-Point Protocol(PPP)를 사용하는 point-to-point link와 같은 다른 datalink들은 설정가능한 MTU를 가지고 있다. SLIP link는 보통 1,006 또는 296 바이트의 MTU를 사용한다.

IPv4에서 최소 MTU는 68바이트이다. 이러한 크기의 MTU는 최고 사이즈의 IPv4 헤더(20바이트의 고정헤더, 30바이트의 옵션)와 최소 사이즈의 fragment(이 fragment의 offset은 8바이트 단위이다.)를 포함할 수 있도록 해준다. IPv6의 최소 MTU는 1,280 바이트이다. IPv6는 더 작은 MTU를 이용해 link를 작동할 수 있지만, link가 최소 1,280바이트의 MTU를 가지고 있는 것처럼 보이게 하는 link-specific 분해(fragmentation)과 재조립(reassembly)를 요구한다. 


여기서 한가지 주목할 점은 이전 포스트에서 말한 MSS(maximum segment size)를 MTU와 연관지을 수 있다. 전송할 수 있는 사용자 데이터의 최대 크기인 MSS는 MTU의 크기에 영향을 받는다. Ethernet과 같이 MTU가 1,500바이트이면, MSS = MTU(1500) - IP헤더(20) - TCP헤더(20) = 1460바이트이다.


4> 두 호스트간의 연결에서 전송중에 지나는 여러 네트워크 중에서 가장 작은 MTU를 경로 MTU(path MTU)라고 부른다.(즉, 양단간의 경로에서 지나는 서로다른 MTU를 갖는 여러 네트워크 중 최소값) 요즘은 Ethernet의 MTU(1500바이트)가 보통 path MTU이다. 두 호스트 사이의 연결에서 각 방향의 path MTU가 서로 같을 필요는 없다. 왜냐하면 Internet에서 routing은 보통 비대칭(asymmetric)이기 때문이다. 이 말은 호스트 A, B가 있다고 가정할 때,  A로부터 B로의 route는 B로부터 A로의 route와 달라도 된다는 말이다.


5> 인터페이스 바깥으로 IP datagram이 나갈 때, 만약 이 datagram이 link의 MTU를 초과한다면 분해(fragmentation)이 IPv4와 IPv6에 의해 일어난다. 이렇게 나눠진 fragment들은 보통 최종 목적지에 도착할 때까지 재조립(reassembly)되지 않는다. IPv4에서는 호스트가 자신이 만든 데이터그램을 fragmentation하기도 하고, 전송되는 도중 라우터가 fragmentation 하기도 하지만, IPv6에서는 오직 호스트만 fragmentation를 수행한다.


IPv6 라우터로 분류된 라우터도 fragmentation을 수행한다. 그러나 오직 자신이 만든 datagram에 대해서만 수행한다. 라우터가 IPv6 datagram을 생성할 때는 마치 호스트처럼 작동한다. 예를들어, 대부분의 라우터들은 Telnet protocol을 지원하고, 이 프로토콜은 관리자에 의해 라우터 환경설정을 위해 사용된다. 이러한 IP datagram들은 라우터가 만든 라우터 Telnet 서버에 의해 생성된다.

그림 1을 보면 IPv4헤더에는 IPv4 fragmentation을 다루는 필드들이 존재함을 알 수 있는데, 그림 2를 보면 IPv6헤더에는 이러한 필드들이 없다. fragmentation은 규칙이라기 보다는 예외이기 때문에, IPv6는 fragmentation의 정보에 대한 option header를 포함한다.

라우터같이 작동하는 방화벽들은 패킷 내용을 검사하기 위해 분해된 패킷들을 재조립 할수도 있다. 이러한 방법은 방화벽이 추가적인 비용(complexity cost)으로 특정한 공격들을 막도록 해준다. 또한 이 방법은 네트워크로 진행하는 유일한 경로의 일부가 돼야하는데, 불필요한 중복을 줄이기 위한 것이다.


6> 만약 그림 1에서 IPv4헤더에 DF(don't fragment)라고 표시된 필드가 설정되면, 이 datagram은 호스트나 라우터에 의해 절대 분해돼서는 안된다고 명시하는 것이다. 송신 link의 MTU를 초과하고 DF bit가 설정된 IPv4 datagram을 수신한 라우터는 아래 그림 3의 "분해가 필요하지만 DF bit가 설정돼있어 목적지에 도달할 수 없다"("destination unreachable, fragmentation needed but DF bit set")는 ICMPv4 에러 메시지를 생성한다.

[그림 3] Handling of the ICMP message types by FreeBSD


IPv6 라우터들은 fragmentation을 수행하지 않기 때문에 모든 IPv6 datagram에는 DF bit가 함축되어있다. IPv6 라우터가 송신 link의 MTU를 초과하는 datagram을 송신하면, 라우터는 아래 그림 4의 "패킷이 너무 크다"("packet too big")는 ICMPv6 에러 메시지를 생성한다.

[그림 4] "RFC2463bis" designates the revision in progress of RFC 2463 - [Conta and Deering 2001]


IPv4 DF bit와 IPv6의 함축된 DF는 경로 MTU 탐색(path MTU discovery, IPv4에서는 RFC 1191 [Mogul and Deering 1990], IPv6에서는 RFC1981 [McCann, Deering, and Mogul 1996])를 위해 사용될 수 있다. 예를들어, TCP는 이 탐색을 IPv4에서 사용하고, DF bit가 설정된 모든 datagram에게 보낸다. 중간에 라우터가 "destination unreachable, fragmentation needed but DF bit set"의 ICMP 에러를 보내면, TCP는 datagram마다 보내는 데이터의 양을 줄이고 재전송한다. path MTU discovery는 IPv4에서는 옵션이고, 모든 IPv6에서는 path MTU discovery를 지원하며 최소 MTU를 이용해 송신한다.

요새 Internet에서는 path MTU discovery는 문제가 많다. 그 이유는 많은 방화벽들이 fragmentation이 필요하다는 메시지를 포함한 모든 ICMP 메시지를 삭제했기 때문이다. 그래서 TCP는 자신이 보내는 메시지 크기를 줄여야 한다는 신호를 받지 못한다. 그래서 요즘에는 IETF에서 ICMP 에러에 의존하지 않기 위해, path MTU discovery를 대체하기 위한 방법을 정의하는 노력을 하고있다.


7> IPv4와 IPv6는 어떤 구현이든 지원해야하는 최소 datagram size를 minimum reassembly buffer size라고 정의한다. IPv4에서는 576바이트이고 IPv6에서는 1,500바이트이다. 예를들어 IPv4에서 우리는 목적지에서 577바이트의 datagram을 받을 수 있는지 없는지를 알 수 없다. 그러므로 많은 UDP / IPv4 어플리케이션은 이 사이즈를 넘는 IP datagram의 생성을 제한한다.

찾아보니 UDP는 fragmentation을 피하기 위해서 minimum reassembly buffer size보다 작은 데이터그램을 전송한다. TCP의 경우 fragmentation을 피하기 위해 path MTU discovery를 사용하는데 UDP는 이러한 처리가 없기 때문이다. 참고로 fragmentation은 TCP에서 많은 양의 재전송을 요구할 수 있으므로 피하는 것이 좋다. UDP도 마찬가지로 라우팅 처리의 효율을 위해서 피하는 것이 좋다. (책에서 설명을 너무 대충해놔서 저자를 존나 때리고 싶기는 처음이다.)


8> TCP는 peer에게 전송 시 한 segment마다 담을 수 있는 최대 TCP데이터의 양인 maximum segment size(MSS)를 가지고 있다. 아래 그림 5를 보면 TCP가 연결 수립 시 SYN에 MSS를 담아서 보내는 것을 볼 수 있다.

[그림 5] Packet exchange for TCP connection


MSS의 목표는 fragmentation을 피하기 위해, 재조립 버퍼 크기(reassembly buffer size)의 실제 값을 peer에게 알려주는 것이다. MSS는 보통 MTU - 고정 IP헤더 - 고정 TCP헤더로 정해진다. IPv4를 사용하는 Ethernet에선 MSS는 1,460이 될 것이고 IPv6에서는 1,440이 될 것이다. (IPv6의 헤더는 IPv4것보다 20바이트 크다)


TCP의 MSS 옵션의 MSS 값은 최대 65,535의 16비트 필드이다. 이크기는 IPv4 데이터그램에서 TCP data의 최대크기는 65,495(65,535에서 IPv4헤더 20바이트와 TCP헤더 20바이트 뺀 것)이기 때문에 IPv4에서는 충분하다. 그러나 IPv6의 jumbo payload 옵션에선 다른 기술이 사용된다. (RFC 2675 [Borman, Deering, and Hinden 1999]) 첫번째로 IPv6 데이터그램에서 jumbo payload 옵션 없이 TCP data의 최대 크기는 65,515(65,535에서 20바이트의 TCP 헤더 뺀 것)바이트이다. 그러므로 MSS 값이 65,535바이트라는 것은 "infinity"를 지칭하는 특별한 경우로 여겨진다. 이 값은 jumbo payload 옵션이 사용되는 경우에만 사용되며, 65,535를 초과하는 MTU를 요구한다. 만약 TCP가 jumbo payload 옵션을 사용하고 65,535의 MSS를 peer로부터 받는다면, peer가 보내는 데이터그램 크기의 한계는 interface MTU일 뿐이다. 만약 이것이 너무 큰것으로 나타난다면(즉, 경로에 더 작은 MTU를 가진 링크가 존재한다면), path MTU discovery가 더 작은 값을 결정한다.


9> SCTP는 모든 peer의 주소에 대해서 가장 작은 path MTU를 찾아서, 그것을 기준으로 fragmentation point를 유지한다. 이 가장 작은 MTU 크기는 큰 user message들을 작은 조각들로 나누어, 하나의 IP datagram에 담아서 보낼 수 있도록 하기위해 사용된다. SCTP_MAXSEG 소켓 옵션은 이 값에 영향을 줄 수 있으며, user가 더 작은 fragmentation point를 요청할 수 있도록 해준다.


* TCP Output

앞에서 설명한 용어들과 정의들로, 아래 그림 6은 어플리케이션이 TCP소켓에 데이터를 썼을 때 어떤 일이 일어나는지를 보여준다.

[그림 6] Steps and buffers involved when an application writes to a TCP socket


모든 TCP 소켓은 send buffer를 가지고 있고 이 버퍼의 크기를 SO_SNDBUF 소켓 옵션으로 바꿀 수 있다. 어플리케이션이 write함수를 호출했을 때, 커널은 어플리케이션 버퍼에 있는 모든 데이터들을 소켓 send buffer로 복사한다. 만약 소켓 버퍼의 공간이 충분하지 않아서 어플리케이션의 데이터를 모두 복사못할 경우(어플리케이션 버퍼가 소켓 send buffer보다 클 경우 또는 소켓 send buffer에 이미 데이터가 존재해서 공간이 모자를 경우), 프로세스는 sleep된다. 이것은 blocking socket의 보통의 경우이다. (nonblocking socket은 챕터 16에서 다룬다.) 커널은 어플리케이션 데이터가 소켓 send buffer에 전부 복사되기 전까지 write함수로부터 return 되지않는다. 그러므로 TCP 소켓에서 write함수가 성공적으로 리턴된다면 어플리케이션 버퍼를 재사용할 수 있다는 것을 의미한다. 그렇지만 peer의 TCP가 data를 수신했다거나 peer의 어플리케이션이 data를 수신했다는 것을 의미하지 않는다. (이것은 Section 7.5에서 SO_LINGER 소켓 옵션으로 좀 더 다룬다.)

TCP는 send buffer로부터 data를 받아서 TCP 데이터 송신 규칙들에 따라 peer TCP로 데이터를 송신한다. (TCPv1을 챕터 19, 20에서 다룬다.) peer의 TCP는 반드시 수신한 데이터에 대한 ACK를 보내야하며, 송신측은 peer로부터 ACK를 받았을 때 비로소 소켓 send buffer의 데이터를 제거할 수 있다. TCP는 peer로부터 ACK를 받기 전까지 데이터의 복사본을 유지하고 있어야한다.

TCP는 IP로 MSS사이즈 또는 그보다 작은 덩어리에 TCP 헤더를 segement마다 붙여서 전달한다. MSS의 크기는 peer로부터 받으며(그림 5 참고), 만약 peer가 MSS 옵션을 보내주지 않으면 536으로 MSS를 정한다. (536 = IPv4에서 fragmentation을 피할 수 있는 최소MTU(576) - TCP헤더(20) - IP헤더(20)) IP는 헤더를 전달받은 데이터 앞에 붙이고, 목적지 IP주소에 대한 routing table을 찾고(routing table 매칭은 송신가능한 인터페이스(outgoing interface)를 명시한다.), 적절한 datalink로 데이터그램을 보낸다. IP는 datalink로 데이터그램을 보내기전에 fragmentation을 수행할 수 있지만, 앞에서 설명했듯이 MSS 옵션의 목적은 fragmentation을 피하는 것이고 요즘의 구현들은 path MTU discovery를 사용한다. (fragmentation은 성능 차원에서 피하는것이 좋다.) datalink는 각자 output queue를 가지고 있으며, 만약 이 queue가 꽉차면 패킷은 버려지고 protocol stack을 따라 에러가 리턴된다. (datalink에서 IP로, IP에서 TCP로) TCP가 이 에러를 받으면, segment 재전송을 시도한다. 어플리케이션은 이런 순간적인 상황을 알 수 없다.


* UDP Output

아래 그림 7은 어플리케이션이 UDP 소켓으로 데이터를 썼을 때 어떤 일이 일어나는지를 보여준다.

[그림 7] Steps and buffers involved when an application writes to a UDP socket



위 그림 7을 보면 socket send buffer가 점선으로 표시된 것을 볼 수 있는데 실제로는 존재하지 않기때문에 그렇다. UDP 소켓은 send buffer의 크기를 가지고 있지만(Section7.5의 SO_SNDBUF 소켓 옵션으로 수정할 수 있다.), 이 크기는 UDP 소켓에 쓸 수 있는 최대 데이터그램 크기를 의미할 뿐이다. 만약 어플리케이션이 send buffer의 크기보다 큰 데이터그램을 보낸다면, EMSGSIZE가 리턴된다. UDP는 신뢰성이 없기 때문에, 전송하는 어플리케이션 데이터의 복사본을 유지할 필요가 없고, 따라서 실제 send buffer가 필요없다.(어플리케이션 데이터는 protocol stack에 따라 내려갈 때 보통 몇가지 형태의 커널 버퍼로 복사되지만, 이 복사본은 전송된 후 datalink layer에 의해 제거된다.)

UDP는 데이터에 간단히 8바이트의 헤더만 붙여서 IP로 데이터그램을 보낸다. IPv4또는 IPv6는 전달받은 데이터에 헤더를 붙이고 routing function을 수행함으로써 송신가능한 인터페이스(outgoing interface)를 결정하고, 그 다음에 datalink output queue로 데이터그램을 전송하거나(만약 데이터그램이 MTU보다 작으면) fragmentation을 수행한 후 datalink output queue로 각각의 fragment들을 전송한다. 만약 UDP 어플리케이션이 큰 데이터 그램을 보낸다면(예를 들면 2,000바이트), TCP에 비해 fragmentation을 수행할 확률히 훨씬 높아진다. 왜냐하면 TCP는 큰 크기의 어플리케이션 데이터를 MSS 사이즈의 덩어리들로 나누기 때문이다. UDP는 이러한 과정이 없다.

UDP 소켓에 대한 write함수가 성공적으로 반환됐다는 것은 datagram 또는 datagram의 모든 fragment들이 datalink output queue에 추가되었다는 것을 의미한다. 만약 datagram이나 fragment들 중 하나라도 queue에 들어갈 자리가 없으면, 보통 ENOBUFS가 어플리케이션에 반환된다.

불행히도 몇몇 구현은 이러한 에러(ENOBUFS)를 반환해주지 않으며, 어플리케이션은 datagram이 재전송 없이 폐기된다는 것을 알수가 없다.


* SCTP Output

아래 그림 8은 어플리케이션이 SCTP 소켓에 데이터를 쓸 때 어떤 일이 일어나는지를 보여준다.

[그림 8] Steps and buffers involved when an application writes to an SCTP socket


SCTP는 TCP처럼 신뢰성이 있는 프로토콜이기 때문에 send buffer를 가지고 있다. TCP처럼 어플리케이션은 이 버퍼의 크기를 SO_SNDBUF 소켓 옵션으로 변경할 수 있다. (Section 7.5 참고) 어플리케이션이 write 함수를 호출하면, 커널은 어플리케이션 버퍼의 모든 데이터를 소켓의 send buffer에 복사한다. 만약 소켓 버퍼에 모든 어플리케이션 데이터가 복사될만큼 충분한 크기가 없으면 (어플리케이션 버퍼가 소켓의 send buffer보다 큰 경우 또는 소켓 send buffer에 이미 데이터가 존재하는 경우), 프로세스는 sleep된다. 이러한 sleep은 보통의 blocking socket일 경우를 가정한 것이다. (위에서 말했듯이 nonblocking socket은 챕터 16에서 다룬다.) 커널은 어플리케이션 버퍼의 마지막 바이트가 소켓 send buffer에 복사될 때까지 write함수를 return하지 않는다. 그러므로 SCTP소켓에 대한 write함수가 성공적으로 return 됐다는 것은 sender에게 어플리케이션 버퍼를 재사용할 수 있다는 것을 알려준다. TCP와 마찬가지로, 이 경우 peer의 SCTP가 데이터를 수신했다거나 peer의 어플리케이션이 데이터를 수신했다는 것을 의미하진 않는다.

SCTP는 소켓 send buffer에서 데이터를 받아서 SCTP 데이터 전송 규칙들에 따라 peer의 SCTP로 전송한다. (데이터 전송의 자세한 부분은 [Stewart and Xie 2001]의 챕터 5에 나온다.) 누적 acknowledgment point가 송신 데이터를 통과시키기 전까지 소켓버퍼에서 데이터를 지우면 안되는데, 통과되면 SACK가 송신 SCTP에 전달되고, 송신 SCTP는 SACK를 수신할 때까지 기다려야한다.


12. Standard Internet Services

그림 9는 대부분의 TCP/IP 구현들이 제공하는 standard service들을 나열한 것이다. 아래 서비스들은 TCP, UDP 둘 다 제공하며 같은 포트를 사용함을 알아두자.

[그림 9] Standard TCP/IP services provided by most implementations


보통 이런 서비스들은 모두 Unix 호스트의 inetd daemon에 의해 제공된다. (Section 13.5 참고) 이러한 standard service들은 standard Telnet 클라이언트를 이용하는 쉬운 테스트 기능을 제공한다. 예를들어 아래 그림 10은 daytime과 echo server를 테스트한 것이다.

[그림 10] daytime and echo server test


위의 두개의 예제에서 호스트의 이름과 서비스의 이름(daytime, echo)을 적었다는 것을 알 수 있다. 이런 서비스 이름들은 /etc/services 파일에 의해 위 그림 9에 나와있는 포트에 mapping된다. Section 11.5에 자세히 나온다. daytime 서버에 접속할 때 서버가 active close를 수행하는 반면, echo 서버에서는 클라이언트가 active close를 수행한다. 앞서 말했듯이 active close를 수행하는 endpoint는 TIME_WAIT state를 거친다.

이러한 "간단한 서비스"들은 보통 denial-of-service 공격이나 이런 서비스에 대한 자원 사용 공격들(resource utilization attack)때문에 현대시스템에서는 비활성화된다. 


13. Protocol Usage by Common Internet Application

아래 그림 11은 다양한 인터넷 어플리케이션들이 사용하는 프로토콜들을 요약해놓은 것이다.

[그림 11] Protocol usage of various common Internet applications


그림 10에서 상위 두개의 어플리케이션들(ping, traceroute)은 ICMP를 사용하는 진단 프로그램이다. traceroute는 자체 UDP 패킷을 만들어 보내고 ICMP 응답을 읽는다.

그 바로 아래 유명한 routing protocol들이 다양한 transport protocol들을 사용하는 것이 나와있다. OSPF는 raw socket을 써서 IP를 바로 사용하고, RIP는 UDP, BGP는 TCP를 사용한다.

다음 다섯개는 UDP 기반 어플리케이션들이고 그 다음 일곱개는 TCP 기반 어플리케이션, 그 다음 네개는 TCP, UDP 모두를 사용하는 어플리케이션들이다. 마지막 다섯개는 SCTP를 독점적으로 사용하거나 UDP, TCP, SCTP를 선택적으로 사용하는 IP 전화 통화 어플리케이션이다.


14. Summary

UDP는 간단하고 신뢰성없고 비연결 프로토콜이다. 반면에 TCP는 복잡하고 신뢰성있고 연결지향 프로토콜이다. SCTP는 UDP, TCP의 특징들 몇가지가 합쳐있고 TCP에서 찾을 수 있는 특징들을 외에도 몇가지 추가적인 특징들을 제공해준다.

'프로그래밍 > Network' 카테고리의 다른 글

Socket Introduction (2)  (0) 2017.05.22
Socket Introduction (1)  (0) 2017.04.05
Transport Layer (2)  (0) 2017.02.24
Transport Layer (1)  (0) 2017.02.21
TCP UDP 통신  (0) 2017.01.17
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함