Going Connect 에 라즈베리파이 검색을 얹다 — mDNS 를 안 하는 장비 찾기
지난 글에서 만든 Going Connect 는 mDNS 로 Going 제품을 찾는 트레이 앱이었습니다. 그런데 사무실 현실은 이렇습니다.
“저 파이는 Going 제품도 아니고 그냥 개발용으로 구운 건데, IP 가 뭐였지?”
ZPi 개발기, 아두이노 업로더 서버, 방금 테스트하려고 구운 Zero 2 — 이런 순정 라즈베리파이는 mDNS TXT 를 광고하지 않습니다. avahi 가 호스트네임 정도는 흘리지만 vendor=Going 같은 우리 표준을 따를 리 없죠. 그래서 기존 Going Connect 의 mDNS 필터에는 아예 안 잡혔습니다.
이번엔 그 빈틈을 메웠습니다. “광고 안 하는 장비를 이쪽에서 능동적으로 찾아내는” 액티브 스캔을 같은 앱에 얹었습니다.
문제의 핵심 — IP 로는 구분이 안 된다
파이 여러 대를 깔면 항상 같은 벽에 부딪힙니다.
- DHCP 라 재부팅하면 IP 가 바뀐다 (
.236이던 게.249가 됨) 192.168.0.x를 봐도 그게 Zero 2 인지 Pi 4 인지 알 수 없다nmap을 돌려도 “포트 22 열림” 까지만 알려주지, “이건 창고에 둔 옛날 Pi3” 는 안 알려준다
결국 사람이 외우거나 라벨지를 붙여야 했습니다. 그래서 목표를 이렇게 잡았습니다.
한 번 별명을 달아두면, 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.dll 의 GetIpNetTable 을 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"
}
}
이 한 줄 결정 덕분에:
- IP 가 바뀌어도 MAC 이 같으면 같은 장비 → 별명이 그대로 따라온다
- 스캔에 안 나와도 목록에서 지우지 않고 회색(오프라인)으로 남긴다 → “지금 꺼져 있는 창고의 Pi3” 도 목록에 보인다
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 주소 |
Product | SSH 로 읽은 모델명 |
Label | 저장소의 별명 |
기존 mDNS 검색과 Find Me 는 한 줄도 건드리지 않았습니다. 두 검색 서비스가 각자 자기 장비 목록을 소유하고 같은 화면에 밀어넣는 병렬 구조라, Going 제품과 순정 파이가 한 창에 공존합니다.
정리
mDNS 는 “장비가 협조할 때” 는 우아하지만, 협조하지 않는 순정 장비에는 무력합니다. 그럴 땐 핑으로 ARP 를 깨우고 → MAC 으로 정체를 좁히고 → SSH 로 확정 하는 능동적 방식이 답이었습니다.
그리고 이 모든 스캔 기술보다 실제로 문제를 푼 건 “MAC 을 키로 쓴다” 는 한 줄의 설계 결정이었습니다. IP 는 변하지만 MAC 은 안 변하니까요. 이제 파이에 한 번 별명을 달아두면, 재부팅으로 IP 가 바뀌든 잠깐 꺼져 있든, 이름으로 찾을 수 있습니다.
댓글
로그인 없이 이름만 적고 남기실 수 있어요. 남긴 댓글은 바로 게시됩니다.
댓글을 불러오는 중…