구현 해설 · 소스 검토 기준 2026-11-11

룰을 공유하고,
패킷의 판정을 추적하다.

SnortDAG는 Snort 2/3 룰의 지원 가능한 부분을 C++20 자료구조로 변환하고, Vectorscan 패턴 검색과 공유 접두사 DAG로 평가하는 CLI 엔진입니다. ncurses 디버거와 실제 DPDK 드라이버에 연결한 IPS 실행 경로도 구현되어 있습니다.

1. 컴파일과 평가의 두 단계

컴파일: 원문에서 재사용 가능한 DB까지

.rules → parser → typed IR → pattern pool + prefix DAG → Vectorscan compile → DB

src/parser.cpp는 헤더와 옵션을 타입으로 구분하고 파일·행·원문을 보존합니다. Snort 버전은 명시적으로 선택하며, 지원하지 않는 옵션은 오류로 중단합니다. src/database.cpp는 여러 파일을 모아 중복 gid:sid를 검사하고 DAG와 검색 엔진을 생성합니다.

DB에는 원문 묶음, 결정적인 패턴 테이블, Vectorscan 직렬화 데이터와 엔진 식별 정보가 담깁니다. 로딩 시 SHA256·원문/패턴 일치·엔진 정보를 검증합니다. SHA256은 변조 주체를 인증하는 서명이 아니므로 DB는 신뢰하는 빌드 산출물로 취급합니다.

평가: 검색 이벤트에서 최종 룰 판정까지

payload / Ethernet frame → Input → buffer별 검색 → header·option 평가 → match / no_match / unavailable

src/packet.cpp는 단일 Ethernet II IPv4/IPv6 TCP/UDP 프레임을 해석합니다. src/matcher.cpp는 헤더와 버퍼별 검색 이벤트를 결합하고 룰의 전체 경로가 성공했을 때만 최종 매치를 만듭니다. raw payload를 사용할 때는 프로토콜·주소·포트 등의 사실을 CLI로 공급해야 합니다.

패턴이 발견됐다는 사실만으로 룰이 매치되지는 않습니다. 옵션의 순서, 버퍼, 검색 구간, 부정 조건을 모두 만족해야 합니다. HTTP 버퍼나 세션 정보가 없으면 추측하지 않고 unavailable을 반환합니다.

2. 패턴 공유와 실행 경로 공유는 다르다

src/patterns.cpp는 표현식과 플래그가 같은 패턴을 같은 PatternId로 묶습니다. src/dag.cpp는 그보다 엄격하게 부모 노드, Snort 버전, 헤더와 옵션의 의미가 같은 접두사만 공유합니다. 따라서 문자열이 같아도 다른 선행 조건에서 실행된다면 별도 노드가 됩니다.

rule A: header → content "GET" → content "/admin"
rule B: header → content "GET" → content "/login"

공유 경로: ROOT → header → "GET" ┬→ "/admin" → A
                                  └→ "/login" → B

각 노드는 하나의 선행 노드를 가집니다. 이 제약은 상대 검색 커서의 출처를 하나로 유지합니다. DAG 검증은 순환, 연결의 상호 일치, 도달 가능성, 룰별 경로와 종료 지점을 확인합니다. RuleId(gid:sid), NodeId, PatternId는 서로 다른 식별자입니다.

커서와 정규식의 의미 보존

content는 겹치는 매치와 가능한 여러 종료 커서를 유지합니다. offset/depth는 절대 검색 구간, distance/within은 이전 양성 content 뒤의 상대 검색 구간을 정합니다. 부정 content는 커서를 전진시키지 않습니다. 첫 번째 검색 결과만 선택하면 놓칠 수 있는 후속 경로도 평가합니다.

terminal pcre/regex는 Vectorscan과 PCRE2 양쪽에서 컴파일 가능한 공통 부분만 허용합니다. Vectorscan 이벤트는 후보이며, PCRE2가 전체 버퍼에서 실제 범위를 독립적으로 검증합니다. 상대 정규식이나 정규식 뒤의 실행 옵션은 거부합니다.

