목차
i - 들어가며
IRC(Internet Relay Chat)에 대한 RFC 문서를 읽고 상용 클라이언트를 사용하는 irc 서버를 구현하고 있다. 그런데 실제 클라이언트와 메시지를 주고 받아보니까 얘네들 뭔가 이상하다… 이거 RFC 문서대로 구현한거 맞아? 실제 서버랑 클라이언트가 어떻게 대화하는지 직접 눈으로 봐야겠어..!!!
1 - IRC 프로토콜 메시지를 확인하기 위한 환경 설정
먼저 프로토콜 메시지를 확인하기 위한 환경을 설정해야한다. Docker를 이용한 Ubuntu 안에서 inspircd(상용 irc 서버)와 irssi(상용 irc 클라이언트)를 이용할 것이다. mac os에서 해도 되지만 관리자 권한이 필요하기도 하고 brew로 irssi를 설치하면 apt 보다 상대적으로 오래 걸린다. 그리고 프로토콜 메시지를 확인하는 방법 중 하나로 tcpflow 라는 프로그램을 이용할 것이다.
1.1 - Docker로 Ubuntu container 만들고 실행하기
아래 링크에서 Docker Desktop을 다운 받고 설치한다.
Docker Desktop의 Home에서 Categories 안의 Ubuntu를 찾아 Run 버튼을 누른다.
Ubuntu 항목에서 Run이나 Run another 버튼을 누르자
그러면 Ubuntu container가 바로 실행이 되는데 Launch terminal 버튼을 누르거나 적혀있는 커맨드를 입력하면 terminal에서 Ubuntu container에 접속할 수 있다.
편리하게 Ubuntu container에 terminal에서 접속할 수 있는 버튼과 커맨드
Lanch terminal 버튼을 누르면 아래와 같이 접속할 수 있다.
터미널에서 docker ps -a 를 쳐서 나오는 container ID를 이용해 docker exec -it container_id /bin/bash를 입력해도 터미널에서 Ubuntu container에 접속할 수 있다.
Docker Desktop을 이용해서 Ubuntu container를 실행하는 방법을 설명했지만 terminal 환경에서 image를 가져오고 container를 가져오고 싶다면 아래 링크를 참고하자.
1.2 - inspircd, irssi, tcpflow 설치하기
Ubuntu에서 apt install로 inspircd, irssi와 tcpflow를 설치할 수 있다. 만약 설치가 되지 않는다면 apt update를 해주자.
이렇게 설치가 안 된다면… apt update를 하고 다시 시도해보자!
apt update를 해주고 apt install inspircd, apt install irssi, apt install tcpflow 명령어를 입력하면 각각 설치가 가능하다.
2 - 주고받는 IRC 프로토콜 메시지 확인하기
2.1 - inspircd 서버 구동
inspircd를 정상적으로 설치했다면 inspircd를 터미널에 입력하면 inspircd 서버를 구동할 수 있다.
어.. 근데 루트계정이라 30초 기다리래요…
root 계정이라면 보안을 이유로 경고 차원에서 30초 정도 기다렸다가 서버를 실행할 것이다. 만약 기다리지 않고 그냥 바로 서버를 구동하고 싶다면 --runasroot 옵션을 넣으면 즉시 서버를 구동할 수 있다. 지금은 인터넷으로 irc 서버를 돌릴게 아니고 확인용으로 loopback에서 다른 클라이언트 프로세스와 통신하는 것이 목적이므로 --runasroot 옵션을 넣고 실행하자.
--runasroot 옵션을 넣고 바로 실행했지만 PID-file이 없어서 inpircd 서버 구동에 실패함…
만약, PID-file이 없어서 구동에 실패한다면 /var/run/ 폴더에 inspircd/ 폴더가 없어서 그럴 수 있으므로 /var/run/inspircd/ 폴더를 만들고 다시 실행하면 해결할 수 있다.
이제 서버가 실행중이라는 메세지가 출력됐지만 바로 터미널로 돌아와서 당황…
inspircd가 이제 구동하고 있다는 메시지가 출력되고나서 바로 터미널 입력창으로 나와 당황스러울 것이다. inspircd는 기본적으로 fork를 통해 다른 프로세스를 만들어 background에서 서버를 실행시키고 원래 프로세스는 종료시켜서 터미널 입력창으로 나오게 한다.
서버를 background로 실행시키고 싶지 않다면 --nofork 옵션을 주어서 foreground인 원래 터미널에서 실행시킬 수 있다. 프로토콜 메시지를 확인하기 위해 돌아가는 서버 내에서 메시지를 확인해야하는 경우도 있으니 --nofork 옵션을 넣어주자.
결과적으로 아래 명령어와 옵션을 넣어서 실행하자!
inspircd --runasroot --nofork
기존 터미널에서 inspircd 서버가 잘 구동하는 것을 확인할 수 있다.
2.2 - irssi 클라이언트로 서버에 접속하기
irssi 클라이언트는 아래 명령어와 옵션을 입력하면 위에서 구동한 서버에 바로 접속할 수 있다.
irssi -c 127.0.0.1 -p 6667 -n nickname
-c 옵션은 뒤에 서버의 IP주소나 도메인을 입력하는 부분이다. loopback에 있는 서버에 접근하므로 127.0.0.1을 넣어주자.
-p 옵션은 irc 서버의 포트번호를 입력하는 부분이다. irc 서버는 일반적으로 6667번을 사용하고 TLS/SSL을 이용해서 암호화된 통신을 하는 경우 6697번을 사용한다.(by. rfc7194) inspircd 서버는 구동하면 기본으로 6667번 포트에서 listening을 하므로 6667을 입력해주자.
-n 옵션은 서버에서 사용할 닉네임을 뒤에 써주는 부분이다. 원하는 닉네임을 적어서 입력하자.
위 커맨드를 입력하고 서버에 성공적으로 접속한 것을 확인할 수 있다.
2.3 - 메시지 내용 확인하기
irc 메시지를 확인하는 방법은 여러가지가 있겠지만 이 글에서는 tcpflow를 사용해서 확인하거나 inspircd의 debug 옵션을 사용해서 확인하는 2가지 방법에 대해서 알아보자.
2.3.1 - tcpflow로 메시지 확인하기
tcpflow -i lo port 6667 -c
을 터미널에 입력해서 실행하면 6667 포트에 지나가는 각 패킷의 데이터를 확인할 수 있다.
-i 옵션은 인터페이스를 지정하는 부분이다. IP주소, 도메인 등의 인터페이스를 입력해서 지정할 수 있다. lo을 입력하면 loopback 으로 지정된다. (-i: network interface on which to listen)
port 뒤에 포트번호를 지정해서 특정 포트를 지나가는 패킷을 확인할 수 있다. irc 서버가 사용하는 6667 포트번호 입력하자.
-c 옵션은 추적한 데이터들을 파일에 write하지 않고 stdout으로 출력하도록 하는 옵션이다. (-c: console print only (don't create files))
loopback의 6667번 포트에서 listening 하면서 지나가는 패킷의 데이터를 출력하는 tcpflow
출력되는 메시지의 구조는 아래와 같다.
{Sender}-{Receiver}: {Message}
위 메시지를 예로 들면 127.0.0.1의 58398번 포트에 있는 클라이언트 프로세스에서 127.0.0.1의 6667번 포트에 있는 서버 프로세스로 PING irc.local 라는 메시지 데이터를 전송한 것을 확인할 수 있다.
2.3.2 - inspircd의 debug 옵션으로 메시지 확인하기
위에서 inspircd를 실행할 때 --nofork 옵션과 --runasroot 옵션을 넣어서 서버를 실행시켰다. inspircd에 --debug 옵션을 추가로 넣어서 실행시키면 inspircd 서버로 들어오는 패킷 메시지를 확인할 수 있다.
debug옵션을 추가해서 실행하면 inspircd 서버에서 일어나는 일들을 볼 수 있다.
클라이언트와 메시지를 주고 받을 때 출력되는 내용. 클라이언트가 PING을 보내서 서버가 PONG 으로 응답했다.
debug 옵션을 넣은 inspircd 서버에서 클라이언트와 주고받는 프로토콜의 메시지 구조는 아래와 같다.
{Timestamp} {USERINPUT of USEROUTPUT}: C[{UUID}] {I or O} {Message}
위 메시지를 예로 들어보자. Timestamp에 적힌 시각에 user로 output을 보낸다. 그 유저는 서버에서 811AAAAAA라는 UUID로 인식한다. O :irc.local PONG irc.local :irc.local 부분은 서버에서 유저로 보내는 아웃풋인데 그 메시지는 :irc.local PONG irc.local :irc.local 이다.
o - 마무리
클라이언트가 실제 서버와 어떻게 프로토콜 메시지를 주고 받는지 확인하는 방법을 알 수 있었다. 클라이언트가 특정 프로토콜 메시지를 보내면 서버가 어떻게 응답해야하는지 참고가 가능하다. irc 서버만 구현하는 상황에서 RFC 문서에 적힌대로 상용 클라이언트에게 프로토콜 메시지를 보냈는데 아무런 반응을 하지 않는 당황스러운 상황을 극복하는데 도움이 될 것이다.
(주의!! 이 문단을 읽을 때 비유가 더러울 수 있음.) 누군가는 주고받는 프로토콜 메시지를 보고 구현하는 것은 코딱지를 보고 콧구멍을 구현하는 것과 비슷해서 좋지 않은 방법이라고 말하곤 한다.(비유가 더러워서 죄송…) 하지만 콧구멍 설계도에서 적힌대로 코딱지를 만들어서 던졌는데 상대방이 아무런 반응을 하지 않는다면 아주 섭섭한 상황이 된다. 상대방이 어떤 방식으로 코딱지를 던져서 보낼 때 반응을 하는지 참고를 한다면 나도 똑같이 상대방의 반응을 얻어낼 수 있다. 그게 좋든 싫든 커뮤니케이션이 되는 것이다.
오래된 irc protocol 특성상 실제로 구현된 클라이언트와 서버가 RFC 문서를 정확하게 따르지 않고 자기들만의 프로토콜 규칙으로 조금씩 수정해서 소통한다고 한다. 그러면 이에 맞춰 실제로 통신하는 프로토콜 메시지를 눈으로 확인하고 구현하는 것도 문제를 효율적으로 해결하는 방법 중 하나라고 생각한다.
※ 참고자료
Default Port for Internet Relay Chat (IRC) via TLS/SSL(RFC - 7194): https://datatracker.ietf.org/doc/html/rfc7194
man tcpflow: https://linux.die.net/man/1/tcpflow
tcpflow console 예제: https://ko.linux-console.net/?p=189#gsc.tab=0