서버 동작

Violet_Evgadn·2023년 4월 27일
0

네트워크

목록 보기
37/37

서버 수신 동작

신호를 디지털 데이터로 변환

이번 섹션에서 배울 내용들은 네트워크 흐름의 마지막임과 동시에 이미 이전에 배웠던 내용들이다.

만약 이전 섹션들에 대하여 공부를 잘했다면 이번 내용은 훑어보기만 해도 바로 이해가 될 것이다.

서버에 들어오는 신호는 전기나 빛 신호일 것이다.

서버의 수신 동작은 이를 디지털 데이터로 변환하는 것부터 시작된다.

이전에 말했듯 LAN을 흐르는 패킷의 신호는 디지털 데이터와 타이밍을 나타내는 클록 신호를 합성한 것이다.

따라서 패킷의 신호를 다시 디지털 데이터로 변환하기 위해선 클록 신호를 통해 타이밍을 계산한 뒤 이를 활용하여 디지털 데이터의 신호만을 추출하여 디지털 데이터로 변환하는 동작이 수행되어야 한다.

전송 속도에 따라 신호의 형식이 다르기 때문에 세부적인 동작이 다르므로 10BASE-T의 동작 과정에 대해서만 다뤄보자.

먼저 프리앰블 부분에서 클록을 추출할 것이다. 프리앰블 부분은 신호가 일정 간격으로 규칙적으로 변화하기 때문에 변화의 타이밍을 조사하면 클록이 어느 위치에 있는지 알 수 있다.

이후 추출한 클록을 그대로 연장시킨 뒤 신호의 변화 방향을 조사한다.

실제 신호는 플러스와 마이너스의 전압을 가지고 있으므로 전압이 플러스에서 마이너스로 변화하는지, 혹은 마이너스에서 플러스로 변화하는지 조사한다.

만약 플러스에서 마이너스로 변화한다면 디지털 데이터 0과 대응되며 마이너스에서 플러스로 변화한다면 1과 대응될 것이다.

이렇게 신호를 디지털 데이터로 변환했다면 패킷의 맨 마지막 FCS(프레임 체크 시퀀스)라는 오류 검사용 데이터를 이용하여 오류 유무를 검사한다.

만약 현재 패킷의 FCS 연산 계산 결과와 패킷에 저장된 FCS 값이 일치한다면 현재 패킷이 데이터 송신이 시작되기 전 내용과 동일하다는 의미이므로 중간에 잡음 등의 영향으로 변형되어 데이터가 변하지 않았다는 의미이다.

이후에는 MAC 헤더의 수신처 MAC 주소를 조사하여 패킷이 자신을 수신처로 하여 보낸 것인지 판단한다.

이는 이더넷의 "신호를 연결된 LAN 전체에 흘린 뒤 실제 패킷 수신자만 신호를 처리한다"라는 기본 동작 때문에 수행되는 내용이다.

서버라고 하더라도 다른 기기로 가야 할 패킷의 신호가 흘러오는 경우도 있을 것이며 이런 경우 패킷을 수신하지 않기 위하여 수신처가 자신으로 되어 있는 패킷만 남기고 나머지는 버리는 동작이 수행되어야 한다.

위 과정을 모두 마쳤다면 신호가 정상적으로 디지털 데이터로 변환되었으므로 이를 LAN 어댑터 내부 버퍼 메모리에 저장한다. 이는 LAN 어댑터의 MAC 부분이 실행한다.

이후 "인터럽트"라는 방법을 통해 LAN 어댑터에서 CPU로 패킷의 도착을 알린다.

CPU는 실행하고 있던 작업을 중단하고 LAN 드라이버로 실행을 전환한다.

LAN 드라이버는 LAN 어댑터의 버퍼 메모리에서 수신한 패킷을 추출하고 MAC 헤더의 타입 필드에 따라 프로토콜을 판별한 뒤 프로토콜을 처리하는 SW를 호출한다.

우리는 TCP/IP 규칙을 사용하여 데이터를 송/수신한다고 가정했기 떄문에 IP 프로토콜을 처리할 수 있는 TCP/IP 프로토콜 스택을 호출한 뒤 그곳에 패킷을 전달할 것이다.

프로토콜 스택 IP 담당의 패킷 처리

TCP/IP 프로토콜 스택에 패킷이 전달되었다면 일단 IP 담당 부분이 IP 헤더를 검사할 것이다.

먼저 수신처 IP 주소가 자신을 대상으로 하는지 조사한다.