3. 관측과 디버깅

src/main.cpp는 validate, compile, scan, graph, stats, benchmark, debug 등의 CLI를 제공합니다. JSON/DOT 그래프로 컴파일 구조를 확인하고 trace로 커서·실패 이유·룰 판정을 추적합니다. src/debug.cpp와 src/tui.cpp의 TUI는 같은 평가기가 기록한 이벤트를 재생하며 노드·SID·옵션 종류 중단점을 지원합니다. 실행 중인 엔진을 정지시키는 디버거는 아닙니다.

build/snortdag compile examples/shared.rules --output build/rules.db
build/snortdag scan build/rules.db --payload examples/request.bin --protocol tcp --trace build/trace.json
build/snortdag graph build/rules.db --format json --output build/dag.json
build/snortdag debug build/rules.db --payload examples/request.bin --protocol tcp

4. 실제 드라이버에 연결한 IPS

rte_eth_rx_burst → nic_dpdk_receive → nic_view → decodeEthernet → evaluate → nic_decide ├─ NIC_DROP → release → rte_pktmbuf_free └─ NIC_PASS → transmit → rte_eth_tx_burst

cmake/IPS.cmake는 형제 저장소 undine의 실제 C 정책·DPDK 어댑터를 링크합니다. src/ips/main.cpp가 EAL·포트·RX 루프를 관리하고 src/ips/engine.cpp가 빌린 프레임을 검사한 뒤 정책 함수에 최종 결정을 전달합니다. IPS의 운영 정책으로 지원되는 alert 룰의 최종 매치를 DROP 처리합니다.

결과처리탐지 집계
DROP_MATCH하나 이상의 최종 룰 매치 → DROP포함
PASS최종 매치와 평가 불가가 모두 없음 → 전달제외
DROP_UNAVAILABLE매치는 없고 평가 불가 룰이 있음 → DROP제외
DROP_ERROR / DROP_TX_ERROR디코딩·엔진 오류 / 송신 거부제외

세션·검사기·변수 해석이 필요한 DB는 기본적으로 포트 시작 전에 거부합니다. --allow-context-missing을 사용하면 누락 컨텍스트가 있는 패킷은 DROP_UNAVAILABLE이 될 수 있습니다. 예외 시에는 Pending RAII guard가 미처리 패킷을 DROP하고, 드라이버 ID의 세대 번호가 오래된 참조를 구분합니다.

5. 확인된 결과와 남아 있는 범위

아래 수치는 저장소의 2026-11-08 검증 기록입니다. 이번 문서 배포에서 IPS 트래픽이나 전체 C++ 테스트를 다시 실행한 결과는 아닙니다.

기존 검증기록된 결과
Release / ASan·UBSan CTest각각 41/41 통과
실제 DPDK PCAP PMD 및 격리 veth입력 8개 → 정상 전달 4, 룰 매치 DROP 3, 오류 DROP 1
원문 적용 룰SID 10006, 11367, 11414의 3개 룰
독립 관측출력 PCAP·수신 소켓으로 전달 여부와 바이트 일치 확인

지원 범위는 alert, 제한된 헤더, content와 검색 구간, 일부 버퍼 지정, terminal regex, dsize, 명시적 flow 사실입니다. byte_test·byte_jump·byte_extract·isdataat·flowbits 등은 지원하지 않습니다. HTTP/file 검사기, TCP 재조립, 세션 추적, VLAN·fragment·IPv6 확장 헤더, 자체 X520 PMD는 미구현입니다. 실제 Snort 동등성, 물리 선로 성능, 원본 트래픽 탐지율은 입증하지 않았습니다.

nginx는 이 해설과 저장된 보고서를 정적으로 제공합니다. 이 페이지의 배포가 IPS 프로세스나 실시간 패킷 차단을 시작하는 것은 아닙니다. IPS 결과 화면은 저장된 실행의 스냅샷입니다.