내 손을 떠난 메시지 처리과정에 대한 정리

2021. 10. 27. 23:56게임서버/TCP IP 이론

320x100
안녕하세요 대학생 개발자입니다.
이번 글에서는

TCP IP에 대한 간단한 정리..라고 적어놓고 나열을 해보겠습니다.

 

이번 글에서 주로 다루게 되는 내용은 다음과 같습니다.

L7에서 메시지를 만들어서 다른 L7에게 전달을 하려고 한다면 어떤 일이 일이 일어나는지 간단하게 정리할겁니다.

 

그럼 시작하겠습니다.

L7에서 AAA라는 데이터를 보내려고 한다면 우리는 API를 통해 전달을 해야 할 겁니다.

리눅스의 경우에는 오픈소스이기 때문에 커널이 오픈되어있고 커널에서 직접 뭔가를 할 수 있을것 같지만,

윈도우즈의 경우에는 유저에 대해서 커널이 닫혀있는 os입니다.

그러므로 우리가 직접 하드웨어를 제어하거나 커널에 직접 접근한다는 것은 불가능합니다.

그러므로 우리는 윈도우즈에서 제공하는 API를 사용해서 간접 접근을 하게 될 것입니다.

 

 

패킷은 택배와 같습니다.

당장은 그냥 socket api 중 send함수를 사용해서 의사 코드 정도로만 정리하겠습니다.

내 코드에서 send(AAA)를 했다고 가정하겠습니다.

이 AAA는 L7의 어플리케이션 레이어의 버퍼에서 L4의 송신 버퍼로 복사되게 될 겁니다. 

그럼 TCP 스택에서는 내가 보내려고하는 메시지에대한 도착지와 출발지를 붙여서 보내게 될겁니다.

위의 상황까지를 그림으로 보면 다음과 같이 되겠습니다.

이때 필요한 포트번호와 데이터는 L7에서 전달해야됩니다.

택배를 보낼때 주소에대한 정보가 없으면 갈 수 없으니까요.

도착지와 출발지에대한 정보는 이미 클라이언트와 서버가 합의를 통해 서로 가지고있는 상황입니다.

 

그럼 L4에서 헤더를 붙이고 난 뒤 L3로 전달을 하면 L3에서는 넓은 네트워크를 지나서 배송되기 위해서 집 주소를 적어야 됩니다.

그게 IP입니다.

IP에 대한 내용은 아마 나중 글에서 조금 더 자세히 다루겠습니다만 지금 당장은 ip란 논리적인 주소로 세상에 단 하나만 존재하는 주소를 의미한다고 생각하시면 될 것 같습니다.

그렇게 내 ip와 상대의 ip를 기입해서 아까 그 L4 헤더 앞에 붙게 됩니다.

L3의 헤더를 붙일 때 L4의 헤더는 payload에 포함이 되어버립니다.

즉... L4에서는 L7의 데이터만이 payload가 되는 것이지만 L3는 L4에 뭐가 담겨있는지 알 필요가 없을뿐더러 알고 싶지도 않기 때문입니다.

 

공유기도 그렇고 라우터도 그렇고 이 ip를 가지고 다음번에 어디로 가야 되는지 판단하게 되는데 이건 나중에 라우팅 테이블이라는 녀석을 설명하면서 언급하도록 하겠습니다.!

 

여기까지는 이제 L7영역에서 알려줘야 되는 부분입니다.

ip와 port 둘 중 하나라도 잘못 기입된다면 이 녀석은 버려질 확률이 99.99999%입니다.

가령 내 아이피를 고쳐서 보낸다고 하면 내가 아무리 백날 보낸다고 해봐야 회신은 그 나한테 안 안 오고 엉뚱한 pc로 가게 될 겁니다.

상대의 ip를 잘못 보낸다면 없는 거라고 한다면 네트워크에서 뺑뻉뺑돌다가 사라질 것이고 제대로 된 아이피라고 한다고 해도 포트에서 또 걸러지게 될 겁니다.

음... ip를 고쳐서 보낸다는 부분은 NAT방식으로 작동하는 L4 스위치의 경우에는 예외가 될 수 있으니 나중에 그 부분에 대해서는 다시 언급하도록 하겠습니다.

 

그리고 이부분까지는 우리가 코드레벨에서 관여할 수 있는 부분이 되겠습니다.

 

여기부터는 패킷이 우리의 손을 떠났습니다.

L2로 내려왔거든요.

우리가 더 이상 줄 수 있는 정보는 없습니다.

