TCP가 신뢰성에 기반을 둔 전송 계층 프로토콜이라면 UDP는 빠른 데이터 전송 위주의 프로토콜입니다. 속도를 떨어뜨리는 요소를 없애 TCP처럼 각종 흐름제어, 오류제어, 혼잡 제어를 하지 않습니다. 따라서 UDP는 비신뢰성(Unreliable) 프로토콜입니다.
이 UDP 프로토콜, 저와 같은 세대시라면 어디서 많이 보셨죠? 저는 뮤컨을 위해서 스타크래프트의 UDP는 필수였습니다. 이렇게 데이터는 그다지 중요하지 않으면서 속도가 빠른 데이터 전송이 필요할때 UDP를 사용합니다.
데이터 전송과정을 보게 되면 이렇습니다.
만일 송신에서 데이터를 1, 2, 3 순으로 전송했다고 할때 네트워크의 사용량에 따라 어떤 데이터그램은 늦게 도착할 수 있습니다. 3번 데이터그램이 가장 좋은 경로를 타고 와서 먼저 도착하게 됩니다. 그 이후에는 1번 데이터그램이 도착하겠네요. 마지막으로 가장 부하가 많은 경로를 타고 들어온 2번 데이터그램이 도착합니다.
결국 수신자는 3, 1, 2 데이터그램 순으로 전달받게 됩니다. 연결과정 자체를 갖지 않기 때문에 데이터의 순서를 보장하지 않습니다.
헤더 정보(UDP Header)
빠른 전송을 요구할때 쓰이는 UDP는 헤더의 구조가 너무나 간단합니다. 헤더는 단 4가지 정보만 존재합니다.
너무 간단합니다.
- Source Port: 송신지의 Port 번호입니다.
- Destination Port: 목적지의 Port 번호입니다.
- Total Length: 헤더를 포함한 전체 데이터그램의 크기를 의미하는 필드입니다.
- Checksum: 데이터그램의 오류를 확인하기 위한 필드입니다.
UDP 특징
UDP의 특징을 요약하면 다음과 같습니다.
1) TCP와 같이 연결을 성립하는 3-way-handshake와 같은 과정이 없습니다. 따라서 비연결형인 특징을 갖습니다. 연결을 확립하는 TCP는 회선을 연결한듯 정확한 연결을 요구하므로 가상회선 방식이라 하며 UDP는 그런 과정없이 데이터를 쪼개 보내므로 데이터그램 방식이라 합니다.
2) 따라서 데이터의 순서, 유실을 보장하지 않고, 데이터 수신 실패시 재전송을 요구하지 않습니다. 데이터를 수신할때 순서가 뒤바뀔 수도 있습니다. 이는 곧 비신뢰성을 의미합니다.
3) 데이터를 일방적으로 수신받기 때문에 속도가 매우 빠릅니다.
4) 데이터의 checksum으로 최소한의 오류만을 검출합니다. 그러니까 받는 데이터는 검사를 하지만 유실된 데이터는 모른다는 것이지요.
IP 계층 위에서 동작하는 TCP는 연결 지향 프로토콜입니다. 마치 물리적인 선로로 연결되어 있는 것처럼 가상의 연결 통로를 설정하여 통신합니다. 그만큼 정확한 데이터 전송을 요구하며 신뢰성을 보장하기 때문에 신뢰할 수 있는 프로토콜(Reliable Protocol)이라고 합니다. 신뢰성 보장을 위해서 다음의 제어를 수행합니다.
1) 흐름 제어(Flow Control)
데이터를 보내는 사람, 받는 사람이 있다고 할때 줄 수 있는 양, 받을 수 있는 양이 다릅니다. 그래서 상대방이 받을 수 있을 만큼의 양으로 적절히 전송하는 것을 흐름제어라고 합니다.
2) 혼잡 제어(Congestion Control)
네트워크가 혼잡해지면 송신자가 데이터의 전송량을 제어하는 것을 뜻합니다. 혼잡하다라는 것을 어떻게 알 수 있을까요? 데이터의 손실 발생이 많아지면 이는 즉, 네트워크가 혼잡한 상태로 판단하여 전송량을 제어하게 됩니다.
3) 오류 제어(Error Control)
데이터의 오류나 손실없는 전송을 보장해주는 것이 혼잡 제어입니다. 오류 발생 시 TCP는 데이터를 제전송합니다.
TCP 헤더
TCP 프로토콜은 신뢰성을 보장하기 위해서 헤더에 많은 정보를 포함하게 되는데 각 필드에 어떤 정보를 포함하는지 알아보도록 하겠습니다.
-Source Port: 출발지의 포트, 즉 데이터를 보내는 컴퓨터의 포트 정보입니다. 컴퓨터가 갖을 수 있는 포트는 65536개이므로 사이즈가 2바이트인것을 확인하세요.
-Destination Port: 반대로 목적지의 포트입니다.
-Sequence Number : 송신 데이터의 일련 번호를 담고 있습니다.
- Acknowledgement Number : 그전의 데이터를 잘 받았다는 표시로 상대방이 다음에 전송할 일련번호를 담고 있습니다. 줄여서 ACK라고 하겠습니다.
-HLEN(Header Length) : 헤더의 정보를 담고 있습니다. 4 bits의 워드 단위입니다. 헤더의 길이는 최소 20바이트 ~ 60바이트까지입니다.
-Reserved : 예약된 비트입니다. 아직 사용하지 않습니다. 나중을 위해서 남겨두는 비트인 셈이지요.
-Control Flags
FLAG
설명
URG
(Urgent Pointer)
Urgent Pointer의 필드가 유요하다는 의미의 FLAG
ACK
(Acknowledgement)
수신 확인 응답 FLAG
PSH
(Request for push)
송수신 버퍼의 있는 데이터 즉시 처리 요청 FLAG
RST
(Reset the connection)
연결을 강제 중단합니다. TCP가 유지되고 있을때 이 FLAG를 사용하면 그 즉시 연결을 끊어 버립니다. 해커들이 Hijacking을 위해 피해자의 연결을 끊어버릴때 사용합니다. 보통의 정상적인 종료는 아래의 FIN FLAG를 설정합니다.
SYN
(Synchronize sequence number)
연결 설정 FLAG
FIN
(Terminate the connection)
정상 종료의 연결 종료 FLAG
- Window Size : 수신자에서 송신자로 보내는 수신자의 윈도우 사이즈입니다. 즉, 수신 버퍼의 여유공간 크기를 의미하게 되지요. 송신자는 이 윈도우 사이즈 범위 내에서 수신측의 수신 확인(ACK)을 확인하지 않고 연속적으로 데이터를 보낼 수 있습니다.
- Checksum : 오류를 검사하기 위한 필드입니다. 전체 데이터가 오류가 나 변형되었는지 확인합니다.
- Urgent Pointer : 긴급 데이터의 위치값을 담고 있습니다.
TCP의 연결 설정 과정(3-way handshake)
TCP연결은 어떻게 연결이 될까요? 3단계 절차에 따라서 연결이 성립됩니다. 이때 사용되는 FLAG는 2개입니다. 바로 SYN과 ACK입니다. 다음의 그림을 통해서 연결과정을 알아보도록 합시다.
1. 최초 클라이언트 측에서 동기화를 위해 SYN FLAG와 함께 Seq(uence) Number를 임의로 설정해서 보내줍니다. 이때 최초의 Seq number를 ISN(Initial Sequence Number)라고 합니다.
아직 상대방에게서 데이터를 수신하지 않았으므로 Ack(nowledgement) Number 필드는 비어있네요. SYN 패킷을 보냈으므로 클라이언트의 상태는 SYN_SENT가 됩니다. 서버는 SYN을 받았으므로 SYN_RECV 상태 또는 SYN_RCVD상태가 됩니다.
이때 클라이언트가 적극적으로 연결 요청을 하고 있네요. 이것을 Active Open이라 합니다. 서버는 수동적으로 받아들이고 있네요. 이것을 Passive Open이라고 합니다.
2. 서버에서 동기화 요청을 받았으면 잘 받았으니 연결하자고 요청합니다. 클라이언트에서 보낸 Ack number에 받은 Seq에 +1을 하여 다음 Seq Number를 요구합니다. 클라이언트의 Seq number가 100이므로 101을 Ack number로 보내는 군요. 또한 자신도 역시 ISN을 설정하여 다시 클라이언트로 보냅니다.
이때 사용한 플래그는 ACK와 SYN입니다. ACK와 SYN이 유요한 데이터이기 때문이죠. 이 페킷을 보낸 후 서버는 연결 확립(ESTABLISHED)상태가 됩니다.
3. 클라이언트는 이에 대한 응답으로 서버에게 ACK num을 설정하여 보냅니다. 이 패킷을 준 후 클라이언트도 연결 확인 상태가 됩니다.
이렇게 보면 초기에 데이터가 왔다 갔다 3번하고 있죠? 이것을 3-Way Handshake라고 합니다.
아래는 실제 3 way handshake를 와이어샤크로 찍어본 화면입니다.
연결 종료(4-way handshake)
정상적인 연결 종료는 FIN, ACK의 플래그를 통해서 이루어집니다. 아래와 같이 4단계를 거쳐 연결이 종료가 됩니다.
1. 연결상태에 있던 클라언트가 연결을 종료하기 위해 FIN을 보냅니다. 이때 클라이언트의 상태는 FIN_WAIT_1 상태가 되고 서버는 CLOSE_WAIT 상태가 됩니다. 3 way handshake와 마찬가지로 먼저 close요청을 한쪽이 Active Close, 받은쪽이 Passive Close라고 합니다.
2. 수신하는 서버는 이에 대한 응답으로 ACK를 보냅니다. 이때 클라이언트는 FIN_WAIT_2의 상태가 됩니다.
3. 서버는 이 후 소켓을 받는 시스템 콜(close)을 호출할때까지 대기 상태로 있다가 소켓이 종료되면 FIN을 보냅니다. 마지막 FIN과 함께 ACK를 보냈으므로 LAST_ACK 상태가 됩니다.
4. 서버로부터 FIN을 받은 클라이언트는 ACK응답을 하여 2MSL만큼의 시간(보통 1분에서 4분)이후 연결 종료 상태(CLOSED)가 됩니다. 서버 CLOSED상태가 되어 연결이 종료됩니다.
이러한 과정을 4-way handshake로 연결이 정상적으로 연결이 종료되는 과정입니다.
네트워크 통신을 하는 표준 방법으로 프로세스간 연결의 종점이라고 볼 수 있습니다. 기본적인 개념은 아래의 그림과 같습니다.
위의 그림은 TCP/IP에서의 인터넷 통신을 보여줍니다. 클라이언트의 컴퓨터의 물리적 주소(MAC 주소) DD-44-EE-55-FF-66이며 논리적 주소(IP 주소)는 10.2.2.2입니다. 클라이언트는 여러가지의 프로그램을 실행시키고 있는데 그 중 어떤 프로세스는 TCP 포트번호 12345를 사용합니다. 이 클라이언트 프로세스는 물리적 주소가 11-AA-22-BB-33-CC이며 논리적 주소 10.1.1.3인 서버 컴퓨터의 포트 번호 80번을 사용하는 서버 프로세스와 연결되어 있습니다.
구체적으로 어떻게 통신할까요? 각 프로세스는 소켓을 통해서 통신을 하게 되는데, 소켓은 간단히 얘기해 ip주소와 포트번호를 갖고 있는 인터페이스라고 생각하면 됩니다. 소켓은 리눅스에서 파일로 다루어지며 프로세스는 이 소켓을 사용할때 파일디스크립터를 통해 사용합니다. 우리는 리눅스 파일 입출력에 대해 배울때 파일디스크립터를 사용했지요? 소켓 역시 파일디스크립터를 이용해서 읽기, 쓰기가 가능합니다.
소켓 통신할때 필요한 주요함수는 무엇이 있을까요? 간단히 알아보도록 합시다.
1. socket(int domain, int type, int protocol)
소켓을 만드는데 바로 이 함수를 사용합니다. 소켓 역시 파일로 다루어지기 때문에 반환값은 파일디스크립터입니다. 만약 소켓을 여는데 실패했다면 -1을 리턴합니다.
연결된 원격 컴퓨터의 정보는 remote_host에 저장됩니다. 오류시에 -1을 반환합니다.
6. send(int fd, void* buffer, size_t n, int flags)
buffer를 소켓 파일 디스크립터인 fd로 전송합니다. 보낸 바이트수를 반환하며 실패시 -1을 반환합니다.
7. recv(int fd, void* buffer, size_t n, int flags)
send함수와 사용법이 거의 비슷합니다. n바이트를 buffer로 읽습니다. 성공시 받은 바이트수를 반환하며 실패시 -1을 반환합니다.
이제 예제를 보며 더 자세한 설명을 하도록 하죠. 다음 예제는 서버 프로그램이 클라이언트 프로그램에서 전송한 메시지를 출력해주는 소스코드입니다. 클라이언트 프로그램은 텔넷을 사용할 것이기 때문에 따로 클라이언트 프로그램 소스코드는 없습니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 12346
#define BUF_SIZE 1024
int main(void){
int socket_fd,accepted_fd;
struct sockaddr_in host_addr, client_addr;
socklen_t size;
int recv_length;
char buffer[BUF_SIZE];
socket_fd=socket(PF_INET,SOCK_STREAM,0);
host_addr.sin_family=AF_INET;
host_addr.sin_port=htons(PORT);
host_addr.sin_addr.s_addr=0;
memset(&(host_addr.sin_zero),0,8);
bind(socket_fd,(struct sockaddr *)&host_addr,sizeof(struct sockaddr));
listen(socket_fd,3);
while(1){
size=sizeof(struct sockaddr_in);
accepted_fd=accept(socket_fd,(struct sockaddr *)&client_addr,&size);
send(accepted_fd,"Connected",10,0);
printf("Client Info : IP %s, Port %d\n", inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
recv_length=recv(accepted_fd,&buffer,BUF_SIZE,0);
while(recv_length>0){
printf("From Client : %s\n",buffer);
recv_length=recv(accepted_fd,&buffer,BUF_SIZE,0);
}
close(accepted_fd);
}
return 0;
}
여기서 포트번호는 12346을 사용한다고 하겠습니다.
socket_fd=socket(PF_INET,SOCK_STREAM,0);
socket의 첫번째 인자는 프로토콜 체계 PF(Protocol Family)를 지정합니다. PF_INET은 인터넷 IP프로토콜 체계입니다. 사용하는 프로토콜 체계에는 여러가지가 있습니다. IP외에도 1970년대에 개발된 공중 데이터 네트워크에 대한 표준 X.25 외에도 애플토크,XEROX 네트워크 등등 있는데 우리는 IP를 사용할 것이기 때문에 PF_INET만 사용할 것입니다.
두번째 인자는 소켓의 타입입니다. 가장 보편적으로 사용하는 타입은 Stream과 Datagram입니다. SOCK_STREAM은 연결형, SOCK_DGRAM은 비연결형이라고 생각하면 되겠습니다.
다음은 바인드할때 구조체를 넘겨야하는데요. 이 프로세스가 사용할 소켓 fd와 컴퓨터의 IP주소, 포트와 묶는 작업이라고 보면 됩니다.
우리는 TCP/IP 상에서의 통신이기 때문에 IPv4용 구조체인 sockaddr_in을 사용합니다.
sin_family에는 IP용 Address Family(AF_INET)을 지정합니다.
sin_port는 이 프로세스가 사용할 포트번호를 지정합니다.
sin_addr.s_addr에는 주소가 들어가게 되는데요. 0은 현재 컴퓨터의 주소를 자동으로 채우라는 의미입니다. 그것이 아니라면 주소를 직접 지정해주어야합니다.
htons?
sin_port에서 htons는 무슨 함수일까요? 이 함수의 풀 네임은 host-to-network short로 16비트 정수를 호스트 바이트 순서에서 네트워크 바이트 순서로 변환하는 함수입니다.
AF_INET 소켓 주소 구조체에서 사용되는 포트 번호와 IP주소는 빅 엔디언(Big-Endian)이라는 네트워크 바이트 순서를 따릅니다. 이것은 보통 우리가 사용하는 x86의 리틀 엔디언과는 반대의 표기법이죠. 그래서 변환없이 그대로 사용하게 되면 바이트 순서가 달라지게 되어 제대로 동작하지 않습니다.
이제 bind를 호출하는데 두번째 인자를 보세요. (struct sockaddr*)로 형 변환하고 있습니다. host_addr이라는 구조체 변수는 sockaddr_in이라는 구조체입니다.
bind는 TCP/IP뿐만 아니라 X.25, 애플토크 등 여러 프로토콜이 존재하기 때문에 인자로 받아야할 구조체 형이 sockaddr_in뿐만이 아닙니다. 그래서 일반화된 구조체가 필요하게 되는데 그 구조체가 sockaddr입니다.
sockaddr_in은 sockaddr로 형변환할 수 있습니다. 왜냐하면 구조체의 크기가 같기 때문이죠. sockaddr 구조체는 2바이트의 Address Family와 14바이트의 주소를 사용합니다. 반면 sockaddr_in의 IPv4 전용 구조체는 sa_data의 주소를 포트번호, ip주소, 기타 추가 비트를 포함하고 있죠.우리는 일반화된 sockaddr에 sa_data에 직접 포트번호와 주소를 읽고 쓰기가 상당히 불편합니다.
그래서 더 사용하기 편한 인터넷 전용 구조체를 사용합니다. 형변환에 문제가 없게 크기를 같게 만들어 호환성에 문제가 없습니다.
listen(socket_fd,3);
그 소켓으로 들어오는 연결을 기다립니다. 마지막인자는 백로그 큐의 최대크기입니다.
while(1){
size=sizeof(struct sockaddr_in);
accepted_fd=accept(socket_fd,(struct sockaddr *)&client_addr,&size);
send(accepted_fd,"Connected",10,0);
printf("Client Info : IP %s, Port %d\n", inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
recv_length=recv(accepted_fd,&buffer,BUF_SIZE,0);
while(recv_length>0){
printf("From Client : %s\n",buffer);
recv_length=recv(accepted_fd,&buffer,BUF_SIZE,0);
}
close(accepted_fd);
}
accept를 통해서 이제 새롭게 연결된 클라이언트 전용 파일디스크립터를 얻어옵니다. 클라이언트에 대한 정보는 client_addr에 저장됩니다.
연결이 성공돼었다면 send를 통해서 "Connected"라는 문자열을 클라이언트 쪽으로 보냅니다.
그 후 클라이언트의 정보를 출력하지요. 두가지를 출력합니다. IP주소와 Port번호입니다.
inet_ntoa(struct in_addr *network_addr)
네트워크 주소를 숫자사이의 점을 찍는 IP주소로 network to acsii라는 뜻입니다. ip주소가 담긴 in_addr구조체는 32비트의 네트워크 주소를 갖고 있기 때문에 숫자사이에 점을 찍는 형태로 바꾸려면 이 함수를 사용합니다.
ntohs(Network-to-Host Short)
이것 역시 네트워크 바이트 순서가 빅 엔디안이고, 호스트의 바이트 순서가 리틀 엔디안일때 변환해야할때 사용합니다.
그 후에는 계속 recv를 통해 클라이언트에서 입력받은 메시지를 서버에서 그대로 출력해줍니다.
결과
서버
# gcc server.c
# ./a.out
클라이언트
# telnet 192.168.10.131 12348
Trying 192.168.10.131...
Connected to 192.168.10.131.
Escape character is '^]'.
Connected
서버
Client Info : IP 192.168.10.131, Port 43774
클라이언트
Hi
I'm Reakwon
서버
From Client :
From Client : Hi
From Client : I'm Reakwon
파이프(Pipe)란 프로세스간 통신을 할때 사용하는 커뮤니케이션의 한 방법입니다. 가장 오래된 UNIX 시스템의 IPC로 모든 유닉스 시스템이 제공합니다. 하지만 두가지 정도의 한계점이 있습니다.
첫번째 한계점으로 파이프는 기본적으로 반이중 방식입니다. 물론 전이중 방식을 지원하는 시스템이 있긴 하나, 최대의 이식성을 위해서는 파이프는 반이중 방식이라는 생각을 해야합니다. 이것은 FIFO라는 명명된 파이프로 극복할 수 있습니다.
두번째 한계점으로는 부모, 자식 관계에서의 프로세스들만 사용할 수 있습니다. 부모프로세스가 파이프를 생성하고, 이후 자식 프로세스와 부모프로세스가 파이프를 이용하여 통신합니다.
이러한 한계점이 잇긴 하지만 여전히 쓸모있는 IPC기법입니다.
파이프는 unistd.h 헤더파일이 존재합니다.
#include <unistd.h>
int pipe(int fd[2]);
pipe함수가 성공적으로 호출되었다면 0, 실패했을 경우 -1을 반환합니다.
인자 fd는 2개의 원소가 있는 배열이라는 점을 주목합시다. 2개의 원소를 쓰는 이유가 있습니다. 아래의 그림을 보면서 이해합시다.
파이프는 커널영역에 생성되어 파이프를 생성한 프로세스는 파일 디스크립터만 갖고 있게 됩니다. 여기서 파일디스크립터 fd[1]은 쓰기용 파이프, fd[0]은 읽기용 파이프입니다. 그러니 우리가 만약 데이터를 fd[1]에 쓰게 되면 fd[0]으로 그 데이터를 읽을 수 있는 것입니다.
그렇다면 자식 프로세스를 하나 더 두어서 자식과 부모가 통신할 수 있게 하려면 어떻게 해야할까요? 우선 자식 프로세스를 fork하면 파일 디스크립터는 부모의 파일디스크립터를 자식이 그대로 사용할 수 있는 것을 활용합니다. (파일디스크립터가 그대로 자식프로세스에 복제됩니다.)
부모프로세스는 파이프에 데이터를 쓰는 프로세스, 자식 프로세스는 그 파이프에서 데이터를 읽는 프로세스로 설계합시다.
우선 부모 프로세스에서 파이프를 생성하면 파이프에 데이터를 쓸것이기 때문에 읽기 파이프는 닫습니다. fd[0]이죠? 그런 후 fd[1]에 데이터를 씁니다.
자식 프로세스는 쓰기 파이프는 쓰지 않으므로 fd[1]을 닫고, 읽기 파이프로 데이터를 읽습니다.
그렇다면 부모 프로세스와 자식 프로세스가 읽기, 쓰기가 가능하게 구현하려면 어떻게 해야할까요? 파이프를 한개만 사용한다고 해봅시다.
그리고 이런 상황을 가정해보지요.
1. 먼저 부모프로세스가 파이프에 fd[1]로 데이터를 보냅니다.
2. 그 이후 자식 프로세스가 부모 프로세스가 쓴 데이터를 fd[0]으로 읽습니다.
3. 자식 프로세스는 바로 fd[1]로 파이프에 응답값을 보냅니다.
4. 부모 프로세스는 fd[0]으로 자식 프로세스가 보낸 응답값을 읽습니다.
결론을 말씀드리면 항상 위의 상황은 발생하지 않습니다. 그 이유는 누가 먼저 파이프를 읽느냐에 따라서 결과가 달라지는데, 만일 부모프로세스가 파이프에 쓰고, 자식 프로세스가 그 데이터를 읽기도 전에 부모프로세스가 먼저 데이터를 읽는다면 파이프에 데이터는 없겠죠. 허나 자식 프로세스는 없는 데이터를 계속 읽기만 기다리고 있기 때문에 프로그램이 망하게 되는 겁니다.
이때는 파이프를 2개 사용해야합니다.
fdA와 fdB 2개 사용합니다. 부모프로세스는 자식에게 쓰기용으로 fdA[1], 자식프로세스로부터 읽기용으로 fdB[0]만 있으면 됩니다. 필요없는 fdA[0], fdB[1]은 닫아줍니다.
그리고 자식프로세스는 부모프로세스로부터 읽기용으로 fdA[0], 쓰기용으로 fdB[1]만 있으면 되지요. 역시 필요없는 fdA[1], fdB[0]은 닫아줍니다.
SSH란 원격에서 서버로 터미널에 접속할때 암호화를 통해 안전한 접속환경을 제공하여 telnet과 같은 안전하지 않은 원격 터미널 접속 프로그램을 보완하는 프로토콜 또는 프로그램입니다. SSH는 기본적으로 포트 22번을 사용합니다.
원격에서 telnet으로 서버에 접속할 경우 암호화 과정을 거치지 않으므로 스니핑을 한다면 계정과 비밀번호가 고스란히 노출됩니다. 하지만 ssh를 사용하여 접속한다면 암호화가 되었기 때문에 스니핑을 하여 패킷을 보더라도 의미를 파악할 수 없습니다. 또한 강력한 인증기능까지 지원합니다.
SSH의 주요 기능은
보안 접속을 통한 rsh, rcp, rlogin, rexec, telnet, ftp 등을 제공합니다.
IP spoofing을 방지하기 위한 기능을 제공합니다.
X11 패킷 포워딩 및 일반적인 TCP/IP 패킷 포워딩을 제공합니다.
[출저 : 위키백과]
그렇다면 telnet접속과 ssh접속은 어떻게 다른지 눈으로 직접 살펴봅시다.
telnet으로 서버접속( 클라이언트 IP : 110.13.7.47 , 서버 IP : 110.13.7.20)
window 클라이언트에서 cmd에서 telnet을 통해 서버로 접속합니다.
엔터를 치는 순간 네트워크에서는 어떤 페킷이 오고 갔을까요? 아래는 와이어샤크로 본 페킷들입니다. 이 페킷을 보면 localhost login: 이라는 문자열을 볼 수 있습니다.
서버(110.13.7.20)에서 클라이언트(110.13.7.47)로 보내는 군요. 그렇다면 클라이언트의 telnet 터미널 화면은 어떻게 됐을까요?
localhost login: 이라는 로그인 화면으로 들어왔습니다. 그렇다면 위의 페킷은 로그인하라는 디스플레이 메시지를 보냈다는 것을 짐작할 수 있겠군요.
이제 로그인을 시도해보지요. 아이디는 reakwon, 비밀번호는 reakwon입니다. 여기서 클라이언트(110.13.7.47)에서 서버(110.13.7.20)로 보내는 페킷만을 봅시다.
자, r이라는 문자를 보내는 군요. 이후에 75.6.355213의 페킷을 봅시다. r문자를 보낸 이후의 페킷입니다.
e라는 문자가 찍혔네요. 이렇게 reakwon이라는 문자를 서버에 보내고 서버는 그에 대한 응답으로 다시 reakwon이라는 문자를 각각 echo합니다. 이렇게 유저의 계정은 찾을 수 있습니다.
비밀번호 역시 다르지 않습니다. 이후의 페킷을 보면 알 수 있습니다. 다음은 계정의 비밀번호를 알아낼 수 있는 페킷들입니다. 하나씩 보도록 하지요.
페스워드의 시작을 알리는 페킷이 나옵니다.
이후 아까처럼 reakwon의 문자들을 연속해서 서버에 보냅니다. 비밀번호를 입력할땐 서버로 부터 echo가 오지 않는군요.
이제 망했습니다. 해킹당했군요.
ssh로 서버접속( 클라이언트 IP : 110.13.7.47 , 서버 IP : 110.13.7.20)
현재 디렉토리를 알려주는 명령어입니다. 현재 유저가 어느 위치에 있는지 알아볼 수 있는 명령어입니다.
아래의 예에서는 현재 작업디렉토리가 /home/reakwon이라는 디렉토리라는 것을 알 수 있습니다.
[reakwon@localhost ~]$ pwd
/home/reakwon
cd (Change Directory)
디렉토리를 이동하는 명령어입니다. 절대 경로나 상대 경로를 주어서 디렉토리를 이동할 수 있습니다.
cd /root = /root 디렉토리로 이동합니다.
cd .. = 현재 디렉토리의 상위 디렉토리로 이동합니다.
cd ~ = 현재 사용자의 홈 디렉토리로 이동합니다.
이런 조합으로 이동이 가능합니다.
ls (List)
디렉토리 안의 파일과 디렉토리를 알 수 있는 명령어입니다. 여러가지 옵션이 존재하는데, 옵션은 여러가지를 혼합해서 사용할 수 있습니다. 옵션을 살펴보도록 하지요.
a : 모든 파일을 보여줍니다. 즉, 숨김파일까지 전부 보여주는 것이죠. 숨김파일이란 파일명 앞에 "."이 달린 파일명이고 ls명령어만으로 보여지지 않는 파일입니다.
l : (소문자 '엘') 파일의 자세한 내용을 보여줍니다.
다음은 제 리눅스 pc의 tmp 디렉토리의 내용의 일부를 보여줍니다.
-rw-r--r--. 1 root root 1148 5월 16 14:33 anaconda.log drwx------. 2 reakwon reakwon 25 5월 16 14:50 firefox_reakwon drwxr-xr-x. 2 root root 18 5월 16 13:51 hsperfdata_root -rw-r--r--. 1 root root 420 5월 16 14:33 ifcfg.log -rwx------. 1 root root 836 5월 16 14:29 ks-script-o30BkB -rw-r--r--. 1 root root 0 5월 16 14:32 packaging.log -rw-r--r--. 1 root root 0 5월 16 14:32 program.log -rw-r--r--. 1 root root 0 5월 16 14:32 sensitive-info.log drwx------. 2 reakwon reakwon 24 5월 16 14:34 ssh-TF6BwHbO3Rdo drwx------. 2 reakwon reakwon 24 5월 16 14:41 ssh-UDg27UAVaZcs drwx------. 2 reakwon reakwon 24 5월 16 23:13 ssh-bWUCqvi956kP drwx------.2reakwonreakwon245월 16 22:38ssh-g8sqmExSvLtD 1. 2. 3. 4. 5. 6. 7.
1. 파일의 종류와 권한: 이 파일이 어떤 파일인지, 소유주, 소유주 그룹, 기타 다른 유저들이 이 파일을 사용할때의 권한을 보여줍니다.
파일의 종류는 다음과 같습니다.
a) - : 일반 파일
b) d : 디렉토리
c) b : block 장치 파일
d) c : character 장치 파일
e) l : 심볼릭 링크 파일
f) p : 명명된 파이프
g) s : 소켓 파일
권한은 다음과 같이 지정됩니다. 각 3개씩 권한이 나누어 지는데요.
rwx(파일 소유자의 권한)r-x(파일 소유 그룹의 권한)r-x(그외 사용자에 대한 권한)
r : read , w : write , x : execute
위의 파일 권한을 해석해본다면 파일 소유자는 읽기,쓰기, 실행 권한을 갖고 있네요. 파일 소유자가 있는 그룹은 이파일을 읽고, 실행만 할 수 있겠네요. 다른 사용자는 읽고, 실행만 할 수 있습니다.
2. 하드링크수를 보여줍니다.
3. 파일의 소유주를 보여줍니다. 디렉토리를 만들거나 파일을 만든 계정입니다. 파일의 소유주는 chown명령어를 이용해 바꿀 수 있습니다.
4. 소유그룹을 보여줍니다. 파일 소유주의 그룹을 나타냅니다.
5. 파일의 크기를 보여줍니다.
6. 최종 수정일, 시간을 보여줍니다.
7 마지막으로 파일명을 보여줍니다.
i : inode번호를 보여줍니다. 디렉토리에는 파일명과 해당 파일의 inode번호가 매핑되어 있는데, ls -l과 같이 파일의 자세한 정보를 볼 수 있는 것은 inode에 파일에 대한 메타데이터가 기록이 되기 때문입니다. 어떤 것들이 있는지는 아래와 같습니다.
파일 모드 : 파일의 형식과 실행 권한
링크 수 : 이 아이노드에 대한 디렉터리 참조 수
소유자 계정 : 파일의 소유자
GID : 이 파일과 관계된 그룹 소유자
파일 크기 : 파일의 바이트
파일 주소 : 주소 정보
마지막 접근(Access) : 마지막으로 파일에 접근한 시각
마지막 수정(Modified) : 마지막으로 파일을 수정한 시각
아이노드 수정(Changed) : 마지막으로 아이노드를 수정한 시각
R : 하위 디렉토리의 내용까지 보여줍니다. 예를 들어 현재 디렉토리가 aaa이며 파일이 bbb,ccc 그리고 그 하위 디렉토리가 ccc이며 ccc의 디렉토리에 eee,fff 파일이 있다면 디렉토리 aaa의 파일을 전부 출력해주며 하위 디렉토리인 ccc의 내용까지 전부 출력해주는 옵션입니다.
십진수와 이진수로 나타냈습니다. 총 32비트, 4바이트라는 것을 알 수 있습니다. 여기서 1바이트가 바로 옥텟이라고 합니다. Oct가 숫자 8을 나타내는 접두사이거든요(온타곤, 옥토퍼스 등). 이것은 우리가 흔히 보는 IPv4의 주소 표기방식입니다.
조금 후에 이야기를 하겠지만 이 IP주소가 C클래스 대역에 속한다면 네트워크 주소는1100 0000. 1010 1000. 0000 0010입니다. 나머지 빨간 부분인 0000 1010은 호스트 주소랍니다. 그래서 192.168.10으로 시작하는 PC는 192.168.10.10과 같은 네트워크에 속하고 있다는 것을 의미합니다.
가장 첫번째 호스트 주소는 네트워크 자체를 지칭하며, 마지막 주소는 브로드캐스트용 주소로 쓰입니다. 예를 들어 위에서 192.168.10.0은 192.168.10의 네트워크를 가리키고, 192.168.10.255가 브로드캐스트용 주소이지요.
우리는 이제 IPv4를 토대로 IP 클래스 대역과 서브넷 마스크에 대해 알아보도록 하겠습니다.
A클래스
A클래스의 첫번째 옥텟의 비트는 0으로 고정됩니다.
0xxx xxxx. xxxx xxxx. xxxx xxxx. xxxx xxxx
그렇기 때문에 표현할 수 있는 범위는 0000 0000.0000 0000.0000 0000.0000 0000~0111 1110.1111 1111.1111 1111.1111 1111입니다.
그래서 0.0.0.0 ~ 127.255.255.255입니다.
이 IP클래스는 대규모 네트워크에 적합합니다.
네트워크 주소는 처음 8비트까지입니다. 나머지 24비트는 호스트 주소를 의미합니다.
B클래스
B클래스는 첫번째 옥텟의 두번째 비트가 고정됩니다. 10으로 고정이 됩니다.
10xx xxxx. xxxx xxxx. xxxx xxxx. xxxx xxxx
그래서 표현할 수 있는 범위는 1000 0000. 0000 0000. 0000 0000. 0000 0000~ 1011 1111. 1111 1111. 1111 1111까지입니다.
그래서 128.0.0.0 ~ 191.255.255.255입니다.
네트워크 주소는 처음 16비트이며 호스트 주소는 나머지 16비트입니다.
C클래스
C클래스는 첫번째 옥텟의 세번째 비트가 110으로 고정됩니다.
110x xxxx. xxxx xxxx. xxxx xxxx. xxxx xxxx
그래서 표현할 수 있는 범위는 1100 0000. 0000 0000. 0000 0000. 0000 0000 ~ 1101 1111. 1111 1111. 1111 1111. 1111 1111까지입니다.
십진수로 표현하면 192.0.0.0 ~ 223.255.255.255 입니다.
네트워크 주소는 처음 24비트이며 나머지 8비트는 호스트 비트입니다.
D클래스
D클래스는 첫번째 옥텟의 네번째 비트가 1110으로 고정됩니다.
1110 xxxx. xxxx xxxx. xxxx xxxx. xxxx xxxx
그래서 표현할 수 있는 범위는 1110 0000. 0000 0000. 0000 0000. 0000 0000 ~ 1110 1111. 1111 1111. 1111 1111. 1111 1111까지입니다.
또한 0.0.0.0, 255.255.255.255처럼 네트워크 시작 주소, 브로드캐스트용주소는 IP주소로 할당되지 않으면 127.0.0.1과 같은 loopback용 주소 또한 사용할 수 없습니다.
서브넷마스크
우리가 C클래스 대역을 사용해서 호스트를 255개를 수용할 수 있는 것도 너무 많이 남을때가 있습니다. 또는 B클래스 대역을 C클래스 대역으로 쓰고 싶을 때가 있습니다. 네트워크 주소를 조금 더 효율적으로 할당하고자 나온 것이 서브넷마스크입니다. 서브넷마스크로 만들어진 네트워크를 서브넷이라고 합니다.
이제 서브넷마스크를 어떻게 계산하며 네트워크 부분과 호스트부분이 어떻게 되는지 살펴봅시다.
예를 가지고 설명을 하는 것이 가장 좋겠네요.
128.255.11.11는 B클래스 주소입니다. 128.255까지가 네트워크 주소이며 나머지 2옥텟이 나머지 호스트 주소입니다.
128.255.11.11을 255.255.255.0이라는 서브넷 마스크를 씌우면 어떻게 될까요? 서브넷 마스크는 비트로 보는 것이 편합니다.
● Telnet : TCP 포트번호 23번을 사용합니다. 원격 터미널을 접속할때 이 포로토콜을 사용합니다.
● SSH (Secure Shell) : 텔넷과 같은 서비스는 보안에 취약합니다. 비밀번호가 암호화되지 않아 그대로 노출이 되기 때문이지요. 이것을 보완한것이 SSH입니다. 포트번호 22번을 사용합니다.
● FTP(File Transfer Protocol) : 파일 전송 프로토콜입니다. 파일을 받거나 올릴때 FTP를 사용하지요. FTP는 파일을 올리거나 내려받을때 신뢰성을 중요시하기 때문에 TCP에서 동작하구요. 2개의 포트를 사용합니다.
TCP 포트 20번은 데이터 전송을 위한 용도, TCP 포트 21번은 제어용으로 사용합니다.
● SMTP (Simple Mail Transfer Protocol) : 메일 전송 프로토콜입니다. TCP 상에서 동작하며 포트는 25번을 사용합니다.
● POP3 (Post Office Protocol Version3) : 메일 수신용 프로토콜입니다. 아웃룩같은 프로그램이 POP3라는 프로토콜을 사용하여 동작합니다. TCP 포트 110번을 사용합니다.
● DNS (Domain Name System) : 도메인명에 대한 호스트 정보를 제공해줍니다. 기본적으로 UDP상에서 동작합니다. 기본적으로 실패하면 다시 한번 요청하면 되며 그렇게 중요한 정보가 아니기 때문이죠. 하지만 신뢰성을 요할 경우에는 TCP상에서도 동작합니다. 데이터의 길이가 길 경우같은 때 TCP 기반으로 동작할 수 있습니다.
UDP, TCP 포트 53번을 사용합니다.
이와 같이 포트번호가 특정 프로토콜이 사용해서 우리가 쓸 수 없는 포트들이 있습니다. 이런 포트들을 well-known port라고 합니다.
프로토콜 헤더 정보를 잘 읽고 분석할 수 있다면 네트워크를 더 잘 이해할 수 있을 겁니다.
네트워크에서 아주 기본적인 지식은 OSI7 계층입니다. OSI7계층은 국제 표준기구인 ISO(International Organization for Standardization)으로부터 탄생합니다.
처음 네트워크 장치는 중구난방이었습니다. 때문에 회사의 장비마다 호환이 되지 않으며 복잡했었죠. 이에따라 네트워크를 7계층으로 분리하면서 표준을 만들게 되었습니다. 그것이 바로 OSI 7 계층입니다.
OSI 7 계층 구조
이렇게 생겨먹었습니다. 딱 7개로 나뉘어져 있지요?
그림을 보면 알겠지만 응용계층에서 내려온 데이터부터 시작해서 계속 헤더가 붙는 것을 알 수 있습니다. 이 헤더에는 그 계층에 대한 정보가 기록되어있지요.
우리가 이메일을 보낸다고 가정할때 처음 응용계층에서 헤더를 붙여 하위 계층으로 넘겨줍니다. 표현계층은 응용계층에서 내려온 헤더와 이메일 데이터를 하나의 데이터로 간주하게 됩니다. 그래서 다시 자신의 헤더를 붙이게 되지요. 이런 과정은 Encapsulation이라고 합니다.
이런식으로 물리계층까지 내려오게 되면 그때부터 0과 1의 이진 비트가 전송되게 되는 것입니다.
받은 수신자는 거꾸로 물리계층부터 시작해 헤더의 정보를 확인하고 떼어냅니다. 그리고 난 후 상위 계층으로 데이터를 전달하는 것이죠. 이렇게 헤더를 떼어내는 과정은 Decapsulation이라고 합니다.
지금부터 응용계층부터 물리계층까지 간략하게 설명하도록 하지요.
물리계층(Physical Layer)
7계층 중 최하위 계층입니다. 주로 전기적, 기계적, 기능적인 특성을 이용해 데이터를 전송하게 됩니다. 데이터는 0과 1의 비트열, 즉 On, Off의 전기적 신호 상태로 이루어져있지요.
이 계층은 단지 데이터를 전달하기만 합니다. 어떤 에러가 있는지 등 그런 기능에는 전혀 관여하지 않습니다.
데이터링크 계층(Data-Link Layer)
물리 계층에서 송수신되는 정보의 오류와 흐름을 관리하여 안전한 정보의 전달을 수행할 수 있도록 도와주는 역할을 합니다.
데이터 링크 계층의 데이터 전송은 Point-To-Point 간 입니다.
안전한 정보의 전달이라는 것은 오류나 재전송하는 기능을 갖고 있다는 이야기죠. 또한 우리가 언젠가 한번 들었을 MAC주소를 갖고 있습니다. 그래서 통신을 할 수 있지요.
이 계층에서 부르는 데이터의 단위는 프레임(Frame)이라고 합니다.
네트워크 계층(Network Layer)
네트워크 계층은 네트워크에서 아주 중요합니다.
중요한 기능 중 한가지는 바로 라우팅이라고 하는데요. 목적지까지 가장 안전하고 빠르게 데이터를 보내는 기능을 말합니다. 따라서 최적의 경로를 설정해야하지요.
이런 라우팅 기능을 맡고 있는 계층이 네트워크 계층입니다.
또한 어느 컴퓨터에게 데이터를 전송할지 주소를 갖고 있어서 통신을 합니다. 우리가 자주 듣는 IP 주소가 바로 네트워크 계층 헤더에 속해있습니다.
네트워크 계층에서 부르는 데이터 단위는 패킷(Packet)이라고 합니다.
전송 계층(Transport Layer)
전송 계층 역시 네트워크에서 중요한 계층입니다. 전송 계층은 양 끝단의 사용자들이 신뢰성있는 데이터를 주고 받게 해주는 역할을 합니다.
송신자와 수신자 간의 신뢰성있고 효율적인 데이터를 전송하기 위하여 오류검출 및 복구, 흐름제어와 중복검사 등을 수행합니다.
중요한 것은 데이터 전송을 위해서 Port 번호가 사용이 됩니다. 대표적인 프로토콜로는 TCP와 UDP가 있습니다. 이 계층에서 사용하는 데이터 단위는 세그먼트(Segment)라고 합니다.
세션 계층(Session Layer)
세션 계층은 응용 프로세스가 통신을 관리하기 위한 방법을 정의합니다.
이 계층은 세션을 만들고 없애는 역할을 하고 있지요.
표현 계층(Presentation Layer)
데이터를 어떻게 표현할 지 정하는 역할을 하는 계층으로 일종의 확장자라고 이야기하면 편하겠네요.
표현 계층은 세가지의 기능을 갖고 있습니다.
1. 송신자에서 온 데이터를 해석하기 위한 응용계층 데이터 부호화, 변화
2. 수신자에서 데이터의 압축을 풀수 있는 방식으로 된 데이터 압축
3. 데이터의 암호화와 복호화
MIME 인코딩이나 암호화 등의 동작이 표현계층에서 이루어집니다. EBCDIC로 인코딩된 파일을 ASCII 로 인코딩된 파일로 바꿔주는 것이 한가지 예이지요.
응용 계층(Application Layer)
사용자와 가장 가까운 계층이 바로 응용 계층입니다. 우리가 사용하는 응용 서비스나 프로세스가 바로 응용계층에서 동작합니다.