서버 중 라우터와 같이 패킷을 중계하는 기능을 가지고 있는 것이 존재하는데 이 경우 수신처 IP 주소가 서버 IP 주소가 아닌 패킷이 도착할 수 있을 것이다.

만약 수신처 IP 주소와 자신의 IP 주소가 다르다면 라우터처럼 경로표에서 중계 대상을 조사한 뒤 그곳에 패킷을 중계할 것이다. 단지 이 경우 방화벽 설정에 조금 더 주의를 기울여야 할 것이다.

수신지 IP 주소가 자신의 IP 주소와 동일하다는 것을 확인했다면 다음에는 단편화(Fragmentation)에 의하여 분활되었는지 여부를 조사한다. 이 정보 또한 IP 헤더에 저장되어 있다.

만약 분할되어 있을 경우 일시적으로 패킷을 메모리에 저장해두었다가 분할된 패킷의 조각이 전부 도착한 시점에서 패킷을 조립하여 원래 패킷으로 복원한다.

이후 IP 헤더의 프로토콜 번호를 조사하여 담당 부분에 패킷을 건네주는데 값이 06이라면 TCP 담당 부분에, 11이라면 UDP 담당에 패킷을 건네주는 형식이다.

프로토콜 스택 TCP 담당 부분의 패킷 처리

이전에도 말했지만 IP 헤더와 MAC 헤더는 패킷이 어떤 동작을 위해 만들어진 패킷이든 상관없이 동일한 형식을 가지는 패킷이다.

하지만 TCP 헤더부터는 패킷의 목적에 따라 설정 값이 완전히 달라지며 이런 특성 때문에 TCP 담당 부분은 패킷의 내용에 따라 동작 방식이 달라진다.

먼저 TCP 헤더 중 SYN이 1이고 ACK가 0이라면 접속 동작의 패킷이다. 일단 수신처 포트 번호를 통해 접속 대기 상태의 소켓이 있는지 확인하고 만약 없다면 오류 통지 패킷을 클라이언트에게 반송할 것이다.

접속 대기 상태인 소켓이 있다면 Socket 라이브러리의 accept 메서드를 통해 커넥션을 연결할 소켓을 하나 복사하고 복사한 소켓에 클라이언트에 대한 정보를 저장해 놓을 것이다. 그리고 클라이언트 측으로 ACK 번호를 보낼 것이다.

이후 클라이언트 측에서도 ACK 번호가 돌아오면 3-way Handshake를 통한 접속 동작은 완료될 것이다.

이전에 말했듯 접속 대기 상태의 소켓을 복사한 실제 커넥션에 연결된 소켓들은 포트 번호가 모두 동일하기 때문에 4개 정보(송/수신처 IP 주소, 송/수신처 포트 번호)를 통해 데이터를 실제로 처리할 소켓을 판단한다.

해당 소켓에 저장되어 있는 시퀀스 번호나 받았던 데이터 조각의 길이 등을 통해 다음 시퀀스 번호 값을 계산할 것이고 이 값이 실제로 받은 패킷의 TCP 헤더 값과 일치하는지 확인할 것이다. 만약 일치한다면 정상적으로 패킷이 도착한 것이므로 패킷에서 데이터 조각을 추출하여 수신 버퍼에 저장한다.

수신 버퍼에 데이터 조각이 새로 저장될 때 마다 이전에 저장되었던 데이터와 연결되며 최종적으로는 데이터 송신 측(클라이언트 측)에서 보내고 싶었던 최종 데이터(Request Message)로 복원될 것이다.

이 과정에서 받은 TCP 헤더 시퀀스 번호 및 데이터 길이 값에 따라 ACK 번호를 기록하여 클라이언트 측에 반송할 것이다.

최종 데이터가 수신 버퍼에 복원되어 저장되었다면 이젠 Socket 라이브러리의 read 함수가 호출된다.

이를 통해 데이터가 애플리케이션 측으로 보내질 것이며 애플리케이션은 수신 데이터를 처리한 결과값을 반환할 것이다.

이후 처리한 결과값을 메시지(Respond Message)에 담아 write 명령을 통해 클라이언트 측으로 송신함으로써 데이터 송/수신 과정이 완료되는 것이다.

데이터 송/수신이 종료되었다면 연결 끊기 동작에 들어갈 것이다.

TCP 프로토콜 규칙에 의하자면 클라이언트 측이든 서버 측이든 어느 쪽에서도 연결을 끊을 수 있으며 HTTP 1.1 버전에서는 클라이언트 측에서 먼저 연결 끊기를 실행하는 경우도 많다.

