원격 데스크톱을 직접 설계해보니 — '직접 연결 먼저, 안 되면 릴레이'의 정체
상대 PC를 보고 조작하는 원격 제어 도구를 설계하며 정리한 연결 전략(WebRTC ICE), 입력 주입, 그리고 보안 우선 설계 이야기입니다.
목차
내 PC에서 다른 PC의 화면을 보고 마우스·키보드로 조작하는 도구 — 구글 크롬 원격 데스크톱 같은 것 — 를 설계했습니다. 아직 코드가 아니라 설계 단계지만, 파고들다 보니 재미있는 사실을 하나 발견했습니다. **"IP가 열려 있으면 직접 붙고, 안 되면 중개 서버로 돌아간다"**는, 제가 막연히 직접 구현해야 한다고 생각했던 그 연결 방식이, 이미 표준으로 존재하더라는 겁니다.
이 글에도 데모 버튼은 없습니다. 원격 제어는 상대 PC에 입력을 실제로 꽂아 넣는 네이티브 프로그램이 있어야 해서(브라우저는 OS에 키 입력을 주입할 수 없습니다), 웹 데모로는 만들 수 없는 종류거든요.
"직접 먼저, 안 되면 릴레이"는 이미 WebRTC다
원격 도구를 만든다고 하면 이런 흐름을 상상하게 됩니다.
- 상대 IP로 바로 TCP 연결을 시도해본다.
- 방화벽·공유기 때문에 안 되면, 중개(릴레이) 서버를 하나 두고 양쪽이 거기로 나가는 연결을 맺어 데이터를 이어준다.
그런데 이걸 손으로 짜려면 NAT 통과, 홀펀칭, 후보 수집, 암호화를 전부 다시 발명해야 합니다. 알고 보니 이 전 과정이 WebRTC의 ICE라는 표준에 그대로 들어 있었습니다. ICE는 연결을 맺을 때 여러 경로 "후보"를 모아 우선순위 순으로 자동 시도합니다.
| 우선순위 | 후보 | 뜻 | 내가 상상했던 것 | | --- | --- | --- | --- | | 1 | host | 같은 LAN의 사설 IP 직접 | 같은 네트워크 직접 | | 2 | srflx (STUN) | 공유기 뒤 공인 IP:포트 직접 | "IP가 열려 있으면 직접" | | 3 | relay (TURN) | 중개 서버 경유, 양쪽이 아웃바운드로 연결 | "안 되면 중개 소켓 통신" |
ICE가 후보 쌍을 전부 시험해서 실제로 뚫리는 가장 좋은 경로를 고릅니다. 그래서 앱은 "직접이 되나 안 되나"를 판단하는 분기 로직을 짤 필요가 없습니다. 직접이 되면 직접, 막히면 자동으로 릴레이로 내려갑니다.
특히 TURN(릴레이)이 "아웃바운드"라는 점이 핵심입니다. 상대 PC가 대칭 NAT나 빡빡한 방화벽 뒤에 있어도, 자기가 릴레이 서버로 나가는 연결만 열면 되니까 인바운드 포트를 열 필요가 없습니다. 상용 원격 도구가 "설치만 하면 어디서든 붙는" 원리가 이겁니다.
대부분은 이미 만들어둔 조각이었다
설계를 하다 보니, 이 프로젝트에 필요한 것의 대부분을 제가 이전 프로젝트들에서 이미 만들어뒀다는 걸 깨달았습니다.
- 연결·시그널링·릴레이: 예전에 만든 화상회의 도구가 바로 이 WebRTC 연결(STUN 직접 / TURN 릴레이)과 방·승인 로직을 갖고 있습니다.
- 화면 캡처·인코딩: 아이패드를 보조 모니터로 쓰는 도구가 화면을 잡아 H264로 인코딩해 보내는 네이티브 호스트를 갖고 있습니다.
그래서 새로 만들어야 하는 건 딱 세 가지로 좁혀졌습니다.
- 입력 주입: 받은 마우스·키보드 이벤트를 상대 PC의 실제 입력으로 꽂기(Windows
SendInput). - 입력 채널: 그 이벤트를 실어 나를 통로. 영상과 같은 WebRTC 연결 안의 DataChannel을 쓰면 소켓을 따로 열 필요가 없습니다. 키 down/up이 뒤바뀌면 안 되니 순서·신뢰성이 보장되는 모드로요.
- 제어 승인 흐름: 이게 다음 이야기입니다.
화면 공유와 원격 제어는 보안 무게가 다르다
화상회의에서 화면을 공유하는 것과, 상대가 내 키보드를 치는 것은 위험의 무게가 완전히 다릅니다. 원격 제어는 네트워크로 받은 데이터가 곧 내 PC의 입력이 되는 도구라, 설계에서 보안을 기능보다 앞에 뒀습니다.
- 매 세션 명시적 승인: 누가 접속을 시도하면 피제어 PC 화면에 "OO가 제어를 요청합니다 [허용/거부]"가 뜨고, 허용을 눌러야만 시작됩니다. 화상회의의 "대기실 승인"을 "제어 허용"으로 확장한 거죠.
- 일회성 페어링: 방 번호를 추측당하지 않게 만료되는 일회용 코드로 연결합니다.
- 무인 접속은 기본 꺼짐: 승인 없이 아무 때나 붙는 상시 접속은, 명시적으로 켠 경우에만.
- 항상 보이게, 언제든 끊게: 제어 중임을 화면에 표시하고, 핫키로 즉시 세션을 끊을 수 있게.
- 통신은 전부 암호화(WebRTC 기본 DTLS-SRTP).
설계에서 얻은 것
가장 큰 수확은 "직접 구현해야 한다고 생각한 것이 이미 표준에 있더라"는 깨달음이었습니다. NAT 통과와 릴레이 폴백을 손으로 짜는 대신 ICE에 맡기면, 코드량도 줄고 보안·안정성도 검증된 표준을 그대로 얻습니다.
그리고 프로젝트를 여러 개 만들다 보니 조각들이 재사용되기 시작했습니다. 연결은 화상회의에서, 캡처는 화면 확장 도구에서 가져오니, 원격 제어라는 큰 주제가 "입력 주입 + 승인 흐름"이라는 손에 잡히는 크기로 줄어들더군요. 다음 단계는 이 설계대로 같은 네트워크에서 화면 보기+제어가 되는 최소 버전을 만들어보는 것입니다.
관련 글
탭 2~3개만 쓰는 나를 위한 저메모리 브라우저 만들기
크롬이 왜 그렇게 무거운지 파고들다 WebView2로 개인용 미니 브라우저를 만들었습니다. 탭 서스펜드, 메모리 모니터, EasyList 광고 차단의 실제 구현 기록입니다.
2026.07.11아이패드를 노트북 보조 모니터로 — 웹으로는 못 만드는 도구를 만든 이야기
Windows 가상 디스플레이 생성, 화면 캡처, H264 하드웨어 인코딩, WebRTC 전송까지 — 브라우저 밖에서만 가능한 화면 확장 도구의 개발 기록입니다.
2026.07.11
데모사내 고객 화상회의 앱을 만들고 정적 사이트에 붙여봤습니다
WebSocket 시그널링 + WebRTC mesh로 동작하는 화상회의 데모를 만들고, 정적으로 배포되는 블로그에 실시간 백엔드를 어떻게 연결했는지 정리했습니다.
2026.07.10