Netsh Portproxy

임베디드 엔지니어쟁이들의 업에서 빼놓을 수 없는 건 실제 타겟 보드에서의 디버깅입니다. 호스트 환경에서 아무리 열심히 개발하고 테스트하더라도, 소프트웨어가 실제 하드웨어에서 제대로 동작하는지는 결국 타겟 보드에서 직접 확인할 수밖에 없습니다. 그 보드가 특정한 물리적 환경에 묶여 있을 때 - 제 경우에는 차량 환경이죠 - 일이 고달퍼지기 시작합니다. 차고나 주차장에 있는 차량에 설치되어 있는 보드를 그 환경에서 사용해야 한다면요. 차량 옆에 쪼그려 앉아서 보드를 디버깅하고, 코드를 수정하고, 다시 배포하고, 또 테스트하는 일은 보기보다 고됩니다. 여기서 게으름을 조금 피워보죠. 여분의 Windows laptop을 차량에 두고, 그 Laptop을 통해 제 Desk에서 보드에 원격으로 접근해 보기로 했습니다.

제 Windows 노트북은 두 네트워크 Comnay VPN Wifi(172.160.a.b)와 Board Ethernet(192.168.x.y)에 동시에 연결되어 있었습니다.

1. SSH Jump Host 사용

쉽게 접근해봅시다. Windows laptop에 SSH로 접속한 다음, 그 shell에서 다시 타겟 보드로 SSH하면 됩니다.

# My current host
ssh windows_account@172.160.a.b

# Windows machine
ssh board_account@192.168.x.y

물론 매번 이렇게 두 번씩 접속할 필요는 없습니다. ProxyJump를 이용하면 Windows laptop을 jump host로 사용할 수 있습니다. 클라이언트의 ~/.ssh/config에 다음과 같이 설정합니다.

Host windows-jump
    HostName 172.16.a.b
    User windows_account

Host target-board
    HostName 192.168.x.y
    User board_account
    ProxyJump windows-jump

이제 다음 명령만 실행하면 한 번에 보드에 다다를 수 있습니다.

ssh target-board

2. SSH Tunnel로 특정 Port 노출

저 혼자만 이 환경을 사용한다면 SSH Jump Host만으로도 충분합니다. 문제는 다른 엔지니어들도 같은 보드를 사용해야 할 때였습니다. ProxyJump를 사용하려면 결국 각각의 사용자가 중간 Windows laptop에 인증을 거쳐 로그인 해야 합니다. Windows laptop이 공용이면 상관없지만 제 경우에는 제 업무용 Windows laptop을 임시로 차량에 가져다 논 거라서 옆 자리 동료가 보드를 살펴보고 싶다고 하니까 꽤나 난감해졌습니다. 저는 자리에서 원격으로 보고 동료는 직접 가라고 할 수는 없잖아요. 그리고 이미 Windows laptop에 물리적으로 연결된 보드의 Ethernet라인을 뻬고 다시 붙이고 다시 되돌려놓고. 그래서 제 Windows laptop 계정을 공유하지 않고 단순히 Windows laptop을 중간 매개체로만 이용하는 방식을 적용하기 시작했습니다.

Windows에는 이미 OpenSSH client가 있었기 때문에 가장 먼저 SSH port forwarding을 이용해 보았습니다. Windows laptop에서 타겟 보드로 SSH session 하나를 열면서 local port forwarding을 설정합니다.

ssh -N `
    -L 172.16.a.b:2222:127.0.0.1:22 `
    root@192.168.x.y

-N은 remote shell을 실행하지 않고 SSH connection을 forwarding 용도로만 사용하겠다는 의미입니다. -L 172.16.a.b:2222:127.0.0.1:22은 Windows 노트북의 172.16.a.b:2222에서 connection을 받은 뒤, 이미 만들어진 SSH connection을 통해 remote 측의 127.0.0.1:22로 전달합니다. 이때 remote SSH server가 192.168.x.y의 타겟 보드이므로 결과적으로 구조는 다음과 같습니다.

Other Engineer
      |
      | ssh root@172.16.x.y -p 2222
      v
Windows laptop
172.16.x.y:2222
      |
      | SSH tunnel
      v
Target board
192.168.x.y:22

이제 사용자는 Windows 계정을 알 필요가 없습니다. 그냥 아래 명령어를 통해 한 번에 보드에 접근 가능합니다. SSH authentication도 Windows 계정이 아니라 최종적으로 타겟 보드의 sshd를 상대로 이루어집니다.

ssh board_account@172.16.a.b -p 2222

3. netsh interface portproxy로 Persistent Port Forwarding 구성

이 방법으로 첫 번째 문제는 해결됐습니다. 하지만 사용하다 보니 또 다른 불편함이 생깁니다. forwarding이 존재하려면 SSH session이 항상 살아 있어야 합니다. Laptop이 재부팅되거나 SSH process가 종료되면 tunnel도 사라집니다. 물론 Task Scheduler나 script 등을 이용해서 자동으로 session을 다시 만들 수도 있습니다. SSH session 자체를 background process나 scheduled task을 lifecycle형태로 관리하는 것도 가능합니다. 하지만 이건 배보다 배꼽이 커지고 있는 느낌이랄까요.

방법은 조금 더 복잡해지지만 그림은 조금 더 쉽게 그려봅시다. 별도의 SSH session을 계속 띄워 두는 대신 Windows 자체에 172.16.a.b:2222 -> 192.168.x.y:22라는 forwarding rule을 영구적인 network configuration으로 설정할 수 없을까요?