하지만 HTTP 1.0 버전의 서버 측에서 연결 끊기를 먼저 시도하는 경우만 살펴보자.

서버 측 애플리케이션의 Socket 라이브러리는 close를 호출하여 TCP 담당 부분이 FIN 컨트롤 비트에 1을 설정하여 클라이언트 측에 보낼 것이다.

클라이언트 측은 ACK 번호를 일단 보낸 뒤 클라이언트 측 소켓에서 모든 데이터를 받았다고 판단된다면 FIN 비트를 1로 설정한 뒤 서버 측으로 보내고 서버 측에서는 다시 ACK 번호를 보내 4-way Handshake를 통해 연결 끊기 동작이 완료된다. 이후 연결 끊기 동작이 끝나면 잠시 기다렸다 소켓을 말소시킨다.


웹 서버 소프트웨어 동작

정적 파일 : 조회 URI를 실제 파일명으로 변환

사실 이 부분은 네트워크라기보다는 프로그래밍 쪽의 영역이다.(Spring 등)

그래서 네트워크에 대해서만 공부한다면 굳이 필수적인 부분은 아닐 것이다.

GET 방식에서 HTML 문서 같은 정적인 문서를 불러온다고 가정하자.

이 경우 URI에 쓰여 있는 경로를 통해 HTML 문서 데이터를 읽어 들이고 응답 메시지를 통해 반송하는데 단순히 URI에 기록되어 있는 파일을 그대로 디스크에서 읽어오는 것은 아니다.

단순히 URI에 기록되어 있는 경로명의 파일을 읽어오면 디스크 파일에 전부 액세스 할 수 있게 되므로 웹 서버 디스크가 무방비 상태로 노출되어 위험할 수 있다.

예를 들어 Linux계 OS를 사용하는 웹 서버라고 생각해보자.

실제 디렉터리의 역할과는 차이가 있겠으나 /home 디렉토리나 /var 디렉토리 등에 정적인 HTML 문서 파일이 저장되어 있고 /dev나 /root에는 서버의 설정이나 보안적으로 중요한 파일이 저장되어 있을 것이다.

일반적인 유저 입장에서는 /home이나 /var에 대해서만 접근해도 되는데 만약 URI를 통해 모든 디렉터리에 접근할 수 있다면 /dev나 /root에도 접근할 수 있을 것이며, 이는 보안적으로 매우 위험한 상황이 되는 것이다.

따라서 웹 서버에서 공개하는 디렉터리는 디스크의 실제 디렉터리가 아닌 가상으로 만든 디렉토리이고, 이 가상 디렉토리 구조에서의 경로명을 URI에 써야 한다.

예를 들어 /var/www라는 디렉터리를 웹 서버 애플리케이션에서는 가상으로 /www라 본다고 가정하자.

이 경우 URI 부분에 /www/sample.html 파일을 요청했을 때 웹 서버 애플리케이션은 이를 /var/www/sample.html로 변환한 뒤 그곳에 존재하는 정적 HTML 파일을 클라이언트에게 반송해 주는 것이다.

여기에 이전에 설명했던 디렉토리명만 URI로 보낼 때, 예를 들어 /www/만 URI로 왔을 때 설정 값에 따라 자동으로 index.html로 변환해 주는 등의 동작을 통하여 정적 HTML 파일을 찾아 클라이언트 측으로 반송할 것이다.

Apache 같은 웹 서버 애플리케이션에서는 파일명을 바꿔 쓰는 규칙을 서버 측에 설정하여 규칙에 따라 파일에 액세스 하도록 설정하기도 한다.

실행 파일 : CGI 프로그램을 작동

URI에 실행 프로그램 파일을 입력할 경우 해당 프로그램을 작동시켜 프로그램이 출력하는 데이터를 클라이언트 측에 반송한다.

웹 서버에서 작동시키는 프로그램은 다양하며 그 유형에 따라 동작 방식도 달라지지만 CGI 타입 프로그램만 간단히 확인해 보자.

일단 사용자가 입력했거나 프로그램 실행을 위해 필요한 데이터가 GET 방식의 경우 파라미터로써, POST 방식의 경우 메시지 본문에 저장되어 서버 측으로 도착할 것이다.

이후 웹 서버는 URI의 확장자(.cgi, .php 등)를 통해 실행 프로그램인지를 판단하거나 프로그램용 디렉터리를 설정하여 설정된 디렉토리 내의 파일일 경우 모두 실행 프로그램으로 판단한다.

