Going Connect 에 라즈베리파이 검색을 얹다 — mDNS 를 안 하는 장비 찾기

지난 글에서 만든 Going Connect 는 mDNS 로 Going 제품을 찾는 트레이 앱이었습니다. 그런데 사무실 현실은 이렇습니다.

“저 파이는 Going 제품도 아니고 그냥 개발용으로 구운 건데, IP 가 뭐였지?”

ZPi 개발기, 아두이노 업로더 서버, 방금 테스트하려고 구운 Zero 2 — 이런 순정 라즈베리파이는 mDNS TXT 를 광고하지 않습니다. avahi 가 호스트네임 정도는 흘리지만 vendor=Going 같은 우리 표준을 따를 리 없죠. 그래서 기존 Going Connect 의 mDNS 필터에는 아예 안 잡혔습니다.

이번엔 그 빈틈을 메웠습니다. “광고 안 하는 장비를 이쪽에서 능동적으로 찾아내는” 액티브 스캔을 같은 앱에 얹었습니다.

문제의 핵심 — IP 로는 구분이 안 된다

파이 여러 대를 깔면 항상 같은 벽에 부딪힙니다.

결국 사람이 외우거나 라벨지를 붙여야 했습니다. 그래서 목표를 이렇게 잡았습니다.

한 번 별명을 달아두면, IP 가 바뀌어도 이름으로 찾는다.

스캔 파이프라인

mDNS 는 “장비가 말해주는” 방식이라 편했지만, 이번엔 반대로 이쪽에서 네트워크를 훑어야 합니다. 6단계 파이프라인을 짰습니다.

서브넷(/24) 결정            ← 게이트웨이 있는 실제 NIC 만 (WSL/Hyper-V 가상 스위치 제외)

병렬 핑 스윕 (254개)        ← 응답이 목적이 아니라 ARP 테이블을 채우는 게 목적

ARP 테이블에서 MAC 필터     ← 라즈베리파이 재단 OUI 만 골라냄

포트 프로브 (22/80/5000…)   ← 어떤 서비스가 떠 있는지 라벨링

호스트명 역조회 (DNS)

SSH 상세 조회 (처음 1회)    ← 정확한 모델명 · OS

MAC 으로 “파이인지” 부터 가려낸다

네트워크에 뜬 수십 개 장비 중 라즈베리파이만 골라내는 열쇠는 MAC 주소 앞 3바이트(OUI) 입니다. 라즈베리파이 재단은 몇 개의 OUI 블록을 쓰는데, 이걸로 1차 필터를 겁니다.

private static readonly string[] PiOuis =
    { "B8:27:EB", "DC:A6:32", "E4:5F:01", "D8:3A:DD", "28:CD:C1", "2C:CF:67" };

핑 스윕은 사실 응답을 보려는 게 아닙니다. 각 IP 에 핑을 한 번씩 던지면 OS 가 그 IP↔MAC 매핑을 ARP 캐시에 넣어주는데, 그 캐시를 읽는 게 진짜 목적입니다. Windows 에서는 iphlpapi.dllGetIpNetTable 을 P/Invoke 로 직접 읽고, 실패하면 arp -a 파싱으로 폴백합니다.

SSH 로 “정확한 모델명” 을 캐낸다

OUI 는 “라즈베리파이다” 까지만 알려줍니다. Zero 2 인지 Pi 4 인지까지는 알 수 없죠. 여기서 한 걸음 더 나갑니다 — 22 포트가 열려 있으면 등록된 계정으로 SSH 접속해서 딱 세 줄을 읽어옵니다.

cat /proc/device-tree/model     → "Raspberry Pi Zero 2 W Rev 1.0"
hostname                          → "raspberrypi"
. /etc/os-release; echo $PRETTY_NAME → "Debian GNU/Linux 13 (trixie)"

이건 처음 발견했을 때 딱 한 번만 합니다. 모델명은 안 바뀌니 MAC 기준으로 캐시해두고, 이후 스캔에서는 SSH 를 다시 건드리지 않습니다. 계정 비밀번호는 평문으로 두면 안 되니 DPAPI(ProtectedData, CurrentUser 스코프)로 암호화해서 저장합니다.

