Linux 패킷 비행 기록 장치(Packet Flight Recorder)를 만들며 즐거웠던 경험
요약
skbx는 Rust와 CO-RE eBPF를 사용하여 Linux 커널 내 패킷의 이동 경로를 추적하는 패킷 비행 기록 장치입니다. 단순한 패킷 캡처를 넘어 커널 함수 처리, XDP 전환, 드롭 이유 등 패킷의 전체 여정을 JSONL 형태로 기록하고 재생할 수 있습니다.
핵심 포인트
- Rust와 CO-RE eBPF 기반의 Linux 패킷 경로 추적 도구
- 커널 내 패킷 클론, 복사, XDP 전환 및 드롭 이유 관찰 가능
- 캡처된 데이터를 JSONL 스트림으로 저장하여 사후 재생 및 검사 지원
- tcpdump와 달리 패킷이 커널 내부에서 이동하는 경로 분석에 특화
어떤 프로젝트들은 로드맵과 함께 시작됩니다. skbx는 제가 Linux 네트워킹이라는 토끼굴(rabbit hole)에 빠져 예상했던 것보다 훨씬 더 즐거워하면서 시작되었습니다.
질문은 간단했습니다:
이 패킷은 실제로 어디로 갔는가?
하지만 답은 간단하지 않았습니다.
패킷은 네트워크 네임스페이스(network namespaces), 라우팅 결정(routing decisions), Netfilter, 트래픽 제어(traffic control), XDP 프로그램, 터널(tunnels), 클론(clones), 복사(copies), 그리고 드롭 경로(drop paths)를 통과할 수 있습니다. 한 인터페이스에서의 패킷 캡처는 완전히 정확할 수 있지만, 그 여정의 단 한 부분만을 보여줄 수도 있습니다.
저는 그 여정을 더 많이 보고 싶었습니다. 그러고 나서 제가 본 것을 저장하고, 나중에 루트(root) 권한 없이 다시 재생(replay)하며, 캡처 자체에서 관찰 데이터가 유실되었는지 알고 싶었습니다.
그것이 skbx가 되었습니다.
요약 버전
skbx는 Rust와 CO-RE eBPF로 구축된 Linux 패킷 경로 비행 기록 장치(packet-path flight recorder)입니다.
라이브 캡처 중에 커널 네트워킹 함수를 관찰하고 추가 전용(append-only) JSONL 증거 스트림을 작성합니다. 그 후, 해당 스트림은 다른 eBPF 프로그램을 로드하지 않고도 재생 및 검사가 가능합니다.
라이브 트래픽(live traffic)
↓
CO-RE eBPF 관찰(observations)
...
다음 사항들을 관찰할 수 있습니다:
sk_buff를 처리하는 커널 함수;- 패킷 클론(clones), 복사(copies), 쓰기 시 복사(copy-on-write), 그리고 XDP-to-SKB 전환;
- TC 및 XDP 프로그램 진입 및 종료;
- 터널(tunnels) 및 내부 패킷 튜플(inner packet tuples);
- 커널이 보고하는 드롭(drop) 이유;
- 선택된 BPF 헬퍼(helper) 및 맵(map) 활동;
- 캡처 유실, 디코딩 실패 및 출력 실패.
이것은 tcpdump나 Wireshark를 대체하는 도구가 아닙니다. 해당 도구들은 캡처 지점에서의 패킷 바이트와 프로토콜에 대한 질문을 다룰 때 매우 훌륭합니다. skbx는 로컬 Linux 커널을 통과하는 경로에 대한 질문을 위한 것입니다.
또한 이 경로를 통해 패킷을 추적하는 첫 번째 도구도 아닙니다. 이 프로젝트는 광범위한 eBPF 패킷 트레이싱(packet tracing)이 얼마나 유용한지를 보여준 pwru에서 명시적으로 영감을 받았습니다.
제가 특히 탐구하고 싶었던 부분은 라이브 터미널의 스크롤이 멈춘 뒤의 증거들이었습니다.
왜 비행 기록 장치(flight recorder)라고 부르는가?
라이브 트레이싱 (Live tracing)은 유용하지만, 장애 대응 작업은 권한이 부여된 세션이 종료된 후에도 계속되는 경우가 많습니다.
다른 누군가가 캡처된 내용을 검사해야 할 수도 있습니다. 성공적인 요청과 비교하고 싶을 수도 있습니다. 버그 리포트에 특정 이벤트를 인용해야 할 수도 있습니다. 자동화 시스템이나 AI 시스템이 구조화된 입력 (structured input)을 필요로 할 수도 있지만, 캡처되지 않은 관찰 내용을 임의로 만들어내는 것은 허용되어서는 안 됩니다.
따라서 traceq라고 불리는 네이티브 skbx 스트림은 다음과 같은 엔벨로프 (envelope) 구조를 가집니다:
capture_start
event
event
...
모든 이벤트는 안정적인 핸들 (handle)을 부여받습니다:
event:111111111111111111111111
리플레이 (Replay)는 정렬된 이벤트들을 제한된 경로 패턴 (bounded route patterns)으로 그룹화하며, 이 패턴들은 자체적인 핸들을 가집니다:
route:1c0201e74424e253f8363577
푸터 (footer)는 이벤트만큼이나 중요합니다. 푸터는 중단 사유와 함께 커널 예약 실패 (kernel reservation failures), 트레이서 재귀 누락 (tracer recursion misses), 읽기 실패 (read failures), 유저스페이스 디코딩 및 인리치먼트 실패 (userspace decoding and enrichment failures), 그리고 출력 실패 (output failures)에 대한 신뢰성 카운터를 기록합니다.
만약 푸터가 없다면, 해당 아티팩트 (artifact)는 불완전한 것입니다. 만약 트레이서가 관찰 내용을 놓쳤다면, 그 불확실성은 결과에 그대로 붙어 있게 됩니다.
저는 이러한 특성을 좋아합니다. 트레이싱 소프트웨어도 결국 소프트웨어이기 때문입니다. 소프트웨어는 마치 모든 것을 알고 있는 것처럼 (omniscient) 조용히 행동해서는 안 됩니다.
설치 방법
현재 라이브 트레이서 (live tracer)는 x86_64 및 arm64 아키텍처의 Linux를 지원합니다. Rust 1.85 이상, BPF 백엔드를 포함한 Clang/LLVM, bpftool, libelf, libpcap, 그리고 /sys/kernel/btf/vmlinux를 노출하는 커널이 필요합니다.
Ubuntu에서는 다음과 같은 네이티브 패키지를 사용합니다:
sudo apt-get install "linux-tools-$(uname -r)" \
clang llvm libelf-dev libpcap-dev pkg-config
Debian에서는 다음과 같습니다:
sudo apt-get install \
bpftool clang llvm libelf-dev libpcap-dev pkg-config
그 다음 GitHub에서 CLI를 직접 설치합니다:
cargo install --git https://github.com/copyleftdev/skbx --locked skbx-cli
무엇인가를 연결하기 전에, doctor 명령어로 호스트를 점검합니다:
skbx doctor --json
plan은 한 단계 더 나아갑니다. 실제 부착(attachment)을 수행하지 않고도 정확히 어떤 함수들이 부착될 것인지 보여줍니다:
skbx plan --filter-func 'ip.*' --json
이러한 분리 방식은 프로젝트를 구축하는 동안 유용하게 작용했습니다. 누락된 커널 기능(kernel capability)은 권한이 필요한 캡처(capture)가 시작된 후 조용히 추정되는 것이 아니라, 시작 전에 미리 확인되어야 합니다.
첫 번째 패킷 캡처하기
다음은 간단한 ICMP 캡처 예시입니다:
sudo skbx capture --probe ip_rcv \
--duration 10 \
--output trace.jsonl \
...
캡처가 실행되는 동안, 다른 터미널에서 트래픽을 생성합니다:
ping -c 3 1.1.1.1
캡처는 의도적으로 제한되어 있습니다. 무제한적인 트레이스(trace)가 안전하다고 가정하는 대신, 유한한 지속 시간(duration)과 이벤트 제한(event limit)을 가집니다.
캡처가 완료된 후, 재생(replay)에는 루트(root) 권한이 필요하지 않습니다:
skbx replay trace.jsonl
재생(replay)은 함수, 프로세스, 패킷 식별자(packet identities), 경로 패턴(route patterns), 합의(consensus), 이상치(outliers), 그리고 신뢰성 상태(reliability state)를 요약합니다. 동일한 유효한 JSONL 입력을 제공하면, 바이트 단위로 일치하는(byte-identical) 요약본을 생성합니다.
만약 특정 이벤트가 흥미로워 보인다면, 해당 이벤트와 주변의 동일 패킷 증거(same-packet evidence)를 함께 가져올 수 있습니다:
skbx explain trace.jsonl event:<handle>
저장소에는 ip_rcv 이벤트에 이어 kfree_skb_reason이 뒤따르는 작은 컨트랙트 픽스처(contract fixture)가 포함되어 있습니다. 해당 픽스처를 재생하면 다음과 같은 형태의 경로(route)가 생성됩니다:
{
"handle": "route:1c0201e74424e253f8363577",
"functions": [
...
해당 출력은 체크인된 테스트 픽스처로부터 현재 CLI에 의해 생성된 것이며, 단순히 예시를 위한 가짜 데이터(pseudodata)가 아닙니다.
유스케이스: 웹사이트 요청에 무슨 일이 일어났는가?
이것은 제가 계속해서 되돌아오는 유스케이스(use case)입니다.
curl이 HTTPS 요청을 시작했는데 결국 타임아웃(timeout)이 발생했다고 가정해 봅시다. 클라이언트에서의 캡처는 다음과 같은 질문에 답할 수 있습니다:
- 요청이 로컬 TCP 및 IP 출력 경로(output paths)에 진입했는가?
- 어떤 인터페이스(interfaces) 및 네임스페이스(namespaces)와 연관되었는가?
- 마크(mark)가 변경되었는가?
- TC 또는 XDP 프로그램이 이를 처리했는가?
- 변환(transformed)되었거나 터널(tunnel)에 배치되었는가?
- 로컬 커널이 드롭(drop)을 보고했는가?
그러한 증거는 클라이언트 호스트의 결백을 증명하거나 책임을 물을 수 있습니다.
하지만 그것이 ISP 내부나 대상 서버에서 무슨 일이 일어났는지를 증명할 수는 없습니다. 그 부분들을 증명하려면 해당 관점(vantage points)에서의 관찰이 필요합니다. 이 한계점은 중요합니다. 로컬 드롭이 없다는 사실이 원격 호스트가 패킷을 받았다는 증거는 아닙니다.
유용한 결과는 증거에 기반하여 더 좁혀진 탐색 영역입니다:
클라이언트 호스트 증거
↓
관찰된 출발 경계(observed departure boundary)
...
저는 관찰할 수 없는 시스템에 대해 도구가 확신에 찬 이야기를 만들어내는 것보다, 차라리 제 증거가 정확히 어디에서 끝나는지를 아는 편을 택하겠습니다.
유스케이스(Use case): 호스트 내부에서 패킷이 사라지는 경우
"네트워크에서 드롭했다"라는 말은 매우 다른 여러 가지 장애를 숨길 수 있습니다.
호스트는 정상적인 커널 경로를 통해 패킷을 거부할 수 있습니다. TC 분류기(classifier)나 XDP 프로그램이 드롭 액션(drop action)을 반환할 수 있습니다. 패킷이 셧다운(teardown) 과정 중에 소비될 수도 있습니다. 변환(transformation)으로 인해 우리가 추적하던 패킷이 다른 커널 객체 하에서 계속 진행될 수도 있습니다.
집중적인 TCP 드롭 질문에 대해서는 작은 bpftrace 프로그램이 가장 빠른 답이 될 수 있습니다. 패킷 내용에 대해서는 여전히 tcpdump나 Wireshark를 사용합니다.
어떤 로컬 서브시스템(subsystem)이 장애의 원인인지 아직 알 수 없거나, 단일 드롭 이벤트보다는 순차적인 경로를 보존하고 싶을 때 저는 skbx를 사용합니다.
유스케이스(Use case): 흔적을 놓치는 대신 변환을 추적하기
Linux는 하나의 논리적 패킷이 전체 수명 동안 하나의 struct sk_buff 주소에 대응한다고 보장하지 않습니다.
패킷은 복제(cloned), 복사(copied)되거나 Copy-on-Write를 통해 변경될 수 있습니다. XDP 프레임은 나중에 SKB가 될 수 있습니다. 터널(Tunnels)은 외부(outer) 및 내부(inner) 패킷 정체성을 도입합니다.
skbx는 이러한 전환(transitions)을 연결하기 위해 제한된 범위의 캡처 로컬 계보 상태(capture-local lineage state)를 사용합니다. 출처(provenance)는 명시적으로 유지됩니다. 즉, 특정 이벤트가 원래의 필터와 일치했는지, 아니면 이미 추적 중인 패킷에 속해 있었기 때문에 포함되었는지를 기록합니다.
예를 들어, 마킹된 패킷은 다음과 같이 추적할 수 있습니다:
sudo skbx capture \--filter-mark 0x2a \--filter-track-skb\n...
여기서 중요한 단어는 _캡처 로컬(capture-local)_입니다. 계보 식별자(lineage identifier)는 분산 트레이스 ID(distributed trace ID)가 아니며, 그렇게 제시되어서도 안 됩니다.
유스케이스: 한 번 캡처하고, 루트 권한 없이 조사하기
eBPF 프로그램을 로드하고 부착(attach)하는 것은 권한이 필요한 작업입니다. 하지만 JSONL 파일을 읽는 것은 그럴 필요가 없습니다.
덕분에 다음과 같은 간단한 인계(handoff)가 가능해집니다:
- 운영자가 계획을 확인하고 제한된 범위의 캡처를 수행합니다.
- 캡처가 종료되고 신뢰성 푸터(reliability footer)를 작성합니다.
- 생성된 결과물(artifact)을 일반적인 개발 또는 분석 환경으로 복사합니다.
- 다른 사람이 이를 재생(replay)하고 정확한
event:또는route:핸들을 인용합니다.
이는 장애 인계 및 버그 보고에 유용할 뿐만 아니라, 학습에도 유용합니다. 저는 작은 실험을 한 번 캡처한 뒤, 결과를 이해하려고 시도하는 동안 프로브(probes)를 계속 부착해 둘 필요 없이 반복적으로 검사할 수 있습니다.
유스케이스: AI에게 미스터리 대신 증거를 제공하기
저의 다소 독특한 동기 중 하나는 CLI를 운영자와 소프트웨어 에이전트(software agents) 모두에게 친숙하게 만드는 것이었습니다.
호스트에 접근하기 전에, 에이전트는 실행 파일에 무엇을 지원하는지 물어볼 수 있습니다:
skbx describe --format json
skbx schema
skbx doctor --json
...
네이티브 엔진이 진실의 근원(source of truth)으로 남습니다. AI 시스템은 경로(route)를 요약하거나 캡처된 이벤트를 설명할 수 있지만, 해당 이벤트는 반드시 트레이스(trace) 내에 이미 존재해야 합니다. 기계의 출력은 표준 출력(standard output)에 유지되고, 진단 정보는 표준 에러(standard error)에 유지되며, 지원되지 않는 기능은 명시적으로 남습니다.
이것이 AI의 설명을 자동으로 정확하게 만들어주는 것은 아닙니다. 다만 설명에 인용할 수 있는 구체적인 근거를 제공하고, 사람이 이를 확인할 수 있는 방법을 제공할 뿐입니다.
즐거웠던 엔지니어링 요소들
분명한 재미는 이전에 다이어그램 속의 상자(box)로만 취급했던 함수들을 통해 패킷이 이동하는 것을 보는 것이었습니다.
덜 명백하지만 흥미로웠던 재미는 경계(boundaries)를 설계하는 것이었습니다:
- 함수 이름으로 추측하는 대신 대상 커널의 BTF로부터 유효한
struct sk_buff *인자를 찾아내는 것; - 커널 측의 상태(state)와 작업(work)을 제한하는 것;
- JSON 인코딩(encoding), 심볼화(symbolization), 파일 시스템 작업을 eBPF 핫 패스(hot path)에서 분리하는 것;
- 프로브(probe) 계획을 부착(attachment) 전에 검사 가능하게 만드는 것;
- 가공되지 않은 관찰(raw observation)과 이후의 설명(explanation)을 분리하는 것;
- 부분적인 출력을 "아마 충분히 괜찮을 것"이라고 치부하는 대신 불완전한 것으로 취급하는 것;
- 재생(replay)을 테스트 및 자동화에 사용할 수 있을 만큼 충분히 결정론적(deterministic)으로 만드는 것.
그러한 제약 조건들이 도구를 만드는 과정을 더 흥미롭게 만들었습니다. 또한 관찰성(observability) 도구가 진정으로 무엇을 주장할 수 있는지에 대해 더 나은 멘탈 모델(mental model)을 갖게 해주었습니다.
이것이 알지 못하는 것
skbx는 실행 중인 커널과 선택된 프로브가 노출하는 것만을 관찰합니다.
이 도구는 ISP 내부를 보지 못합니다. 원격 호스트에서도 실행되지 않는 한 그곳을 볼 수 없습니다. Wireshark를 대체하는 패킷 콘텐츠(packet-content) 도구도 아닙니다. 커널 설정, BTF 가용성, 권한, 숨겨진 주소, 지원되지 않는 시그니처(signature), 그리고 캡처 손실(capture loss) 등이 모두 증거를 제한할 수 있습니다.
그러한 한계점들은 각주라기보다는 인터페이스의 일부입니다.
저는 이 프로젝트가 어디에서 가장 유용할지 여전히 탐색 중입니다. 가장 좋은 다음 입력값은 "멋져 보이네요"가 아닙니다(물론 그런 말을 듣는다면 기쁘겠지만요). 그것은 도구가 아직 설명할 수 없는 패킷 경로와 함께 커널 버전, 정확한 명령어, doctor --json 출력 결과, 그리고 신뢰성 푸터(reliability footer)입니다.
소스 코드, 설치 지침, 아키텍처 노트 및 필드 가이드는 저장소에 있습니다:
GitHub logo copyleftdev / skbx
Rust/eBPF를 이용한 에이전트 우선(Agent-first) Linux 패킷 경로 추적: 제한된 증거, 결정론적 재생, 그리고 영수증이 포함된 패킷 경로.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기