eBPF를 이용한 네트워크 트래픽 모니터링

2024. 10. 31. 22:51 개발 이야기/eBPF

 

 최근 네트워크 트래픽을 실시간으로 모니터링하고 분석하기 위해 eBPF(Extended Berkeley Packet Filter)를 활용하는 사례가 증가하고 있습니다. eBPF는 커널 레벨에서 네트워크 패킷을 효율적으로 분석할 수 있는 기능을 제공하며, 높은 성능과 유연성을 제공합니다. 이번 포스팅에서는 eBPF를 이용해 네트워크 트래픽을 모니터링하는 방법을 코드와 함께 설명드리겠습니다.

eBPF란?

eBPF는 리눅스 커널에서 실행되는 프로그램을 작성하고 실행할 수 있는 메커니즘입니다. eBPF 프로그램은 커널에서 특정 이벤트가 발생할 때 실행되며, 이를 통해 네트워크 패킷 분석, 성능 모니터링, 보안 등의 다양한 기능을 수행할 수 있습니다. eBPF의 가장 큰 장점은 커널에서 동작하기 때문에 오버헤드가 적고, 사용자 영역에서의 상세한 모니터링이 가능하다는 것입니다.

환경 준비

eBPF를 사용하기 위해서는 최신 리눅스 커널(4.1 이상)과 eBPF 도구들이 필요합니다. 여기에서는 bccbpftrace 같은 라이브러리를 사용해 eBPF 프로그램을 작성해 보겠습니다.

우선, eBPF 도구인 BCC를 설치합니다:

sudo apt-get update
sudo apt-get install -y bpfcc-tools linux-headers-$(uname -r) python3-bpfcc

네트워크 트래픽 모니터링 코드 작성

다음으로, eBPF를 사용해 특정 인터페이스에서 네트워크 트래픽을 모니터링하는 간단한 Python 스크립트를 작성해보겠습니다. 이 스크립트는 BCC 라이브러리를 사용해 eBPF 프로그램을 로드하고 네트워크 패킷 정보를 수집합니다.

from bcc import BPF

# eBPF 프로그램 작성
bpf_program = """
#include <uapi/linux/bpf.h>
#include <uapi/linux/if_ether.h>
#include <uapi/linux/ip.h>

BPF_HASH(packet_count, u32, u64);

int count_packets(struct __sk_buff *skb) {
    u32 key = 0;
    u64 *value, zero = 0;

    // 패킷 수를 key = 0 에 누적
    value = packet_count.lookup_or_init(&key, &zero);
    if (value) {
        (*value)++;
    }
    return 0;
}
"""

# BPF 객체 생성
b = BPF(text=bpf_program)

# 모든 네트워크 인터페이스에서 패킷을 처리하는 함수 연결
b.attach_kprobe(event="dev_queue_xmit", fn_name="count_packets")

try:
    print("Monitoring network traffic... Press Ctrl+C to stop.")
    while True:
        # packet_count에 저장된 데이터를 읽어옴
        total_packets = b["packet_count"].items()
        for k, v in total_packets:
            print(f"Total Packets: {v.value}")
except KeyboardInterrupt:
    print("Stopping network traffic monitoring.")

위 코드에서 count_packets 함수는 커널에서 실행되어 네트워크 패킷을 카운트합니다. 이 함수는 BPF의 해시 맵을 사용하여 특정 키에 패킷의 수를 누적하며, 이를 통해 전체 트래픽 양을 모니터링할 수 있습니다.

주요 함수 설명

  • BPF_HASH(packet_count, u32, u64): BPF 해시 맵을 정의합니다. 여기서는 패킷 수를 저장하기 위한 해시 맵으로 사용됩니다.
  • count_packets(struct __sk_buff *skb): 네트워크 인터페이스에서 전송되는 모든 패킷을 처리하기 위해 호출되는 함수입니다. 패킷이 수신될 때마다 카운터를 증가시킵니다.
  • attach_kprobe(event="dev_queue_xmit", fn_name="count_packets"): 커널 함수 dev_queue_xmit에 eBPF 프로그램을 연결하여 패킷이 네트워크 인터페이스로 전송될 때마다 count_packets 함수가 호출되도록 합니다.

코드 실행 결과

이 스크립트를 실행하면 터미널에서 실시간으로 패킷 수를 확인할 수 있습니다. 네트워크 트래픽을 모니터링하는 동안 특정 인터페이스에서 발생하는 패킷의 수를 간단하게 확인할 수 있으며, 이를 기반으로 네트워크 상태를 평가할 수 있습니다.

확장 예제: 특정 포트 모니터링

이번에는 네트워크 트래픽 중 특정 포트(예: 80번 포트, HTTP 트래픽)를 모니터링하는 예제를 확장해 보겠습니다.

from bcc import BPF

bpf_program = """
#include <uapi/linux/bpf.h>
#include <uapi/linux/if_ether.h>
#include <uapi/linux/ip.h>
#include <uapi/linux/tcp.h>

BPF_HASH(http_count, u32, u64);

int count_http_packets(struct __sk_buff *skb) {
    struct iphdr *ip;
    struct tcphdr *tcp;
    u32 key = 0;
    u64 *value, zero = 0;

    ip = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
    if (ip->protocol == IPPROTO_TCP) {
        tcp = (struct tcphdr *)(skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr));
        if (tcp->dest == htons(80)) { // HTTP 포트
            value = http_count.lookup_or_init(&key, &zero);
            if (value) {
                (*value)++;
            }
        }
    }
    return 0;
}
"""

b = BPF(text=bpf_program)
b.attach_kprobe(event="dev_queue_xmit", fn_name="count_http_packets")

try:
    print("Monitoring HTTP traffic... Press Ctrl+C to stop.")
    while True:
        total_http_packets = b["http_count"].items()
        for k, v in total_http_packets:
            print(f"Total HTTP Packets: {v.value}")
except KeyboardInterrupt:
    print("Stopping HTTP traffic monitoring.")

위 확장된 코드에서는 TCP 패킷 중 포트 80(HTTP 트래픽)에 해당하는 패킷만을 카운트합니다. 이를 통해 특정 서비스에 대한 트래픽 모니터링도 가능해집니다.

마무리

eBPF를 활용하면 커널 수준에서 네트워크 트래픽을 실시간으로 모니터링할 수 있습니다. 이러한 기능은 네트워크 보안, 성능 분석, 문제 진단 등 다양한 목적에 활용될 수 있습니다. 이번 포스팅에서 소개한 간단한 예제를 바탕으로, 네트워크 분석 및 모니터링 시스템을 직접 구축해 보시길 바랍니다. eBPF는 매우 강력한 도구로, 다양한 응용 분야에 확장될 수 있습니다.