netsh interface portproxy는 Windows가 특정 IP와 TCP port에서 connection을 받은 뒤, 이를 다른 IP와 TCP port로 전달하도록 설정할 수 있는 기능입니다.

제가 원하는 구성은 다음과 같았습니다.

172.16.10.22:2222
       |
       | TCP proxy
       v
192.168.0.10:22

관리자 권한의 PowerShell이나 Command Prompt에서 다음과 같이 설정합니다.

netsh interface portproxy add v4tov4 `
    listenaddress=172.16.10.22 `
    listenport=2222 `
    connectaddress=192.168.0.10 `
    connectport=22

설정된 rule은 다음 명령으로 확인할 수 있습니다.

netsh interface portproxy show all
...
Listen on ipv4:             Connect to ipv4:
Address         Port        Address         Port
--------------- ----------  --------------- ----------
172.16.10.22    2222        192.168.0.10    22

사용자는 역시 다음 명령어를 통해 한 번에 보드에 다다를 수 있습니다. SSH Tunnel과 똑같이 SSH authentication Windows 계정이 아니라 최종적으로 타겟 보드의 sshd를 상대로 이루어집니다.

ssh root@172.16.10.22 -p 2222

Firewall

조금 더 살을 붙여보겠습니다. 제대로 위 기능을 이용하기 위해서는 Firewall설정을 해야 합니다.

먼저 Windows Firewall에서 다른 machine이 TCP 2222에 접근할 수 있어야 합니다.

New-NetFirewallRule `
    -DisplayName "Board SSH Port Proxy" `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 2222 `
    -Action Allow

실제 Company 환경이라면 모든 source에서 접근할 수 있도록 열기보다 source subnet을 제한하는 것이 더 적절할 수 있습니다.

New-NetFirewallRule `
    -DisplayName "Board SSH Port Proxy" `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 2222 `
    -RemoteAddress 172.16.0.0/16 `
    -Action Allow

처럼 범위를 제한할 수 있습니다.

Windows에서 Target까지의 Connectivity

또 하나 당연하지만 중요한 조건은 Windows 노트북 자체가 타겟에 접근할 수 있어야 한다는 것입니다.

Windows laptop
172.16.10.22
      |
      | board-facing Ethernet
      v
192.168.0.10:22

먼저 Windows에서 아래 명령어가 성공하는지 확인합니다.

Test-NetConnection 192.168.0.10 -Port 22

실패한다면 portproxy를 디버깅하기 전에 Ethernet interface와 routing부터 확인해야 합니다. 아래 명령어를 통해 Windows가 192.168.0.10으로 가는 traffic을 어느 interface로 보내는지 확인할 수 있습니다.

route print

Listen 상태 확인

설정 후 실제로 해당 port가 열려 있는지도 확인할 수 있습니다.

Get-NetTCPConnection -LocalPort 2222

or

netstat -ano | findstr :2222

4. netsh portproxy

netsh portproxy에 대해서 조금 더 Deep dive를 해보겠습니다. Linux에서 흔히 사용하는 DNAT를 아시는 분이라면 DNAT과의 비교가 직관적으로 와 닿을 것 같습니다.

먼저 portproxy기능은 이름 그대로 이 기능은 일반적인 NAT forwarding과는 다릅니다. NAT rule이 아니라 특정 주소와 TCP port에서 listen하고 incoming TCP connection을 다른 endpoint로 forwarding하는 proxy rule로만 동작합니다.

이 방식의 장점은 Windows를 두 subnet 사이의 router로 만들 필요가 없다는 점입니다. Target board 역시 Company network인 172.16.x.x에 대한 route를 알 필요가 없습니다. Windows가 중간 TCP endpoint가 되어 target과 직접 통신하기 때문입니다. (반면 DNAT처럼 packet을 forwarding하는 방식에서는 target의 return route가 고려해야 합니다.)

이 방식의 가장 큰 단점은 TCP만 지원한다는 점입니다.그래서 SSH, HTTP, HTTPS 같은 TCP service에는 잘 맞지만 UDP나 broadcast/multicast traffic까지 전달하려면 다른 방법이 필요합니다.

Rule 관리

설정 확인:

netsh interface portproxy show all

Rule 삭제:

netsh interface portproxy delete v4tov4 `
    listenaddress=172.16.10.22 `
    listenport=2222

Destination을 변경하고 싶다면 기존 rule을 수정하거나 다시 등록할 수도 있습니다.


5. Windows에서 가능한 다른 방법들

Windows에서 위 목적을 실현하는 방식이 netsh portproxy만 있는 것은 아닙니다. 예를 들어 WinNAT을 이용해 Windows를 실제 NAT gateway처럼 구성할 수도 있고, Internet Connection Sharing(ICS)이나 RRAS 같은 기능을 사용할 수도 있습니다. 별도의 TCP relay application을 설치해서 forwarding하는 방법도 있습니다.

다만 제 경우에는 SSH port 하나만 단순하게 노출하면 됐기 때문에 이런 방식들은 다소 과했습니다.

netsh portproxy는 별도의 application이나 session을 계속 유지할 필요 없이 Windows 기본 기능만으로 특정 TCP port 하나를 forwarding할 수 있었고, 제 목적에는 이게 가장 단순했습니다.