실행 프로그램일 경우 웹 서버는 OS에 프로그램을 작동시키도록 의뢰하며 리퀘스트 메시지에서 데이터를 추출하여 프로그램에게 건네준다.(헤더까지 건네주는 경우도 있다)

OS가 작동시킨 프로그램은 데이터를 처리한 뒤 결과물을 만들어낼 것이며 이를 웹 서버에 되돌려준다.

주로 실행 프로그램이 웹 서버 측에 되돌려주는 문서는 HTML 문서이므로 이를 통해 응답메시지를 만들고 클라이언트 측에 반송한다.

이때 웹 서버는 실행 프로그램이 어떠한 동작을 하는지, 어떠한 결과물을 내는지 알지 못하므로 웹 서버는 내용에 관여하지 않는다.

액세스 제어

웹 서버에서 동작을 실행할 때 사전에 설정해 둔 조건에 부합하는지 조사하고 조건에 해당하는 경우 그 동작을 금지하거나 조건에 해당하는 경우에만 동작을 실행시킬 수도 있다.

이처럼 조건에 따라 액세스 동작 여부를 설정하는 기능을 "액세스 제어"라고 한다.

이는 회원제 시스템에서 특정 사용자에게만 액세스를 허가해야 하는 경우 등에서 활용되며 이번 섹션에서 유일하게 처음 배우는 개념일 것이다.

웹 서버에서 설정하는 조건은 주로 "클라이언트 주소", "클라이언트 도메인명", "사용자명과 패스워드" 3가지이다.

클라이언트 IP 주소가 설정되어 있을 경우는 간단하게 처리할 수 있는데 accept로 접속 단계를 수행할 때 클라이언트 IP 주소를 알 수 있으므로 이 과정에서 점검을 하기만 하면 된다.

클라이언트 도메인 명이 조건으로 설정되어 있는 경우에는 DNS 서버를 이용해야 한다.

보통 DNS 서버를 사용하는 것은 도메인명에서 IP 주소를 조사하기 위함이지만 그 반대 상황에서도 DNS 서버를 사용한다.

웹 서버가 리퀘스트 메시지를 받으면 웹 서버는 패킷의 송신처 IP를 조사한 뒤 이를 조사하는 UDP 메시지를 만들어 가장 가까운 DNS 서버에 보낼 것이다.

이후 UDP 메시지를 받은 웹 서버 측 DNS 서버는 송신처 IP 주소가 등록된 클라이언트 측 DNS 서버를 찾아 조회를 보낼 것이며 이를 통해 웹 서버는 도메인명을 회답받을 수 있을 것이다.

송신처 IP 주소를 통해 도메인명을 찾았다면 다시 도메인명에서 IP 주소를 조사한 뒤 송신처 IP 주소와 일치하는지를 확인한다.

즉 "송신처 IP 주소를 통해 도메인 명 찾아냄 → 찾아낸 도메인 명으로 다시 IP 주소 찾아냄 → 찾아낸 IP 주소와 송신처 IP주소가 일치하는지를 확인함"이라는 과정을 거치는 것이다.

이는 도메인명을 위조하여 DNS 서버에 등록하는 공격 방법이 있으므로 이를 방지하기 위하여 이중으로 점검하는 것이다.

양쪽이 일치하면 웹서버는 조건에 따라 액세스 할 수 있는지 여부를 판단할 것이다.

이 방법은 DNS 서버에서의 조회 과정이 많이 수행되어야 하기 때문에 그만큼 웹 서버 응답 시간이 길어진다.

마지막으로 사용자명과 패스워드가 조건으로 설정된 경우이다.

클라이언트 측에서는 일단 평범한 리퀘스트 메시지를 보낼 것인데 이 메시지에는 사용자명 및 패스워드가 입력되어 있지 않을 것이다.

따라서 웹 서버는 사용자명과 패스워드가 기록된 리퀘스트 메시지를 보내도록 응답 메시지를 통해 클라이언트 측에 통지한다.

이때 응답 코드 및 메시지는 "401 Authorization Required"일 것이며 "WWW-Authenticate"라는 HTTP 헤더가 추가적으로 붙게 된다.

브라우저에서는 응답 메시지를 받으면 사용자명과 패스워드를 입력하는 화면을 표시할 것이다.

여기에서 사용자가 사용자명과 패스워드를 입력하여 웹 서버 측에 보내면 브라우저는 이를 리퀘스트 메시지에 기록하고 다시 한번 서버에 액세스 한다.