이제 스스로 ip와 port를 가지고 떠나야 합니다.

L2에서는 논리적 주소인 IP를 가지고 Broadcasting을 해버립니다.

wire shark의 번역을 기준으로 한다면 who has 111.111.111.111 이런 식의 메시지가 날라가게 됩니다.

이 프로토콜은 ARP라고 하며 나중에 따로 언급할 기회가 있다면 언급하도록 하겠습니다.

그리고 이 ARP라는 프로토콜은 실제 연결되어있는(L1으로 직접 연결되어있는) 모든 디바이스에게 날라가게 됩니다. 

만약 LAN 안에 그 아이피를 가진 호스트가 있다면 자신이 그 아이피를 가진 호스트라고 소리치겠지만 없다면 이 데이터는 default(공유기)로 보내지게 됩니다.

물론 나중에 언급드릴 부분이지만 매번 ARP프로토콜을 발생시키는 것은 엄청난 낭비이기 때문에 이를 메모리에 캐시 하고 있습니다.

그리고 그 캐시의 기간은 아주 짧습니다.

하지만 괜찮습니다. 그 짧은 순간 어마어마하게 많은 데이터를 주고받을 테니까요.

 

ipconfig라는 명령어를 cmd창에 쳐보신다면 쉽게 아실 수 있습니다.

내 아이피와 마스크가 있고 게이트웨이의 주소가 적혀있을 것입니다.

그리고 내가 가진 수신 측 아이피가 있을 겁니다.

그리고 상대 아이피와 내 서브넷 마스크를 마스킹했을 때 동일한 ip대역이 나온다면 그건 LAN입니다.

그게 아니라면 디폴트 즉 게이트웨이로 나가게 되는 겁니다.

일반적으로 공유기는 L4 스위치입니다.

하지만 LAN 환경에서만 본다면 이는 L2이상의 역할을 수행하지는 않습니다.

그렇기 때문에 내부에서는 L2 스위치로써의 역할을 하고 외부로 나가게 될 때는 L3, L4 스위치의 역할을 하게 됩니다.

L3의 역할을 한다는 건 외부망과 내부망의 ip가 다르다는 걸 의미하겠죠...

L4 스위치로써의 역할은 나중에 정리해놓도록 하겠습니다.

그럼 공유기를 통해 외부로 전달되어서(사실 공유기 또한 L3 스위치이기 때문에 라우터입니다) 다음 라우터로 전달되고 그 라우터에서 아이피를 확인한 다음 그 아이피가 자신의 라우팅 테이블 위에 있다면 거기로 전달할 것이고 없다면 다시 그 라우터의 default로 보내게 될 것입니다.

그게 반복되다 보면 어느 센가 목적지에 도착을 해있겠죠.

 

이제 패킷의 여정이 끝났습니다.

그렇게 된다면 L3, L2의 역할은 끝이 났습니다.

제대로 된 목적지에 찾아왔기 때문이죠.

그럼 이제 남은 건 그 L4에서 포트에 해당하는 프로세스를 찾아서 거기다가 데이터를 던져주면 L7에서 recv라는 API를 통해서 수신을 하게 되고 길었던 메시지의 전달 과정이 완료가 됩니다.

 

그럼 간략한 정리는 끝났습니다.

각 레이어마다의 헤더에 대한 정리를 해볼 텐데요...

OSI 7 layer의 경우에는 헤더를 따로 정의하고 있지 않습니다.

frame, ip, tcp, udp등과 같은 프로토콜은 ip/tcp 4 계층에서 나오는 거고요.

OSI 7 layer의 경우에는 이에 대한 틀만을 제시하고 있습니다.

그러므로 지금까지 말씀드렸던 ip, tcp등과 같은 것들은 OSI 7 layer구나라고 생각하시면 조금 안 맞는 구간이 생길 겁니다.

 

여기서도 마찬가지로 하위계층부터 하나씩 살펴보도록 하겠습니다.

Frame Header

먼저 L2 계층을 통과하기 위한 헤더는 위와 같이 생겼습니다.

하나씩 차근차근 파보도록 하겠습니다.

L2계층을 통과하는 데이터 하나의 단위는 프레임입니다.

 

Preamble은

10101010~을 7번 보내는데 클럭을 맞추기 위한 부분입니다.

클럭을 맞추는 이유는 각 pc마다 클럭이 다르기 때문에 같은 값을 보내서 난 이런 속도로 보내니까 니가 맞춰서 받아라 라는 의미입니다.

