Microsoft MVP성태의 닷넷 이야기
Linux: 124. eBPF - __sk_buff / sk_buff 구조체 [링크 복사], [링크+제목 복사],
조회: 785
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 

eBPF - __sk_buff / sk_buff 구조체

지난 글에서 설명한,

eBPF (bpf2go) - BPF_PROG_TYPE_SOCKET_FILTER 예제 - SEC("socket")
; https://www.sysnet.pe.kr/2/0/14017

BPF_PROG_TYPE_SOCKET_FILTER 프로그램에서 __sk_buff 구조체를 사용했었는데요,

SEC("socket")
int socket_handler(struct __sk_buff *skb)
{
    // Your eBPF program logic here
    return skb->len;
}

이에 대한 자료를 찾아보면,

Understanding struct __sk_buff
; https://wangcong.org/2021-08-15-understanding-struct-__sk_buff.html

Program context __sk_buff
; https://docs.ebpf.io/linux/program-context/__sk_buff/

원래의 커널 구조체는 sk_buff이고, eBPF 로더가 런타임 시에 __sk_buff의 필드에 대한 접근을 sk_buff로 변환해 준다고 합니다. 이에 대한 변환 코드를 bpf_convert_ctx_access 함수에서 확인할 수 있는데,

// https://elixir.bootlin.com/linux/v5.13.10/source/net/core/filter.c#L8411

static u32 bpf_convert_ctx_access(enum bpf_access_type type,
                  const struct bpf_insn *si,
                  struct bpf_insn *insn_buf,
                  struct bpf_prog *prog, u32 *target_size)
{
    struct bpf_insn *insn = insn_buf;
    int off;

    switch (si->off) {
    case offsetof(struct __sk_buff, len):
        *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->src_reg,
                      bpf_target_off(struct sk_buff, len, 4,
                             target_size));
        break;

    case offsetof(struct __sk_buff, protocol):
        *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->src_reg,
                      bpf_target_off(struct sk_buff, protocol, 2,
                             target_size));
        break;

    case offsetof(struct __sk_buff, vlan_proto):
        *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->src_reg,
                      bpf_target_off(struct sk_buff, vlan_proto, 2,
                             target_size));
        break;

    case offsetof(struct __sk_buff, priority):
        if (type == BPF_WRITE)
            *insn++ = BPF_STX_MEM(BPF_W, si->dst_reg, si->src_reg,
                          bpf_target_off(struct sk_buff, priority, 4,
                                 target_size));
        else
            *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->src_reg,
                          bpf_target_off(struct sk_buff, priority, 4,
                                 target_size));
        break;

    case offsetof(struct __sk_buff, ingress_ifindex):
        *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->src_reg,
                      bpf_target_off(struct sk_buff, skb_iif, 4,
                             target_size));
        break;

    case offsetof(struct __sk_buff, ifindex):
        *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_buff, dev),
                      si->dst_reg, si->src_reg,
                      offsetof(struct sk_buff, dev));
        *insn++ = BPF_JMP_IMM(BPF_JEQ, si->dst_reg, 0, 1);
        *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
                      bpf_target_off(struct net_device, ifindex, 4,
                             target_size));
        break;

    case offsetof(struct __sk_buff, hash):
        *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->src_reg,
                      bpf_target_off(struct sk_buff, hash, 4,
                             target_size));
        break;

    case offsetof(struct __sk_buff, mark):
        if (type == BPF_WRITE)
            *insn++ = BPF_STX_MEM(BPF_W, si->dst_reg, si->src_reg,
                          bpf_target_off(struct sk_buff, mark, 4,
                                 target_size));
        else
            *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->src_reg,
                          bpf_target_off(struct sk_buff, mark, 4,
                                 target_size));
        break;

    case offsetof(struct __sk_buff, pkt_type):
        *target_size = 1;
        *insn++ = BPF_LDX_MEM(BPF_B, si->dst_reg, si->src_reg,
                      PKT_TYPE_OFFSET());
        *insn++ = BPF_ALU32_IMM(BPF_AND, si->dst_reg, PKT_TYPE_MAX);
#ifdef __BIG_ENDIAN_BITFIELD
        *insn++ = BPF_ALU32_IMM(BPF_RSH, si->dst_reg, 5);
