항해 최종프로젝트에서 오디오/비디오 채팅 기능이 있었고
이 때 일부 네트워크 간 P2P 연결이 안되는 문제를 해결하기 위해 TURN
서버를 구현했었다.
급하게 구현하느라 기록하지 못했던 점, 복습차원 겸,
계속 켜둬서 AWS 과금이 계속 발생했기에 평생무료인 Oracle Cloud Instance
로 서버를 교체하는 겸사겸사 뒤늦게 구현 과정을 기록해보았다.
안그래도 돈 없는데 잊고 있다가 결제 기록을 보고 마음이 아팠다..
TURN 서버가 왜 필요할까?
const peerConnection = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302',
},
],
});
구글에서 제공하는 무료 STUN
서버를 사용하면 된다.
하지만 개발하며 직접 테스트할 때 다음과 같은 환경에서 문제가 있었다.
모바일 LTE환경에서 접속한 사용자와 랜선 사용자 간 P2P 연결이 되지 않았다. 마찬가지로 모바일 와이파이를 사용해도 그랬다.
특정 카페 와이파이로 접속했을 때 다른 사용자와 P2P 연결이 되지 않았다.
위와 같은 문제를 겪어보며 왜 그런지 조사하였고 이유를 알게되었다.
NAT
을 통과하는 클라이언트 장치의 공인 IP주소와 포트를 확인하고 이를 활용해 클라이언트 간 연결을 수행할 수 있게 도와준다.
(1) 어떤 종단이 NAT/Firewall 뒤에 있는지를 판단하게 해준다.
(2) 어떤 종단에 대한 Public IP Address를 결정하고 NAT/FIrewall의 유형에 대해서 알려준다.
인터넷 같은 공용 네트워크에서 사설 네트워크를 사용하기 위한 기술입니다.
NAT은 사설 네트워크에서 사용되는 IP 주소 범위인 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16과 같은 사설 IP 주소를 인터넷에서 사용되는 공인 IP 주소로 변환하고 이 변환된 주소를 사용해 인터넷에 연결합니다.
그리고 이 NAT
은 다양한 변환 방식이 존재한다.
Full Cone NAT
클라이언트와 한번 매핑하면 다른 곳에서도 해당 포트로 접속이 가능하다.
Restricted NAT
외부에서 내부로 들어오는 트래픽에 대해서만 포트 매핑을 생성하고 유지한다.
Symmetric NAT
같은 내부 IP와 포트에서 나가는 패킷이 항상 같은 공인 IP주소와 포트로 매핑되지 않고 랜덤하게 매핑된다.
NAT
의 변환 방식이 다양하고 동적이여서 통신에 사용되는 public ip
가 늘 같지 않다는 것이다.
따라서 앞서 공인 IP와 포트를 확인해 연결시킨다는 역할을 하는 STUN
서버만으로는 연결이 확실하지 않을 수 있다는 결론을 얻었다.
더 찾아보니 기업 네트워크, 공용 와이파이 등에서 Symmetric NAT
을 주로 사용하고
모바일 네트워크 같은 경우도 동적으로 할당되는 공인 IP주소를 활용한다고 한다.
공인 IP주소가 동적으로 바뀌는 경우 STUN
서버에 제대로 전달되지 않거나 이전에 수집한 NAT정보와 매칭되지 않게되어 잘못된 정보를 전달할 수 있다고 하며
그 외에도 네트워크의 방화벽
문제로 STUN
서버를 통해 연결할 수 없는 상황이 있다고 한다.
클라이언트 서버 간 데이터 전송을 중계한다.
STUN
은 NAT
뒤의 공인 IP주소를 알아내 클라이언트끼리 이를 활용해 연결할 수 있도록 중계하는 역할을 했다면
TURN
은 데이터를 중계해준다.
따라서 공인IP가 변경되어도 중계서버인 TURN
을 통해 데이터를 주고받을 수 있게 해준다.
TURN
서버는 하나의 공인 주소를 가지고 있고 이 주소를 통해 클라이언트들이 직접적으로 미디어를 릴레이하기 때문에 네트워크와 컴퓨팅 자원이 소모된다.
Twillo
등의 서비스를 찾아보았지만 결국 14일 무료 체험판 뿐이였고 무료버전은 없었다.
아무래도 직접적인 미디어 트래픽이 발생하기 때문에 공짜가 있을리 없지 않나 싶긴 했다.
따라서 coturn
이라는 오픈소스로 iaas 인프라에 직접 구축하기로 했다.
여기를 참고하여 구현했다.
다행히 예전에 구현했던 서버에 history
가 남아있어 이를 기반으로 다시 구현했다.
ubuntu 20.04
기준이다.
Coturn 설치
sudo apt-get update
sudo apt-get install coturn
인스턴스 종료해도 자동 재시작 설정
sudo vi /etc/default/coturn
TURNSERVER_ENABLED=1
Coturn 시작
sudo systemctl start coturn
sudo service coturn status
Coturn 설정하기
원본을 복제해두자
sudo mv /etc/turnserver.conf /etc/turnserver.conf.original
설정 추가하기
sudo vi /etc/turnserver.conf
realm=[realm]
server-name=[서버이름]
listening-ip=0.0.0.0
external-ip=[public-ip]
listening-port=3478
min-port=49152
max-port=65535
# Use fingerprint in TURN messages
fingerprint
# log the file
log-file=/var/log/turnserver.log
#enable verbose logging
verbose
# Specify the user for the turn authentication
user=[이름]:[비번]
# Enable long term credential mechanism
It-cred-mech
사용되는 포트 수신 규칙을 잘 설정하자.(생략)
재시작
sudo service coturn restart
연결 테스트
여기서 테스트해보면 된다.
Done
이 뜨면 relay가 되는 것이다
TLS 적용하기
Let's Encrypt
서비스를 통해 매우 쉽게 도메인과 함께 보안 인증서를 적용할 수 있다.
Let's Encrypt
설치
sudo apt-get install certbot
등록된 도메인(A 레코드로 public ip가 매핑되어있는)을 등록해 인증서를 발급한다.
sudo certbot certonly --standalone --preferred-challenges http -d [your-domain]
인증서를 적용해준다.
turn server를 실행하는 coturn
이 인증서를 읽을 수 있게 하기 위함이다.
sudo mkdir /etc/letsencrypt/renewal-hooks/deploy
sudo vi /etc/letsencrypt/renewal-hooks/deploy/coturn
#!/bin/bash -e
for certfile in fullchain.pem privkey.pem ; do
cp -L /etc/letsencrypt/live/<turn.example.com>/"${certfile}" /etc/turnserver/"${certfile}".new
chown turnserver:turnserver /etc/turnserver/"${certfile}".new
mv /etc/turnserver/"${certfile}".new /etc/turnserver/"${certfile}"
done
systemctl kill -sUSR2 coturn.service
turnsever.conf
에 다음 설정을 추가해준다.
# for TLS (secure)
tls-listening-port=5349
재시작!
sudo service coturn restart
마찬가지로 테스트 해보고 연결이 되면 성공이다!
log 설정하기
위의 turnserver.conf
파일에 로깅 설정을 했지만 사실 해당 로그파일은 없다.
로그파일을 만들어주고 coturn
이 이를 로깅할 수 있게 해주고 로그가 많이 찍히기 때문에 logrotate
를 설정해 관리하도록 해주었다.
sudo vi /etc/logrotate.d/coturn # logrotate
/var/log/turnserver/*.log
{
rotate 7
daily
missingok
notifempty
compress
postrotate
/bin/systemctl kill -s HUP coturn.service
endscript
}
turnserver
가 var/log/turnserver
에 로그를 찍을 수 있도록 해준다.
sudo mkdir -p /var/log/turnserver
sudo chown turnserver:turnserver /var/log/turnserver
coturn
을 재시작해준다.
sudo systemctl daemon-reload
sudo systemctl restart coturn
서비스하면서 발생한 로그들이다. 열어보면 어마어마하게 많이 찍힌다.
근데 솔직히 의미를 알기 쉽지는 않았다..
const peerConnection = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302',
},
{
urls: "turn:yourdomain",
username: "username",
credential: "password",
},
],
});
적용하는 것은 매우 간단하다.
peer connection
시 TURN
서버도 지정해주기만 하면 된다.
RTCPeerConnection
객체가 iceServers
배열에 등록된 서버들 중 STUN
서버를 통해 먼저 연결을 시도하고 P2P연결이 성공할 수 없는 경우에 TURN
서버를 사용한다.