아무런 준비 없이 노래를 틀면 당연히 박자를 놓치겠지만 그전에 전주를 깔아줌으로써 박자를 안 놓치게 하려는 목적이라고 생각하시면 이해하시기가 편할 것 같습니다

 

SFD(Start Frame Delimiter)는

이 1바이트가 지나게 되면 실제 헤더의 데이터가 들어온다는 걸 의미합니다.

위의 예시와 마찬가지로 노래방에서 간주가 끝나갈 무렵에 4,3,2,1을 띄워주는 맥락이라고 생각하시면 이해하기 편할 것 같습니다

그래서 얜 preamble이랑 다르게 10101011 10을 63번 하고 11 한번... 해서 클럭을 맞추고 실질적인 시작 부분 앞이라는 명시를 하고 있는 겁니다.

 

이제부터는 상대의 주소와 나의 주소입니다.

맥주소는 6바이트이기 때문에 둘 합해서 12바이트를 먹습니다.

타입은음... 상위 프로토콜 중 어떤 프로토콜인지에 대한 정보를 담았다고 생각하시면 될 것 같습니다.

ipv4에 대한 정보이다, 혹은 ipv6이다, 혹은 arp다 rarp이다 등등에 대한 정보를 담는 곳입니다.

몇 개 없기 때문에 2바이트로도 충분합니다.

사실 2바이트만 해도 unsigned short 즉 65535개의 케이스를 담을 수 있으므로 충분합니다.

 

Data Payload는

이 부분에서는 이제 L3~L7까지의 헤더와 데이터들이 들어있는 곳입니다.

위에서 언급했던 택배의 내용물이라고 생각하시면 될 것 같습니다.

프레임 차원에서 봤을 땐 그렇게 중요하지 않습니다. 

 

FCS(frame check sequence)는

정보가 제대로 도착했는지 확인하기 위한 부분인데 일종의 체크섬 같은 부분이라고 생각하시면 될 것 같습니다.

FCS부분을 제외한 모든 부분을 CRC(cycle redundancy check)를 돌렸을 때 상대가 보낸 FCS와 동일한 값이 나오지 않는다면 그 패킷은 중간에 변질되어서 유효하지 않은 패킷으로 간주해 버립니다. (제가 알기로는 2개의 비트인가... 뭐 몇 개까지는 봐준다고 했던 거 같은데 뇌피셜이니 거르시길 바랍니다)

 

프레임의 경우에는 앞과 뒤로 헤더와 트레일러가 붙는 구조입니다.

다른 헤더들과는 조금 차별이 되어있는 모습입니다.

모든 레이어에서 동일하겠지만 이 과정을 마치 캡슐안에 넣어서 내부를 공개하지 않는 모습과 비슷한 encapsulation(캡슐화)라는 말을 사용합니다.

여기까지가 프레임 헤더였습니다. 

 

 

 

다음으로는 L3에 해당하는 ip헤더를 한번 까 보도록 하겠습니다.

ip header

그나마 제일 있어 보이는 사진으로 가져왔습니다. 허허

L3를 지나는 데이터 하나의 이름은 패킷입니다.

 

먼저 ip는 preamble 같은 건 없습니다.

이미 L2에서 했기도 했을뿐더러 그냥 프레임 헤더만 띠어내면 그게 ip헤더인데 굳이 할 필요 성은 없었던 거죠. 

그래서 제일 먼저 나오는 게 version입니다.

뭐... 대부분의 경우에는 tcp ip 프로토콜을 채택하기 때문에 저긴 기본으로 ipv4에 대한 정보가 들어있을 겁니다.

 

HeaderLength는

일반적으로 거의 20이 들어갑니다.

왜냐면 IP Option이 있긴 하지만 거의 안 쓰거든요.

길이를 적는 이유는 헤더의 길이가 option을 통해서 가변적으로 변할 수 있기 때문입니다.

 

Type of Service는

어떤 식으로 라우팅을 해줘라고 라우터에 요구하는 겁니다.

뭐 신뢰성 있는 라우팅을 선호한다면 어떤 옵션을 선택할 수 있고.

가장 짧은 경로로 안내해달라고 한다면 그런 옵션을 또 선택할 수 있고 표가 있었는데 제가 그걸 외우지는 않고 있어서 정확히 기억은 나지 않습니다

 

total length는

ip패킷 하나의 전체 길이를 바이트 단위로 나타낸 것입니다.

이를 통해서 알 수 있는 건 아이피 패킷의 최대 크기는 65535라는 점인데요.