#endif
        break;

    case offsetof(struct __sk_buff, queue_mapping):
        if (type == BPF_WRITE) {
            *insn++ = BPF_JMP_IMM(BPF_JGE, si->src_reg, NO_QUEUE_MAPPING, 1);
            *insn++ = BPF_STX_MEM(BPF_H, si->dst_reg, si->src_reg,
                          bpf_target_off(struct sk_buff,
                                 queue_mapping,
                                 2, target_size));
        } else {
            *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->src_reg,
                          bpf_target_off(struct sk_buff,
                                 queue_mapping,
                                 2, target_size));
        }
        break;

    case offsetof(struct __sk_buff, vlan_present):
        *target_size = 1;
        *insn++ = BPF_LDX_MEM(BPF_B, si->dst_reg, si->src_reg,
                      PKT_VLAN_PRESENT_OFFSET());
        if (PKT_VLAN_PRESENT_BIT)
            *insn++ = BPF_ALU32_IMM(BPF_RSH, si->dst_reg, PKT_VLAN_PRESENT_BIT);
        if (PKT_VLAN_PRESENT_BIT < 7)
            *insn++ = BPF_ALU32_IMM(BPF_AND, si->dst_reg, 1);
        break;

    case offsetof(struct __sk_buff, vlan_tci):
        *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->src_reg,
                      bpf_target_off(struct sk_buff, vlan_tci, 2,
                             target_size));
        break;

    case offsetof(struct __sk_buff, cb[0]) ...
         offsetofend(struct __sk_buff, cb[4]) - 1:
        BUILD_BUG_ON(sizeof_field(struct qdisc_skb_cb, data) < 20);
        BUILD_BUG_ON((offsetof(struct sk_buff, cb) +
                  offsetof(struct qdisc_skb_cb, data)) %
                 sizeof(__u64));

        prog->cb_access = 1;
        off  = si->off;
        off -= offsetof(struct __sk_buff, cb[0]);
        off += offsetof(struct sk_buff, cb);
        off += offsetof(struct qdisc_skb_cb, data);
        if (type == BPF_WRITE)
            *insn++ = BPF_STX_MEM(BPF_SIZE(si->code), si->dst_reg,
                          si->src_reg, off);
        else
            *insn++ = BPF_LDX_MEM(BPF_SIZE(si->code), si->dst_reg,
                          si->src_reg, off);
        break;

    case offsetof(struct __sk_buff, tc_classid):
        BUILD_BUG_ON(sizeof_field(struct qdisc_skb_cb, tc_classid) != 2);

        off  = si->off;
        off -= offsetof(struct __sk_buff, tc_classid);
        off += offsetof(struct sk_buff, cb);
        off += offsetof(struct qdisc_skb_cb, tc_classid);
        *target_size = 2;
        if (type == BPF_WRITE)
            *insn++ = BPF_STX_MEM(BPF_H, si->dst_reg,
                          si->src_reg, off);
        else
            *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg,
                          si->src_reg, off);
        break;

    case offsetof(struct __sk_buff, data):
        *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_buff, data),
                      si->dst_reg, si->src_reg,
                      offsetof(struct sk_buff, data));
        break;

    case offsetof(struct __sk_buff, data_meta):
        off  = si->off;
        off -= offsetof(struct __sk_buff, data_meta);
        off += offsetof(struct sk_buff, cb);
        off += offsetof(struct bpf_skb_data_end, data_meta);
        *insn++ = BPF_LDX_MEM(BPF_SIZEOF(void *), si->dst_reg,
                      si->src_reg, off);
        break;

    case offsetof(struct __sk_buff, data_end):
        off  = si->off;
        off -= offsetof(struct __sk_buff, data_end);
        off += offsetof(struct sk_buff, cb);
        off += offsetof(struct bpf_skb_data_end, data_end);
        *insn++ = BPF_LDX_MEM(BPF_SIZEOF(void *), si->dst_reg,
                      si->src_reg, off);
        break;

    case offsetof(struct __sk_buff, tc_index):
