"내 컴퓨터가 인터넷과 주고받는 모든 패킷을 실시간으로 잡아, 어느 나라·어느 회사·어느 앱과 얼마나 통신하는지를 차트와 지도로 보여주는 오픈소스 네트워크 모니터"입니다. 네트워크 카드에서 패킷을 떠서(캡처) 헤더를 분석하고(파싱), 누가 누구와 통신하는지를 사람이 읽을 수 있는 정보(국가·도메인·서비스·프로그램)로 바꿔 한 화면에 펼칩니다.
인터넷에 연결된 컴퓨터는 끊임없이 작은 소포(패킷)를 주고받습니다. 보통은 누가 무엇을 보내는지 전혀 안 보입니다. Sniffnet은 현관(네트워크 카드)을 지나는 모든 소포의 송장을 자동으로 읽어 "이건 구글에서 온 거, 저건 어떤 광고 서버로 나가는 거, 이 트래픽은 크롬이 만든 것"이라고 실시간으로 정리해 줍니다.
Wireshark가 "송장 원문을 전부 보여주는 전문가 도구"라면, Sniffnet은 "송장을 알아서 해석해 보기 좋게 요약해 주는 일반인용 대시보드"입니다. 이 '해석과 요약'을 빠르고 안 멈추게 하려고 Rust와 멀티스레드를 쓴 것이 코드 전체를 관통하는 설계입니다.
네트워크를 들여다보고 싶은 사람은 많지만, Wireshark·tcpdump는 패킷 필드를 날것으로 쏟아내 초보자에겐 진입장벽이 높습니다. Sniffnet은 "강력함을 조금 줄이는 대신 직관성을 크게 올린" 포지션을 잡았습니다. 실시간 트래픽 인텐시티 차트, 국기·도메인·ASN 표시, 앱별 트래픽 같은 "바로 이해되는 정보"를 전면에 내세웁니다.
Electron 기반 모니터링 도구들이 무겁다는 불만이 쌓인 가운데, Sniffnet은 Rust + iced로 진짜 네이티브 GUI를 만들었습니다. GPU(wgpu) 렌더링에 메모리 사용이 적고, 단일 실행파일로 배포됩니다. "Rust로 실사용 데스크탑 GUI 앱을 어떻게 만드는가"의 살아 있는 레퍼런스라는 점이 개발자 커뮤니티의 관심을 끕니다.
NLnet·IPinfo 등의 후원을 받고, MaxMind의 GeoLite2 데이터를 제공받아, 24개 언어 번역(한국어 포함)에, v1.4~1.5에서 BPF 필터·webhook 알림·앱별 트래픽·커스텀 IP 블랙리스트를 잇따라 추가하는 등 활발히 발전 중입니다. 단순 토이 프로젝트가 아니라 실제로 쓰이고 관리되는 도구라는 신뢰가 트렌딩을 뒷받침합니다.
| Sniffnet | Wireshark | tcpdump | glasswire(상용) | |
|---|---|---|---|---|
| 대상 사용자 | 일반+개발자 | 전문가 | 전문가/CLI | 일반 |
| 가격/라이선스 | 무료·MIT/Apache | 무료·GPL | 무료·BSD | 유료 |
| GUI | 네이티브(iced) | 네이티브(Qt) | 없음(터미널) | 네이티브 |
| 기반 기술 | Rust | C/C++ | C | 비공개 |
| 지리위치/ASN | 내장(MMDB) | 플러그인 | 없음 | 일부 |
| 앱별 트래픽 | 지원(v1.5) | 제한적 | 없음 | 지원 |
| 심층 패킷 분석 | 요약 중심 | 매우 강력 | 강력 | 약함 |
Sniffnet의 강점은 트래픽을 보기 좋게 요약·시각화하는 것입니다. 반대로 패킷 본문(payload)을 바이트 단위로 파헤치거나, 특정 프로토콜의 모든 필드를 디코딩하는 정밀 분석은 Wireshark의 영역입니다. "무슨 일이 일어나는지 한눈에 파악"이 목적이면 Sniffnet, "이 패킷이 정확히 왜 깨졌는지 포렌식"이 목적이면 Wireshark를 써야 합니다. 둘은 경쟁이라기보다 역할이 다릅니다.
패킷 캡처는 OS의 깊은 권한이 필요합니다. Linux는 libpcap + cap_net_raw 권한 설정, Windows는 Npcap 별도 설치가 필요하고, macOS는 libpcap이 내장돼 있습니다. 이 전제가 안 맞으면 어댑터 목록이 비거나 캡처가 시작되지 않습니다 — Sniffnet 자체 버그가 아니라 환경 설정 문제인 경우가 대부분입니다.
Sniffnet은 크게 캡처·파싱 엔진(백엔드 스레드) / iced GUI(화면) / 조회·데이터 계층(MMDB·rDNS·서비스 DB) 3층으로 나뉩니다. "무거운 일은 별도 스레드에서, 화면은 절대 멈추지 않게"가 일관된 원칙입니다.
| 분류 | 기술 | 역할 |
|---|---|---|
| 패킷 캡처 | pcap 2.4 (libpcap/Npcap 바인딩) | 네트워크 카드에서 raw 패킷 읽기. BPF 필터·promisc·snaplen 제어 |
| 패킷 파싱 | etherparse 0.20 | link→network→transport 헤더를 계층별로 분석(LaxPacketHeaders) |
| 서비스 DB | phf 0.13 + build.rs 코드젠 | 12,097개 포트·프로토콜→서비스명 매핑을 컴파일타임 완전해시맵으로 |
| IP 연산 | ipnet, prefix-trie | CIDR 계산, IP 블랙리스트를 트라이로 빠르게 매칭 |
| 역방향 DNS | dns-lookup 3.0 | IP→도메인명 조회(rDNS) |
| 앱별 트래픽 | listeners 0.6 + picon 0.1 | 로컬 포트→프로세스(PID) 매핑 + 앱 아이콘 추출 |
| 분류 | 기술 | 역할 |
|---|---|---|
| GUI 프레임워크 | iced 0.14 (tokio·svg·advanced·lazy·image) | Elm 아키텍처 기반 Rust GUI. wgpu 렌더러 + tiny-skia CPU 폴백 |
| 비동기 런타임 | tokio 1.52 | iced와 통합. freeze(일시정지) 신호는 broadcast 채널로 |
| 차트 | plotters 0.3 + plotters-iced2 0.14 | 실시간 트래픽 차트·도넛 차트·미리보기 차트 |
| 곡선 보간 | splines 5.0 | 차트 라인을 부드럽게 |
| 파일 다이얼로그 | rfd 0.17 | PCAP import/export·설정 파일 선택 네이티브 창 |
| 알림 사운드 | rodio 0.22 (mp3) | 임계치·즐겨찾기·블랙리스트 알림음 |
| 분류 | 기술 | 역할 |
|---|---|---|
| 지리위치/ASN | maxminddb 0.28 + 번들 GeoLite2 MMDB | IP→국가·ASN 매핑(Country 9.5MB·ASN 11.7MB DB 동봉) |
| 설정 영속화 | confy 2.0 + serde/toml | 설정·즐겨찾기를 TOML로 저장/복원 |
| 네트워크 요청 | reqwest 0.13 (json) | 업데이트 체크 + webhook 원격 알림 |
| CLI | clap 4.6 (derive) | 명령행 인자 파싱 |
| 빌드/품질 | phf_codegen, rustrict, winres / rstest, serial_test | 서비스 맵 생성·욕설 검열·Windows 아이콘 / 테스트 |
| 릴리스 프로필 | lto=true, opt-level=3, codegen-units=1, strip=true | 최적화·용량 축소된 단일 바이너리 |
우체국에 비유하면 — 분류 작업장(캡처·파싱 엔진)은 들어오는 소포를 쉴 새 없이 뜯어 송장을 읽는 곳이고, 안내 데스크(iced GUI)는 손님에게 결과를 보기 좋게 보여주는 창구이며, 주소록·전화번호부(MMDB·rDNS·서비스 DB)는 "이 번호가 어느 회사·어느 나라인지"를 찾아주는 참고 자료입니다. 작업장이 아무리 바빠도 안내 데스크는 막히지 않아야 — 그래서 둘을 다른 스레드로 떼어 놨습니다.
핵심은 "캡처·파싱·조회는 전부 별도 OS 스레드, GUI는 채널로 받기만 한다"입니다. 패킷이 폭주해도 화면이 멈추지 않는 비결이 이 분리입니다.
iced GUI의 뼈대입니다. Sniffer 구조체가 Model(상태 전체: 설정·트래픽 정보·차트·현재 페이지·모달 등), Message enum이 일어날 수 있는 모든 사건(70여 개 변형), update(&mut self, Message) -> Task<Message>가 상태를 바꾸는 유일한 지점(거대한 match), view() -> Element<Message>가 상태를 화면으로 그리는 순수 함수입니다. 파일 다이얼로그·타이머·채널 구독 같은 부수효과는 전부 Task로만 일으킵니다.
무거운 작업을 종류별로 다른 스레드에 맡기고, 스레드끼리는 채널(큐)로만 대화합니다. 백엔드→GUI는 async-channel(다대다), 스레드 간 패킷 전달은 std::sync::mpsc의 sync_channel(버퍼 10,000), 일시정지 신호는 tokio::broadcast로 모든 스레드에 동시에 뿌립니다. 공유 메모리에 락을 거는 대신 메시지를 주고받는 메시지 패싱 방식이라 데이터 경쟁이 줄어듭니다.
mpsc=다중 생산자·단일 소비자, async-channel의 MPMC=다대다. sync_channel(10000)은 버퍼가 차면 보내는 쪽이 잠깐 멈춰(backpressure) 메모리 폭주를 막습니다. broadcast는 한 메시지를 구독자 전원에게 복사해 보냅니다(일시정지 같은 신호용).백엔드 스레드가 만든 메시지 스트림(rx)을 Task::run(rx, |backend_msg| → Message)로 감싸면, iced 이벤트 루프가 그걸 자기 Message로 받아 update에 흘려보냅니다. "GUI 바깥 세계(캡처 스레드)와 GUI 안 세계(Elm 루프)를 잇는 다리"가 바로 이 한 줄입니다.
build.rs가 빌드 시점에 services.txt(12,097줄, 포트·프로토콜→서비스명)를 phf_codegen으로 완전해시맵 소스코드로 변환해 바이너리에 굳혀 넣습니다. 런타임에는 파일을 읽거나 해시 충돌을 걱정할 필요 없이 O(1)로 "이 포트는 무슨 서비스인가"를 즉시 찾습니다.
build.rs는 Rust의 "빌드 전 실행되는 스크립트"로, 여기서 데이터를 미리 코드로 생성(코드젠)해 두면 런타임 비용이 사라집니다. 큰 정적 데이터를 빠르게 조회할 때 쓰는 정석 패턴입니다.캡처 종류(라이브 / 저장파일 동반 라이브 / PCAP 오프라인 / 에러)를 CaptureContext enum 하나로 추상화해, 어댑터·필터·일시정지 처리를 일관되게 다룹니다. 또 캡처를 새로 시작할 때마다 cap_id 카운터를 올려, 이전 캡처의 늦게 도착한 메시지를 무시합니다(어댑터를 바꿨는데 옛 데이터가 섞이는 버그 방지).
networking/(엔진)과 gui/(화면)가 깔끔히 분리돼 있다는 게 핵심입니다. 엔진은 iced를 몰라도 되고, GUI는 pcap을 직접 만지지 않습니다. 둘은 gui/types/message.rs의 Message와 채널로만 만납니다. "화면을 다시 디자인해도 엔진은 그대로, 캡처 방식을 바꿔도 화면 코드는 안 건드린다"가 이 구조가 말하는 의도입니다. 처음 읽을 땐 main.rs → gui/sniffer.rs의 start() → networking/parse_packets.rs 순서가 가장 빠릅니다.
iced의 Elm 아키텍처를 "토이"가 아닌 실제 제품 규모(70개 Message·다중 페이지·커스텀 테마)로 보여주는 드문 레퍼런스입니다. update의 거대한 match를 어떻게 관리하는지, Subscription::batch로 키보드·마우스·타이머·윈도우 이벤트를 어떻게 묶는지, Task로 부수효과를 어떻게 격리하는지 실전으로 익힐 수 있습니다.
"네트워크 카드에서 패킷을 떠서 → 계층별로 헤더를 벗겨 → 의미를 부여하는" 전 과정을 코드로 봅니다. capture_context.rs의 pcap 설정(promisc·snaplen·buffer·timeout·BPF), manage_packets.rs의 link→network→transport 순차 파싱이 핵심 읽을거리입니다. 네트워크 프로그래밍 입문에 더없이 좋은 실제 예제입니다.
"무거운 일은 스레드로 떼고 채널로 결과만 받는다"는 동시성 설계의 정석을 보여줍니다. std::thread spawn, sync_channel의 backpressure, async-channel, tokio::broadcast를 용도에 맞게 골라 쓰는 판단을 배울 수 있습니다. GUI가 절대 블로킹되지 않게 하는 실무 패턴의 교본입니다.
12k짜리 서비스 테이블을 빌드 시점에 코드로 굳히는 기법은, 데이터를 자주 조회하는 어떤 앱에도 응용됩니다. "런타임에 할 일을 빌드 타임으로 옮긴다"는 Rust다운 최적화 사고를 익힐 수 있습니다.
build.rs + phf_codegen으로 컴파일타임 맵으로 만들어 조회해 보세요.maxminddb로 GeoLite2 DB에서 IP의 국가·ASN을 뽑아내는 실전 코드를 볼 수 있습니다. rDNS(도메인명)·서비스명·국기와 합쳐, 무미건조한 IP 숫자를 "구글(미국, AS15169)"처럼 의미 있는 정보로 바꾸는 데이터 보강(enrichment) 패턴입니다.
24개 언어를 모듈로 나눠 관리하는 i18n 구조(translations_*.rs + Language enum), location!() 매크로 기반 에러 로깅, 듀얼 라이선스, 릴리스 최적화 프로필(lto·strip), rstest·serial_test 기반 테스트까지 — "오픈소스를 실제로 굴리는 법"을 통째로 배울 수 있습니다.
| 항목 | 내용 |
|---|---|
| 지원 OS | Windows · macOS · Linux (모두 데스크탑) |
| 캡처 라이브러리(필수) | Linux: libpcap · Windows: Npcap 설치 · macOS: libpcap 내장 |
| Linux 권한 | 비루트 캡처 시 sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/sniffnet |
| Linux 추가 의존 | DEB: libasound2, libpcap0.8, libfontconfig1 · RPM: alsa-lib, libpcap, fontconfig |
| 설치(사용자) | GitHub Releases 바이너리(Windows MSI · macOS DMG · Linux AppImage/DEB/RPM) 또는 cargo install sniffnet |
| GPU | 기본 wgpu(GPU) 렌더링. 문제 시 ICED_BACKEND=tiny-skia로 CPU 소프트웨어 렌더 폴백 |
| 빌드(개발자) | Rust 최신 toolchain(edition 2024) + 위 캡처 라이브러리 → cargo build --release |
대개 권한 또는 라이브러리 문제입니다. Windows는 Npcap이 설치됐는지, Linux는 setcap을 줬거나 sudo로 실행했는지, macOS는 권한 프롬프트를 허용했는지 확인하세요. any 인터페이스(Linux SLL)로 모든 어댑터를 한 번에 보는 옵션도 있습니다.
본인 OS에 맞게 설치(필요 시 Npcap/권한 설정)한 뒤 어댑터를 골라 캡처를 켜고, 1분간 어느 국가·어느 서비스와 통신하는지 관찰해 보세요. 브라우저에서 사이트 하나를 열 때 도넛 차트와 호스트 목록이 어떻게 변하는지 보면 "패킷→시각화"의 흐름이 체감됩니다.
레포를 클론해 cargo build --release로 빌드하고 실행해 보세요. 처음 컴파일은 시간이 꽤 걸립니다. build.rs가 services.txt를 PHF 맵으로 코드젠하는 단계가 빌드 로그에 어떻게 나타나는지 확인합니다.
앱의 필터 입력에 tcp port 443, udp, host 8.8.8.8 같은 BPF 표현식을 넣어 결과가 어떻게 좁혀지는지 실험해 보세요. 그런 다음 capture_context.rs에서 이 필터가 pcap에 어떻게 적용되는지 코드를 따라가 봅니다.
gui/types/message.rs에 Message 변형을 하나 추가하고, sniffer.rs의 update match에 처리 분기를, view에 그걸 트리거하는 버튼을 더해 보세요(예: "트래픽 통계 콘솔 출력"). Elm 아키텍처의 update/view 왕복을 손에 익히는 최소 실습입니다.
Sniffnet의 골격 — pcap 캡처 스레드 + etherparse 파싱 + 채널로 GUI에 전달 + iced로 실시간 차트 — 을 본떠, "초당 패킷 수와 상위 목적지 IP를 막대로 보여주는" 최소 GUI를 만들어 보세요. 캡처 스레드와 GUI를 채널로 잇고, GUI가 절대 멈추지 않게 설계하는 것까지가 목표입니다.
| 주차 | 주제 | 할 일 |
|---|---|---|
| 1주차 | iced / Elm 아키텍처 | iced 공식 예제(counter·todos)로 Model·Message·update·view·subscription 개념 익히기. 과제 1·2 수행 |
| 2주차 | 네트워크 기초 + pcap | OSI 계층(link/network/transport)·TCP/UDP/ICMP·BPF 이해. pcap+etherparse로 패킷 덤프 CLI 작성. 과제 3 |
| 3주차 | Rust 동시성 | 스레드·mpsc/async-channel·broadcast·backpressure 정리. "캡처 스레드 → 채널 → 집계" 파이프라인 직접 구현 |
| 4주차 | 데이터 보강 + 종합 | maxminddb로 IP→국가/ASN, rDNS, build.rs+PHF 정적 맵 실습. 과제 4·5로 미니 스니퍼 완성 |
| 용어 | 뜻 |
|---|---|
| 패킷(Packet) | 인터넷에서 오가는 데이터의 한 조각. 헤더(송장)와 본문으로 구성 |
| 패킷 캡처/스니핑 | 네트워크 카드를 지나는 패킷을 가로채 복사하는 일. 관리자 권한 필요 |
| libpcap / Npcap | 패킷 캡처용 OS 라이브러리. Linux/macOS=libpcap, Windows=Npcap |
| BPF | Berkeley Packet Filter. tcp port 443 같은 커널 레벨 패킷 필터 표현식 |
| OSI 계층 | link(이더넷/MAC) → network(IP/ARP) → transport(TCP/UDP/ICMP). Sniffnet은 이 순서로 헤더를 벗김 |
| etherparse | Rust 패킷 헤더 파싱 라이브러리. 계층별로 헤더를 안전하게 해석 |
| iced | Elm 아키텍처 기반 Rust GUI 프레임워크. wgpu(GPU) 렌더, tiny-skia(CPU) 폴백 |
| Elm 아키텍처(MVU) | Model·View·Update로 데이터를 한 방향으로 흐르게 하는 GUI 설계 |
| 채널(mpsc/MPMC) | 스레드 간 데이터를 안전히 전달하는 큐. backpressure로 폭주 방지 |
| Task::run | 외부 스레드의 메시지 스트림을 iced 이벤트 루프에 주입하는 다리 |
| MMDB / GeoLite2 | IP→국가·도시·ASN을 매핑하는 MaxMind 바이너리 DB 포맷 |
| ASN | Autonomous System Number. IP 블록을 운영하는 사업자 번호(예: AS15169=Google) |
| rDNS(역방향 DNS) | IP→도메인명 조회. 무미건조한 IP에 의미를 붙임 |
| PHF(완전해시) | 충돌이 0인 해시. build.rs 코드젠으로 큰 정적 테이블을 빌드타임에 굳힘 |
| promiscuous / snaplen | 자기 것 아닌 패킷도 받기 / 패킷당 캡처할 바이트 수 |
| bogon | 공인 라우팅에 나오면 안 되는 IP(사설·예약 대역) |
| wgpu / tiny-skia | GPU 렌더러 / CPU 소프트웨어 폴백 렌더러 |
| PCAP 파일 | 캡처한 패킷을 저장하는 표준 포맷. Sniffnet은 import(분석)·export(저장) 모두 지원 |
| 링크 | 설명 |
|---|---|
| GitHub 저장소 | 소스 전체. src/networking/parse_packets.rs·src/gui/sniffer.rs가 핵심 읽을거리 |
| 공식 웹사이트 | 스크린샷·설치 안내·기능 소개 |
| TrendShift 페이지 | 트렌딩 추이 |
| iced 공식 사이트 | Elm 아키텍처 기반 Rust GUI 문서·예제 |
| etherparse 문서 | 패킷 헤더 파싱 API |
| rust-pcap | libpcap/Npcap Rust 바인딩 |
| Npcap | Windows 패킷 캡처 라이브러리(Windows 사용자 필수) |
| MaxMind GeoLite2 | IP 지리위치·ASN 무료 DB |