65535면 해봐야 64kb인데 많이 모자라지 않나? 생각이 듭니다.

하지만 요새는 ip option에 저걸 2의 n승으로 더 올려치는 옵션이 추가되었다고 합니다. 

 

IP Flag는

do not Fragment bit을 조절해서 framgmentation을 할지 말지 결정을 하게 됩니다.

more fragments follow bit는 만약 fragment가 되었을 때 뒤에 비트가 더 있는지를 확인하는 용도로 사용합니다.

fragment offset은 fragment가 되었을 경우 몇 번째 데이터인지 구분을 하기도 fragment가 되었다면 다시 합쳐져야 되는데 그 순서를 보장하기 위해서도 사용됩니다.

근데 tcp를 사용하게 되면 이 기능이 거의 꺼지게 됩니다.

그 이유는 차차 나중에 정리해드리겠습니다.

일단은 왜 fragment라는 게 생기는 가에 대한 궁금증이 생기실겁니다.

당연히 궁금해야 정상입니다 왜 멀쩡한 패킷을 잘라서 보내고 도착지에서 합체시킨다는 의미냐라는 것입니다.

이유는 간단합니다 실제 물리적으로 한 번에 보낼 수 있는 양이 1500byte(MTU)이기 때문입니다.

제 생각엔 기술이 없어서 이러진 않을 것 같긴 하지만 약간 전통적으로 써왔었던 고정된 값? 정도의 느낌으로 저는 받아들이고 있습니다.

 

 

TTL은

만약 아이피의 TTL부분이 없었다면 영원히 떠도는 패킷이 발생하게 될 것이고 이건 엄청난 결과를 초래하게 될 겁니다.

왜냐면 유령 같은 패킷들이 떠돌아다니면서 영원히 네트워크 트래픽을 잡아먹을 거기 때문인데요.

영원히 사라지지 않는 패킷이라 네트워크의 트래픽을 영원히 먹는 다는건 큰 문제가 될 것 같습니다.

이게 한두 개면 당장은 문제가 안 되겠지만 실제로 아이피를 잘못 기입하는 실수는 하루에도 어마어마하게 일어나고 있으니까요.

이게 만약 없었다면 네트워크는 이미 먹통이 되어서 난리가 났겠죠.

TTL은 1바이트의 최대값 255로 세팅을 해두고 라우터를 거칠 때마다 1씩 감소합니다.

그리고 0이 되면 그 라우터에서 discard 하는 방식으로 사용됩니다.

물론 discard가 되면서 그 메시지가 전달이 안됬다고 icmp라는 프로토콜을 통해서 전달해주긴 하는데 뭐 의미 없습니다.

그걸 받아서 다시 전송하진 않으니까요.

재전송은 IP에서는 보장해주지 않고 있기 때문입니다.

 

Protocol은

상위 프로토콜이 뭔지에 대한 정보를 가지고 있는 겁니다.

TCP인지 UDP인지 ICMP인지 뭐... 등등 이런 정보들을 가지고 있습니다.

TCP == 6이었던 걸로 기억합니다.

나머지는 잘 기억이나지 않습니다.

그냥 #define이나 열거 타입을 쓰니까 숫자를 굳이 확인을 안 하게 되더라구요

 

CheckSum은

말 그대로 데이터가 올바르게 잘 들어왔는지 점검하는 부분입니다.

이야기를 들어보니까 이건 거의 무시될 확률이 높다고 하더라고요.

왜냐면 L2계층에서 이미 검증을 마치고 온 데이터를 한 번 더 검사한다는 건 큰 의미가 없는 행동이라고 하셨거든요...

그래서 요새는 거의 잘 안 하는 걸로 알고 있다고 말씀하셨는데 저도 이 부분은 100% 확신이 아니라서 잘 모르겠습니다 허허...

그다음 Source IP, Destination IP 순서고 옵션은 거의 잘 안 쓰기 때문에 스킵하도록 하겠습니다.

 

 

 

다음으로는 TCP 헤더입니다.

TCP헤더

네 이건 TCP의 헤더입니다. 

L4를 지나는 하나의 데이터 이름은 세그먼트입니다.

네 우선 source port, dest port가 적혀있구요

시퀀스 넘버라는 것이 있습니다.

TCP에서는 시퀀스 넘버와 어크 넘버라는 것이 있는데 이게 왜 있는지는 차차 설명드리고 지금은 일단 저런 게 있고 src와 dest가 서로 공유한다는 정도 알고 넘어가시면 될 것 같습니다.

 