#ifdef CONFIG_NET_SCHED
        if (type == BPF_WRITE)
            *insn++ = BPF_STX_MEM(BPF_H, si->dst_reg, si->src_reg,
                          bpf_target_off(struct sk_buff, tc_index, 2,
                                 target_size));
        else
            *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->src_reg,
                          bpf_target_off(struct sk_buff, tc_index, 2,
                                 target_size));
#else
        *target_size = 2;
        if (type == BPF_WRITE)
            *insn++ = BPF_MOV64_REG(si->dst_reg, si->dst_reg);
        else
            *insn++ = BPF_MOV64_IMM(si->dst_reg, 0);
#endif
        break;

    case offsetof(struct __sk_buff, napi_id):
#if defined(CONFIG_NET_RX_BUSY_POLL)
        *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->src_reg,
                      bpf_target_off(struct sk_buff, napi_id, 4,
                             target_size));
        *insn++ = BPF_JMP_IMM(BPF_JGE, si->dst_reg, MIN_NAPI_ID, 1);
        *insn++ = BPF_MOV64_IMM(si->dst_reg, 0);
#else
        *target_size = 4;
        *insn++ = BPF_MOV64_IMM(si->dst_reg, 0);
#endif
        break;
    case offsetof(struct __sk_buff, family):
        BUILD_BUG_ON(sizeof_field(struct sock_common, skc_family) != 2);

        *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_buff, sk),
                      si->dst_reg, si->src_reg,
                      offsetof(struct sk_buff, sk));
        *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->dst_reg,
                      bpf_target_off(struct sock_common,
                             skc_family,
                             2, target_size));
        break;
    case offsetof(struct __sk_buff, remote_ip4):
        BUILD_BUG_ON(sizeof_field(struct sock_common, skc_daddr) != 4);

        *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_buff, sk),
                      si->dst_reg, si->src_reg,
                      offsetof(struct sk_buff, sk));
        *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
                      bpf_target_off(struct sock_common,
                             skc_daddr,
                             4, target_size));
        break;
    case offsetof(struct __sk_buff, local_ip4):
        BUILD_BUG_ON(sizeof_field(struct sock_common,
                      skc_rcv_saddr) != 4);

        *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_buff, sk),
                      si->dst_reg, si->src_reg,
                      offsetof(struct sk_buff, sk));
        *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
                      bpf_target_off(struct sock_common,
                             skc_rcv_saddr,
                             4, target_size));
        break;
    case offsetof(struct __sk_buff, remote_ip6[0]) ...
         offsetof(struct __sk_buff, remote_ip6[3]):
#if IS_ENABLED(CONFIG_IPV6)
        BUILD_BUG_ON(sizeof_field(struct sock_common,
                      skc_v6_daddr.s6_addr32[0]) != 4);

        off = si->off;
        off -= offsetof(struct __sk_buff, remote_ip6[0]);

        *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_buff, sk),
                      si->dst_reg, si->src_reg,
                      offsetof(struct sk_buff, sk));
        *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
                      offsetof(struct sock_common,
                           skc_v6_daddr.s6_addr32[0]) +
                      off);
#else
        *insn++ = BPF_MOV32_IMM(si->dst_reg, 0);
#endif
        break;
    case offsetof(struct __sk_buff, local_ip6[0]) ...
         offsetof(struct __sk_buff, local_ip6[3]):
#if IS_ENABLED(CONFIG_IPV6)
        BUILD_BUG_ON(sizeof_field(struct sock_common,
                      skc_v6_rcv_saddr.s6_addr32[0]) != 4);

        off = si->off;
        off -= offsetof(struct __sk_buff, local_ip6[0]);

        *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_buff, sk),
                      si->dst_reg, si->src_reg,
                      offsetof(struct sk_buff, sk));
        *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->dst_reg,
                      offsetof(struct sock_common,
                           skc_v6_rcv_saddr.s6_addr32[0]) +
                      off);
#else
        *insn++ = BPF_MOV32_IMM(si->dst_reg, 0);
#endif
        break;

    case offsetof(struct __sk_buff, remote_port):
        BUILD_BUG_ON(sizeof_field(struct sock_common, skc_dport) != 2);

        *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_buff, sk),
                      si->dst_reg, si->src_reg,
                      offsetof(struct sk_buff, sk));
        *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->dst_reg,
                      bpf_target_off(struct sock_common,
                             skc_dport,
                             2, target_size));