진짜 핵심 — MAC 을 키로 쓴 기억

이 기능의 심장은 화려한 스캔이 아니라 저장소 설계입니다. %AppData%\GoingConnect\pi-devices.json 에 장비를 저장할 때, 키를 IP 가 아니라 MAC 으로 잡았습니다.

{
  "D8:3A:DD:XX:XX:XX": {
    "nickname": "테스트 Zero2 (제어보드용)",
    "lastIp": "192.168.0.x",
    "model": "Raspberry Pi Zero 2 W Rev 1.0",
    "os": "Debian GNU/Linux 13 (trixie)",
    "hostname": "raspberrypi"
  }
}

이 한 줄 결정 덕분에:

mDNS 쪽은 90초 미응답이면 목록에서 지우는데, 파이 쪽은 정반대로 오프라인이어도 유지 합니다. 우리가 알고 싶은 건 “지금 살아있는 것” 이 아니라 “내가 관리하는 그 파이가 지금 어디 있나(혹은 꺼져 있나)” 이기 때문입니다.

만들면서 만난 함정 — ARP 는 가끔 늦는다

첫 실행에서 파이 두 대 중 한 대(Pi 4)가 안 잡혔습니다. 원인은 254개를 동시에 핑하면 개별 ARP 등록이 간헐적으로 누락 되는 것. 핑 응답은 왔는데 그 순간 ARP 테이블을 읽으면 아직 안 들어가 있는 경우가 생깁니다.

해결은 단순했습니다. 이미 아는 장비의 마지막 IP 는 스윕에서 한 번 더, 넉넉한 타임아웃으로 핑 하도록 했습니다. 어차피 우리가 제일 확실하게 찾고 싶은 건 “전에 봤던 그 파이” 니까요.

// 알려진 장비 IP 를 한 번 더 (응답 확실화)
foreach (var ipStr in knownIps)
    if (IPAddress.TryParse(ipStr, out var ip))
        tasks.Add(PingOnce(ip));

수정 후 재검증하니 Zero 2 와 Pi 4 가 모두 잡혔고, 별명을 단 장비는 재시작 후에도 이름이 유지됐으며, 존재하지 않는 가짜 장비는 오프라인으로 회색 표시되며 목록에 남았습니다.

기존 앱에 얹는 방식 — 병렬, 비침습

새 프로그램을 따로 만들지 않고 Going Connect 에 얹은 이유는 구조가 이미 맞았기 때문입니다. Device 모델에 Brand 그룹핑과 Label(사용자 라벨)이 이미 있었고, 화면은 Label 을 우선 표시하도록 되어 있었습니다. 그래서 파이를 이렇게만 매핑하면 UI 는 거의 손대지 않고 새 그룹이 생겼습니다.

Device 필드파이 매핑
Brand"Raspberry Pi" (새 그룹으로 자동 분류)
Serial (식별 키)MAC 주소
ProductSSH 로 읽은 모델명
Label저장소의 별명

기존 mDNS 검색과 Find Me 는 한 줄도 건드리지 않았습니다. 두 검색 서비스가 각자 자기 장비 목록을 소유하고 같은 화면에 밀어넣는 병렬 구조라, Going 제품과 순정 파이가 한 창에 공존합니다.

정리

mDNS 는 “장비가 협조할 때” 는 우아하지만, 협조하지 않는 순정 장비에는 무력합니다. 그럴 땐 핑으로 ARP 를 깨우고 → MAC 으로 정체를 좁히고 → SSH 로 확정 하는 능동적 방식이 답이었습니다.

그리고 이 모든 스캔 기술보다 실제로 문제를 푼 건 “MAC 을 키로 쓴다” 는 한 줄의 설계 결정이었습니다. IP 는 변하지만 MAC 은 안 변하니까요. 이제 파이에 한 번 별명을 달아두면, 재부팅으로 IP 가 바뀌든 잠깐 꺼져 있든, 이름으로 찾을 수 있습니다.

댓글

로그인 없이 이름만 적고 남기실 수 있어요. 남긴 댓글은 바로 게시됩니다.

댓글을 불러오는 중…