HLEN은

헤더의 길이인데 하나의 비트당 4바이트라고 생각하시면 됩니다.

4개의 비트라서 최대 0~15까지 가능하고 최대가 그럼 60byte가 되겠네요 최소는 20byte입니다.

 

그리고 URG~FIN까지 매우 중요합니다.

URG는 긴급 포인터 옵션이라고 하는데 얘는 따로 송신 버퍼에 저장되지 않고 바로 빠져나가는 그런 버퍼에 옮겨지는 그런 녀석입니다.

URG비트가 1이 되면 Urgent pointer에 포인터가 들어가게 되고 그 포인터에 있는 값은 바로 전송되게 된답니다.

우리는 따로 사용할 일은 없습니다.

ACK bit는 상대측으로부터 데이터를 받았을 때 그 시퀀스 번호 + 받은 데이터 수를 어크 번호로 적고 ACK비트를 1로 세팅해서 보내게 된다면 상대방은 헤더를 뜯어서 아 잘 받았구나 하고 생각한답니다.

PSH 얘는 사실 설명하기가 상당히 까탈스럽습니다.

일반적으로 널리 알려진 건 빠르게 어플리케이션 레벨로 데이터를 올릴 수 있게 해주는 bit입니다.

음... 좀 추상적이죠?

대략 설명하자면 L7의 데이터 한 덩어리의 끝을 알려주는 비트라고 생각하시면 될 것 같습니다. 약간 버퍼로 따지면 flush와 같은 느낌으로 보셔도 좋을 것 같습니다.

SYN bit는 최초 연결할 때 보내는 비트로 나 너랑 연결하고 싶은데...라는 의미입니다 상대가 받으면 SYN과 ACK를 동시에 보내게 됩니다. socket API 중 connect라는 함수를 보내게 되면 처음으로 보내게 되는 것이 SYN bit가 포함된 헤더입니다.

RST, FIN 둘 다 종료에 관한 비트입니다. 대신 친절하냐 친절하지 않느냐의 차이입니다.

FIN의 경우에는

A : 나 너랑 종료하고 싶어 + 남은 거 다 보내줄게 잠시만~ 

B : 그래 그거 잘 받았고 나도 너랑 종료할게 + 남은 거 다 보내줄게~ 

A : 그래 고마워~

라고 한다면

RST 같은 경우에는

A : 님이랑 연락 안 함 ㅂㅂ 남은 거 안 보냄 ㅃ

입니다. 아주 싹 바가지가 없습니다^^ 일반적으로 linger라는 옵션을 통해서 설정을 하거나 아니면 프로세스가 강제 종료되었을 때 os가 커버를 해주기 위한 방법으로 rst 시그널을 쏴 보내는 방법이 있습니다. 이경우에는 문제점이 rst가 중간에 유실되어도 보낸 측은 종료를 완료했지만 받아야 될 측은 아직 받지 않았기 때문에 끝났는지 모르는 경우가 발생하게 됩니다... 이경우는 실제 netstat을 확인해봐도 연결이 되어있는 것으로 나타납니다. 이건뭐... 저희가 어쩔 수 없는 부분입니다.

 

아직 네트워크 초반부이고 정리가 안되어있는 부분이 있고 그 부분을 빼놓고 설명드리다 보니 중간 생략된 부분이 꽤나 많습니다...

그런 부분들을 빼놓고 정리를 하고 있다 보니 이런 식으로 밖에 정리가 되지가 않습니다...

그래도 아주 대략적인 틀에 대해서는 설명이 쪼금 된 것 같습니다... 100% 만족스럽지는 않지만 말이죠

다음에 설명하면서 왜 이렇게 되었던 것인지 까지 종합해서 설명을 하도록 하고 오늘의 원래 목표는... 

내 L7부터 상대 L7까지 가는 길목에 있는 모든 걸 설명하려고 했는데... 글도 너무 길어지고 하니 여기까지 하고 내일 나머지 올리도록 하겠습니다

 

긴 글 읽어주셔서 감사드리고 다음에 뵙겠습니다

 

320x100

'게임서버 > TCP IP 이론' 카테고리의 다른 글

L4에 대한 정리2  (0) 2021.10.31
L4에 대한 정리  (0) 2021.10.31
L3에 관한 정리  (0) 2021.10.29
L2에 대한 내용 정리  (1) 2021.10.28
네트워크의 기본(TCP IP Layer (feat OSI 7Layer))  (1) 2021.10.27