#ifndef __BIG_ENDIAN_BITFIELD
        *insn++ = BPF_ALU32_IMM(BPF_LSH, si->dst_reg, 16);
#endif
        break;

    case offsetof(struct __sk_buff, local_port):
        BUILD_BUG_ON(sizeof_field(struct sock_common, skc_num) != 2);

        *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_buff, sk),
                      si->dst_reg, si->src_reg,
                      offsetof(struct sk_buff, sk));
        *insn++ = BPF_LDX_MEM(BPF_H, si->dst_reg, si->dst_reg,
                      bpf_target_off(struct sock_common,
                             skc_num, 2, target_size));
        break;

    case offsetof(struct __sk_buff, tstamp):
        BUILD_BUG_ON(sizeof_field(struct sk_buff, tstamp) != 8);

        if (type == BPF_WRITE)
            *insn++ = BPF_STX_MEM(BPF_DW,
                          si->dst_reg, si->src_reg,
                          bpf_target_off(struct sk_buff,
                                 tstamp, 8,
                                 target_size));
        else
            *insn++ = BPF_LDX_MEM(BPF_DW,
                          si->dst_reg, si->src_reg,
                          bpf_target_off(struct sk_buff,
                                 tstamp, 8,
                                 target_size));
        break;

    case offsetof(struct __sk_buff, gso_segs):
        insn = bpf_convert_shinfo_access(si, insn);
        *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct skb_shared_info, gso_segs),
                      si->dst_reg, si->dst_reg,
                      bpf_target_off(struct skb_shared_info,
                             gso_segs, 2,
                             target_size));
        break;
    case offsetof(struct __sk_buff, gso_size):
        insn = bpf_convert_shinfo_access(si, insn);
        *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct skb_shared_info, gso_size),
                      si->dst_reg, si->dst_reg,
                      bpf_target_off(struct skb_shared_info,
                             gso_size, 2,
                             target_size));
        break;
    case offsetof(struct __sk_buff, wire_len):
        BUILD_BUG_ON(sizeof_field(struct qdisc_skb_cb, pkt_len) != 4);

        off = si->off;
        off -= offsetof(struct __sk_buff, wire_len);
        off += offsetof(struct sk_buff, cb);
        off += offsetof(struct qdisc_skb_cb, pkt_len);
        *target_size = 4;
        *insn++ = BPF_LDX_MEM(BPF_W, si->dst_reg, si->src_reg, off);
        break;

    case offsetof(struct __sk_buff, sk):
        *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_buff, sk),
                      si->dst_reg, si->src_reg,
                      offsetof(struct sk_buff, sk));
        break;
    }

    return insn - insn_buf;
}

예를 들어, __sk_buff.gso_size 필드는 sk_buff 구조체의 skb_shared_info.gso_size 필드로 변환되는 식입니다.




그런데, 좀 모호한 면도 있습니다. 가령, __sk_buff.data 필드는 "__u32" 타입이지만 그것의 대응이 되는 sk_buff.data 필드는 포인터에 해당하는 "unsigned char *" 타입입니다. 그래서인지 모르겠지만 __sk_buff.data 필드를 직접 접근하면,

SEC("socket")
int socket_handler(struct __sk_buff *ctx)
{
    bpf_printk("[__sk_buff] data == %d\n", ctx->data);
    // ...[생략]...
}

eBPF 로딩 시 이런 오류가 발생합니다. (이에 대해서는 아래에서 더 다룰 것입니다.)

// 테스트 환경: Ubuntu 24.04 LTS

load program: permission denied: 0: (61) r3 = *(u32 *)(r1 +76): invalid bpf_context access off=76 size=4 (3 line(s) omitted)

// 76 오프셋은 struct __sk_buff.data 필드 위치

참고로, data 필드는 패킷의 시작 위치를 가리킨다고 합니다.

// struct sk_buff
// ; https://docs.kernel.org/networking/skbuff.html
//
// Measuring TCP Latency Using eBPF: Part 2 - Kernel Space - struct __sk_buff
// ; https://fiwippi.net/tcplat-p2
//
// struct sk_buff 관련 함수
// ; https://hand-over.tistory.com/2

    sk_buff.data --> +-----------------+
                     | Ethernet Header |
                     +-----------------+
                     |    IP Header    |