이때 "Authorization"이라는 헤더 필드가 추가되어 웹 서버 측으로 보내지게 되는데 이 헤더 필드의 값으로는 암호화 방식과 기입한 암호화 방식으로 암호화된 사용자명&패스워드를 저장한다.

웹 서버는 메시지를 받은 뒤 기입된 암호화 방식을 통해 사용자명과 패스워드를 복호화할 것이다.

이후 사용자명과 패스워드를 사전에 설정한 설정 값과 대조하여 액세스 가능 여부를 판단한 뒤 액세스를 허가하는 경우에만 데이터를 반송할 것이다.


웹 브라우저에서의 화면 표시

화면 표시 동작 과정

웹 브라우저는 웹 서버 측에서 Respond Message를 받을 것이고 해당 메시지를 수신하는 과정은 서버 측 신호 수신 과정과 동일하다.

단지 서버와 브라우저에 차이가 있다면 서버는 "데이터를 처리"하는 곳이고 브라우저는 "처리한 데이터를 보여주는" 곳이라는 것이다.

즉, 웹 브라우저는 메시지를 받았다면 이를 화면에 표시해야 한다.

화면 표시 동작은 응답 메시지에 저장된 데이터가 어떤 종류인지 조사하는 것부터 시작된다.

웹에서 취급하는 데이터는 문장, 화상, 음성, 영상 등 다양하며 종류에 따라 표시 방법도 다르므로 이 과정이 필수적이다.

데이터의 종류를 판단하는 근거로는 몇 가지가 있는데 응답 메시지 맨 앞부분에 있는 "Content-Type"이라는 헤더 값으로 판단하는 것이 원칙이다.

여기에는 다음과 같은 형식으로 종류를 써야 한다.

Content-Type: text/html

Content-Type 내용 중 /의 왼쪽 부분을 "주 타입"이라고 하며 이를 통해 데이터 종류 대분류를 나타낸다.

그리고 /의 오른쪽 부분인 "서브 타입"을 통해 실제 데이터의 종류를 나타낸다.

주타입과 서브 타입의 의미는 모두 결정되어 있고 이 중 자주 쓰는 타입은 아래에 정리해 두었다.

데이터의 종류가 텍스트일 경우 어떤 문자 코드를 사용하는지도 판단해야 하는데, 이때 "charset"으로 문자 코드 정보를 부가한다.

Content-Type: text/html; charset=utf-8

utf-8은 UNICODE, euc-kr은 EUC 코드, iso-2022-jp이면 JIS 코드라는 의미를 가진다.

이렇게 데이터 종류를 조사하였다면 Content-Encoding이라는 헤더 필드의 값도 조사한다.

만약 압축 기술이나 암호화 기술에 따라 원래 데이터를 변환하였다면 어떤 기술을 활용하여 변환을 했는지 Content-Encoding 필드에 기록해야 한다.

이를 기록해 놔야 나중에 클라이언트 측에서 데이터를 복호화하거나 압축을 해제할 수 있기 때문이다.

Content-Type 필드를 사용하여 데이터 종류를 나타내는 방법은 MIME라는 사양에 규정되어 있는 방법이다.

MIME는 원래 전자 메일에서 화상이나 첨부 파일 등의 문자 이외 정보를 보내기 위해 정해진 사양으로 나중에는 웹에서도 이용하게 되었다.

하지만 웹 서버 운영 관리자가 익숙하지 않아 설정이 부적절하여 Content-Type에 정확한 헤더 값이 설정되지 않는 상황이 발생할 수 있기 때문에 Content-Type만을 통해 데이터 종류를 파악하는 데는 한계가 있다.

따라서 다른 판단 근거를 사용하여 종합적으로 데이터 종류를 판단하는 경우도 있는데 요청한 파일의 확장자나 데이터 내용의 포맷 등을 통해 종합적으로 판단하는 것이다.

예를 들어 HTML 문서 같은 경우 ".html"이라는 확장자명으로 판단하거나 화상 데이터는 맨 앞부분에 화상 데이터임을 판단할 수 있는 정보가 기록되어 있어 이를 통해 판단하는 경우가 있다. 물론 이 부분은 정해진 규격은 아니므로 브라우저의 종류나 버전에 따라 동작이 달라질 수 있다.

◎ Content-Type 데이터 형식

profile
혹시 틀린 내용이 있다면 언제든 말씀해주세요!

0개의 댓글