sk_buff.data_end --> +-----------------+
                     |   TCP Header    |
                     +-----------------+
                     |     Payload     |
                     +-----------------+

[출처: https://www.cubrid.org/blog/3826497]
sk_buff

다행히 eBPF에서는 저 영역을 접근할 수 있는 도우미 함수를 제공하는데요,

Helper function bpf_skb_load_bytes (커널 버전 4.5)
; https://docs.ebpf.io/linux/helper-function/bpf_skb_load_bytes/

관련해서 검색해 보면 "L7 Tracing with eBPF: HTTP and Beyond via Socket Filters and Syscall Tracepoints" 글에 bpf_skb_load_bytes 함수를 사용한 예제가 나옵니다.

SEC("socket")
int socket_handler(struct __sk_buff *skb)
{
    __u16 proto;

    bpf_skb_load_bytes(skb, 12, &proto, 2);
    proto = __bpf_ntohs(proto);

    return skb->len;

/* proto 값의 예:
0x0800 for IPv4
0x0806 for ARP
0x86DD for IPv6
*/
}

위의 코드를 해석해 보면, skb->data 필드가 가리키는 위치가 ethernet 헤더의 시작 위치이므로,

// cat vmlinux.h | grep -A 18 "struct ethhdr {"

struct ethhdr {
        unsigned char h_dest[6];  // offset 0
        unsigned char h_source[6]; // offset 6
        __be16 h_proto; // offset 12
};

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|h_dest     | h_source  |** |  // (** h_proto)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 

결과적으로, 12 위치는 h_proto 필드의 offset 값이 됩니다. 즉, protocol 값을 가져오는 것인데요, 그런데 제 실습 환경에서 실행해 보면 엉뚱한 값이 나왔습니다.

// TCP 소켓 예제에서 출력된 h_proto 값들

bpf_trace_printk: __sk_buff.h_proto == 8019
bpf_trace_printk: __sk_buff.h_proto == b010
bpf_trace_printk: __sk_buff.h_proto == 5014
bpf_trace_printk: __sk_buff.h_proto == 5004

반면 ctx->protocol 필드를 직접 읽어오면, (TCP 소켓 예제이므로) 항상 정확하게 ETH_P_IP에 해당하는 8(BE 표현으로는 0x0800) 값이 나옵니다. 이해할 수 없군요, ^^ 분명히 "L7 Tracing with eBPF: HTTP and Beyond via Socket Filters and Syscall Tracepoints" 글에서의 코드와 같은데... 이유를 모르겠습니다. (혹시 아시는 분은 덧글 부탁드립니다. ^^;)

이와 관련해 좀 더 검색했더니 SEC("tc") 프로그램 예제가 나오는데요,

hello-libbpfgo/25-tc-parse-packet-with-direct-memory-access/main.bpf.c
; https://github.com/mozillazg/hello-libbpfgo/blob/master/25-tc-parse-packet-with-direct-memory-access/main.bpf.c#L24

Measuring TCP Latency Using eBPF: Part 2 - Kernel Space
; https://fiwippi.net/tcplat-p2

SEC("tc")
int handle_ingress(struct __sk_buff *skb) {
    bpf_skb_pull_data(skb, 0);

    void *data_end = (void *)(long)skb->data_end;
    void *data = (void *)(long)skb->data;

    struct iphdr *ip_hdr = data + ETH_HLEN;
    if ((void *)ip_hdr + sizeof(struct iphdr) > data_end) {
        return TC_ACT_OK;
    }

    // ...[생략]...
}

오호~~~ 32비트 값을 포인터로 변환해 사용하고 있습니다. 리눅스 커널은 저런 구조체를 4GB 하위에 할당하는 걸까요? (그러고 보니 전에도 한 번 이와 관련한 의문을 남긴 적이 있습니다.) 과연 이것을 올바른 예제라고 봐야 할지 모르겠지만... 어쨌든 동작했으니까 ^^ 저렇게 github에 올렸을 것입니다. 그리고 위의 예제에서 특이한 점은 bpf_skb_pull_data 함수를 사용했다는 점인데,

Helper function bpf_skb_pull_data (커널 버전 4.9)
; https://docs.ebpf.io/linux/helper-function/bpf_skb_pull_data/

문서에 보면, skb.data 영역을 접근할 수 있는 용도로 제공한 첫 번째 함수가 bpf_skb_load_bytes였고, 두 번째로 나온 것이 "direct packet access"를 가능하게 만든 bpf_skb_pull_data였다고 합니다. (이후 Linux 4.7부터는 bpf_skb_load_bytes 함수도 "direct packet access"를 지원하게 돼 이제는 그런 차이는 없어졌습니다.)

참고로, bpf_skb_pull_data는 SEC("socket")에 해당하는 BPF_PROG_TYPE_SOCKET_FILTER 프로그램에서는 사용할 수 없습니다.

SEC("socket") // SEC("tc")에서는, 즉 BPF_PROG_TYPE_SCHED_CLS 유형의 프로그램에서는 bpf_skb_pull_data 사용 불가능
int socket_handler(struct __sk_buff *ctx)
{
    bpf_skb_pull_data(ctx, 0); // eBPF 프로그램 로딩 시 오류 발생: invalid argument: program of this type cannot use helper bpf_skb_pull_data#39 (7 line(s) omitted)

    return ctx->len;
}




github에 있는 다른 예제를 하나 더 테스트해 봤는데요,

// https://github.com/mozillazg/hello-libbpfgo/blob/master/19-socket-filter-capture-icmp-traffic-userspace-parse/main.bpf.c

#include "vmlinux.h"

#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_endian.h>

unsigned long long load_half(void *skb, unsigned long long off) asm("llvm.bpf.load.half");

SEC("socket")
int socket_handler(struct __sk_buff *ctx)
{
    // load_half 함수는 data 포인터가 가리키는 영역으로부터 offset 위치의 16비트 값을 읽어오는 도우미 함수
    __u16 proto = load_half(ctx, offsetof(struct ethhdr, h_proto));
    bpf_printk("ethhdr.h_proto == %04x, protocol == %04x\n", proto, __bpf_htons(ctx->protocol));

    return ctx->len;
}

// ETH_P_IP == 0x0800

이것 역시 ETH_P_IP 값이 나오는 것으로 사용하고 있지만, 제가 테스트한 Ubuntu 24.04 환경에서는 다른 결과가 나옵니다.

bpf_trace_printk: ethhdr.h_proto == 8019, protocol == 0800
bpf_trace_printk: ethhdr.h_proto == 8010, protocol == 0800
bpf_trace_printk: ethhdr.h_proto == 5014, protocol == 0800

원래 의도했던 값은 h_proto와 protocol 값이 같아야 할 텐데, 이번에도 protocol은 정상인 반면 h_proto는 (if_ether.h에는 없는) 이상한 값이 나옵니다.




마지막으로 "Understanding struct __sk_buff" 글에 보면, "Notice that the base address is same for both sk_buff and __sk_buff."라는 설명도 나옵니다. 그렇다면 struct sk_buff *skb로 대체해도 되는 걸까요?

하지만, 이것 역시 실제로 해보면,

SEC("socket")
int socket_handler(struct sk_buff *ctx)
{
    return ctx->len;
}

eBPF 로더에서 오류가 발생합니다.

load program: permission denied: 0: (79) r3 = *(u64 *)(r1 +112): invalid bpf_context access off=112 size=8 (3 line(s) omitted)

// 112 오프셋은 struct sk_buff.len 필드 위치

따라서 eBPF 프로그램에 따라 struct __sk_buff와 struct __sk_buff 타입은 정해진 대로 구별해서 사용해야 하는 것이 맞습니다. 찾아보면, StackOverflow 글에서도 이에 대한 언급이 있군요. ^^

Cannot access __skb_buff in eBPF
; https://stackoverflow.com/questions/72717564/cannot-access-skb-buff-in-ebpf

First is that the argument to the tracepoint is of type struct sk_buff, not struct __sk_buff.





암튼, eBPF를 무작위로 공부하고는 있지만 실습도 잘 안되는 예제들이 있어 답답한 면이 좀 있습니다. ^^ 그나마 다행인 것은 리눅스 커널 소스 코드가 공개인 덕분에 많은 Q&A에서 소스 코드를 함께 언급하며 설명하고 있어 이해하는 데 도움은 되고 있다는 점입니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 10/2/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... [136]  137  138  139  140  141  142  143  144  145  146  147  148  149  150  ...
NoWriterDateCnt.TitleFile(s)
1753정성태9/22/201424465오류 유형: 240. Lync로 세미나 참여 시 소리만 들리지 않는 경우 [1]
1752정성태9/21/201444288Windows: 100. 윈도우 8 - RDP 연결을 이용해 VNC처럼 사용자 로그온 화면을 공유하는 방법 [5]
1751정성태9/20/201442510.NET Framework: 464. 프로세스 간 통신 시 소켓 필요 없이 간단하게 Pipe를 열어 통신하는 방법 [1]파일 다운로드1
1750정성태9/20/201428424.NET Framework: 463. PInvoke 호출을 이용한 비동기 파일 작업파일 다운로드1
1749정성태9/20/201427851.NET Framework: 462. 커널 객체를 위한 null DACL 생성 방법파일 다운로드1
1748정성태9/19/201429532개발 환경 구성: 238. [Synergy] 여러 컴퓨터에서 키보드, 마우스 공유
1747정성태9/19/201432966오류 유형: 239. psexec 실행 오류 - The system cannot find the file specified.
1746정성태9/18/201428804.NET Framework: 461. .NET EXE 파일을 닷넷 프레임워크 버전에 상관없이 실행할 수 있을까요? - 두 번째 이야기 [6]파일 다운로드1
1745정성태9/17/201427176개발 환경 구성: 237. 리눅스 Integration Services 버전 업그레이드 하는 방법 [1]
1744정성태9/17/201435507.NET Framework: 460. GetTickCount / GetTickCount64와 0x7FFE0000 주솟값 [4]파일 다운로드1
1743정성태9/16/201425009오류 유형: 238. 설치 오류 - Failed to get size of pseudo bundle
1742정성태8/27/201431657개발 환경 구성: 236. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 [2]
1741정성태8/26/201425228.NET Framework: 459. GetModuleHandleEx로 알아보는 .NET 메서드의 DLL 모듈 관계파일 다운로드1
1740정성태8/25/201437003.NET Framework: 458. 닷넷 GC가 순환 참조를 해제할 수 있을까요? [2]파일 다운로드1
1739정성태8/24/201430834.NET Framework: 457. 교착상태(Dead-lock) 해결 방법 - Lock Leveling [2]파일 다운로드1
1738정성태8/23/201426579.NET Framework: 456. C# - CAS를 이용한 Lock 래퍼 클래스파일 다운로드1
1737정성태8/20/201423143VS.NET IDE: 93. Visual Studio 2013 동기화 문제
1736정성태8/19/201429828VC++: 79. [부연] CAS Lock 알고리즘은 과연 빠른가? [2]파일 다운로드1
1735정성태8/19/201422555.NET Framework: 455. 닷넷 사용자 정의 예외 클래스의 최소 구현 코드 - 두 번째 이야기
1734정성태8/13/201424213오류 유형: 237. Windows Media Player cannot access the file. The file might be in use, you might not have access to the computer where the file is stored, or your proxy settings might not be correct.
1733정성태8/13/201430529.NET Framework: 454. EmptyWorkingSet Win32 API를 사용하는 C# 예제파일 다운로드1
1732정성태8/13/201439334Windows: 99. INetCache 폴더가 다르게 보이는 이유
1731정성태8/11/201431063개발 환경 구성: 235. 점(.)으로 시작하는 파일명을 탐색기에서 만드는 방법
1730정성태8/11/201426556개발 환경 구성: 234. Royal TS의 터미널(Terminal) 연결에서 한글이 깨지는 현상 해결 방법
1729정성태8/11/201422201오류 유형: 236. SqlConnection - The requested Performance Counter is not a custom counter, it has to be initialized as ReadOnly.
1728정성태8/8/201434999.NET Framework: 453. C# - 오피스 파워포인트(Powerpoint) 파일을 WinForm에서 보는 방법파일 다운로드1
... [136]  137  138  139  140  141  142  143  144  145  146  147  148  149  150  ...