Bonding/Team/MACVLAN/veth/TUN-TAP

Linux 커널 NIC 이중화 및 가상 인터페이스 분석: Bonding(802.3ad LACP, Active-Backup, balance-xor/tlb/alb), Team 드라이버, MACVLAN/IPVLAN 가상 NIC, veth pair 네임스페이스(Namespace) 연결, TUN/TAP 유저스페이스 패킷(Packet) I/O까지 실무 관점으로 다룹니다.

전제 조건: 네트워크 스택(Network Stack)디바이스 드라이버 문서를 먼저 읽으세요. NIC 이중화(Bonding)와 가상 인터페이스는 물리 NIC 위에 동작하는 소프트웨어 계층입니다.
일상 비유: Bonding은 여러 개의 차선을 하나로 합쳐 고속도로를 만드는 것과 같고, MACVLAN은 하나의 물리 NIC에 여러 MAC 주소를 부여하여 여러 가상 NIC로 분신술을 쓰는 것과 같습니다. veth는 두 네임스페이스를 연결하는 가상 케이블입니다.

핵심 요약

  • NIC 이중화의 핵심 — Bonding은 여러 물리 NIC를 하나의 논리 인터페이스로 묶어 장애 시 자동 전환(failover)과 대역폭 집계(aggregation)를 제공합니다.
  • 7가지 모드 — Active-Backup(단순 이중화), 802.3ad LACP(스위치 연동 집계), balance-xor/rr/tlb/alb 등 용도별 모드를 선택합니다.
  • Team vs Bonding — Team 드라이버는 Netlink 기반 사용자 공간 제어로 유연성이 높고, Bonding은 커널 내부 완결형으로 안정성과 호환성이 우수합니다.
  • 가상 인터페이스 계층 — MACVLAN(L2 분리), IPVLAN(L3 분리), veth(네임스페이스 연결), TUN/TAP(유저스페이스 I/O)는 컨테이너·가상화 네트워크의 기본 빌딩 블록입니다.
  • SR-IOV 연동 — VF(Virtual Function)와 Bonding을 결합하면 하드웨어 가속과 이중화를 동시에 달성할 수 있습니다.

단계별 이해

  1. Bonding 기초 파악
    Active-Backup과 802.3ad LACP 모드의 동작 원리를 이해하고, bond_main.c의 핵심 자료구조(bonding, slave)를 파악합니다.
  2. 모드별 차이 비교
    7가지 Bonding 모드의 해시 정책, 페일오버 메커니즘, 스위치 요구사항을 비교하여 환경에 맞는 모드를 선택합니다.
  3. 가상 NIC 계층 이해
    MACVLAN/IPVLAN의 L2/L3 분리 방식, veth pair의 네임스페이스 연결 구조, TUN/TAP의 유저스페이스 패킷 경로를 학습합니다.
  4. 실전 구성과 트러블슈팅
    Bonding+VLAN 스택, SR-IOV+Bonding, 대규모 운영 모니터링 등 실무 시나리오를 연습하고 흔한 실수를 파악합니다.

Bonding 개요

Linux Bonding은 여러 물리 NIC를 하나의 논리 인터페이스로 묶어 이중화(redundancy)부하 분산(load balancing)을 제공합니다. bonding 커널 모듈(drivers/net/bonding/)로 구현되며, 7가지 모드를 지원합니다. 커널 2.0부터 존재해온 가장 오래된 NIC 집계 기술로, 현재까지 가장 널리 사용됩니다.

Linux Bonding 아키텍처 Application (TCP/UDP Socket) Kernel Network Stack (routing, netfilter, qdisc) bond0 (net_device) struct bonding — mode / xmit_hash / miimon / lacp_rate bond_xmit_activebackup() | bond_xmit_xor() | bond_3ad_xmit_xor() | bond_xmit_roundrobin() slave: eth0 BOND_LINK_UP | speed:10G slave: eth1 BOND_LINK_UP | speed:10G slave: eth2 BOND_LINK_DOWN | backup Physical Switch (LACP / Static EtherChannel / Standalone) External Network Monitoring MII: miimon=100ms ARP: arp_interval
Bonding 전체 아키텍처: 애플리케이션 → bond0(논리 디바이스) → slave NIC들 → 물리 스위치

Bonding 모드

모드이름설명스위치 설정최대 대역폭(Bandwidth)
0balance-rr라운드 로빈(Round Robin): 패킷을 순서대로 분산필요 (정적 EtherChannel)N × link speed (TX+RX)
1active-backupActive-Standby: 활성 슬레이브 하나만 사용불필요1 × link speed
2balance-xorXOR 해시(Hash): xmit_hash_policy 기반 분산필요 (정적 EtherChannel)N × link speed (TX+RX)
3broadcast모든 슬레이브로 동일 패킷 전송 (fault tolerance용)필요1 × link speed (TX), N × (RX)
4802.3adLACP: IEEE 802.3ad 동적 링크 집계필요 (LACP 지원)N × link speed (TX+RX)
5balance-tlb송신 부하 분산 (Adaptive TLB)불필요N × link speed (TX), 1 × (RX)
6balance-alb송수신 부하 분산 (Adaptive ALB)불필요N × link speed (TX+RX)
모드 선택 가이드:
  • 스위치 설정 불가능active-backup(안정), balance-tlb/alb(성능)
  • 스위치 LACP 지원802.3ad (업계 표준, 권장)
  • 스위치 정적 LAG만 지원balance-xor
  • 장애 내성이 최우선broadcast (모든 슬레이브에 복제 전송)

Bonding 커널 구조

/* drivers/net/bonding/bond_main.c — 핵심 구조체 */
struct bonding {
    struct net_device       *dev;             /* bond 디바이스 */
    struct bond_opt_value   params;

    struct slave __rcu      *curr_active_slave; /* active-backup: 활성 슬레이브 */
    struct slave __rcu      *primary_slave;

    struct list_head        slave_list;       /* 슬레이브 목록 */
    s32                     slave_cnt;

    struct bond_params      params;
    struct ad_bond_info     ad_info;          /* 802.3ad LACP 정보 */

    struct workqueue_struct *wq;
    struct delayed_work     mii_work;         /* MII 모니터링 */
    struct delayed_work     arp_work;         /* ARP 모니터링 */
    struct delayed_work     ad_work;          /* LACP 워크 */
    /* ... */
};

struct slave {
    struct net_device       *dev;             /* 슬레이브 NIC */
    struct bonding          *bond;
    s16                     delay;
    unsigned long           last_link_up;

    u8                      backup:1,         /* 백업 상태 여부 */
                            inactive:1;
    u32                     speed;            /* 링크 속도 */
    u8                      duplex;
    u32                     link;             /* BOND_LINK_UP/DOWN/... */
    /* ... */
};

Bonding 구성

# --- 모드 1: active-backup (가장 일반적) ---
ip link add bond0 type bond mode active-backup miimon 100
ip link set eth0 master bond0
ip link set eth1 master bond0
ip addr add 10.0.0.1/24 dev bond0
ip link set bond0 up

# --- 모드 4: 802.3ad (LACP) ---
ip link add bond0 type bond mode 802.3ad \
    miimon 100 \
    lacp_rate fast \
    xmit_hash_policy layer3+4

ip link set eth0 master bond0
ip link set eth1 master bond0
ip link set bond0 up

# 해시 정책 옵션 (xmit_hash_policy)
#   layer2        : src/dst MAC (기본값)
#   layer2+3      : src/dst MAC + IP
#   layer3+4      : src/dst IP + Port (권장)
#   encap2+3      : 터널 내부 L2+L3
#   encap3+4      : 터널 내부 L3+L4

# Bonding 상태 확인
cat /proc/net/bonding/bond0

# 실시간 슬레이브 전환 (active-backup)
ip link set bond0 type bond active_slave eth1

# ARP 모니터링 (MII 대신)
ip link set bond0 type bond arp_interval 200 arp_ip_target 10.0.0.254
LACP 모범 사례: 802.3ad 모드에서는 lacp_rate fast(1초 간격 LACPDU)와 xmit_hash_policy layer3+4를 권장합니다. 스위치 측에서도 LACP를 활성화하고 동일한 해시 알고리즘을 설정해야 합니다.

Bonding 모드별

각 모드는 bond_xmit_*() 함수와 별도의 수신 처리 로직을 가집니다. 커널 소스에서 모드 선택은 bond_set_mode_ops()가 담당합니다.

Bonding 모드별 TX 패킷 흐름 비교 Mode 0: balance-rr Pkt1→eth0, Pkt2→eth1 Pkt3→eth0, Pkt4→eth1... eth0 eth1 P1,P3,P5 P2,P4,P6 rr_tx_counter++ 순환 주의: TCP 재정렬 문제 packets_per_slave로 조절 Mode 1: active-backup 모든 패킷 → Active만 eth0 A eth1 S ALL pkts (standby) failover → eth1 활성화 GARP 전송 (MAC 이동) 스위치 설정 불필요 Mode 2: balance-xor hash(src,dst) % slaves eth0 eth1 hash=0,2 hash=1,3 동일 플로우=동일 슬레이브 TCP 순서 보장됨 xmit_hash_policy 설정 Mode 4: 802.3ad LACP 협상 + XOR hash eth0 eth1 LACPDU 교환 Actor+Partner 상태 관리 Aggregator 그룹화 업계 표준 (IEEE) Mode 5: balance-tlb (Adaptive TLB) TX: 슬레이브 속도 비례 분산 RX: curr_active_slave로만 수신 eth0 10G eth1 1G TX: ~91% TX: ~9% 스위치 불필요 Mode 6: balance-alb (Adaptive ALB) TX: TLB와 동일 (속도 비례) RX: ARP 협상으로 수신도 분산 eth0 eth1 TX+RX TX+RX 스위치 불필요 Mode 3: broadcast 모든 슬레이브에 동일 패킷 전송 극단적 fault tolerance용 eth0 eth1 COPY COPY 대역폭 이득 없음 커널 함수 매핑 Mode 0: bond_xmit_roundrobin() Mode 1: bond_xmit_activebackup() → curr_active_slave Mode 2: bond_xmit_xor() → bond_xmit_hash() Mode 3: bond_xmit_broadcast() → skb_clone() per slave Mode 4: bond_3ad_xmit_xor() → LACP aggregator Mode 5: bond_xmit_tlb() → rlb_choose_channel() Mode 6: bond_xmit_alb() → TLB + RX load balancing 공통 RX: bond_handle_frame() → RX handler
7가지 Bonding 모드의 TX 패킷 분산 방식과 커널 함수 매핑(Mapping)

Mode 0: balance-rr (라운드 로빈)

bond_xmit_roundrobin()은 내부 카운터 rr_tx_counter를 증가시키며 슬레이브를 순환 선택합니다. 패킷 단위 분산이므로 단일 TCP 연결의 패킷이 서로 다른 경로로 전송되어 수신 측에서 패킷 재정렬(reordering)이 발생할 수 있습니다.

/* drivers/net/bonding/bond_main.c — Round Robin TX */
static netdev_tx_t bond_xmit_roundrobin(struct sk_buff *skb,
                                        struct net_device *bond_dev)
{
    struct bonding *bond = netdev_priv(bond_dev);
    struct slave *slave;

    /* IGMP/MLD는 항상 curr_active_slave로 전송 (브로드캐스트 루프 방지) */
    if (isBroadcastorMulticast(skb))
        return bond_xmit_activebackup(skb, bond_dev);

    slave = bond_xmit_roundrobin_slave_get(bond, skb);
    if (slave)
        bond_dev_queue_xmit(bond, skb, slave->dev);
    else
        bond_tx_drop(bond_dev, skb);

    return NETDEV_TX_OK;
}

/* packets_per_slave: 한 슬레이브에 연속 전송할 패킷 수 (기본 1)
 * 값을 높이면 재정렬은 줄지만 분산 효율이 낮아짐 */
balance-rr 주의사항: TCP는 패킷 순서에 민감합니다. 라운드 로빈은 단일 TCP 플로우의 패킷이 서로 다른 물리 경로를 타므로 지연(Latency) 차이에 의한 재정렬이 빈번합니다. 이는 TCP 성능 저하(불필요한 재전송(Retransmission), cwnd 감소)를 유발합니다. TCP 워크로드에는 balance-xor802.3ad가 더 적합합니다.

Mode 1: active-backup

가장 간단하고 안정적인 모드입니다. curr_active_slave 하나만 TX/RX에 사용하고 나머지는 대기합니다. 활성 슬레이브 장애 시 bond_change_active_slave()가 백업 슬레이브를 활성화하고, GARP(Gratuitous ARP)를 전송하여 스위치 FDB를 갱신합니다.

/* drivers/net/bonding/bond_main.c — Active-Backup TX */
static netdev_tx_t bond_xmit_activebackup(struct sk_buff *skb,
                                            struct net_device *bond_dev)
{
    struct bonding *bond = netdev_priv(bond_dev);
    struct slave *slave;

    slave = rcu_dereference(bond->curr_active_slave);
    if (slave)
        bond_dev_queue_xmit(bond, skb, slave->dev);
    else
        bond_tx_drop(bond_dev, skb);

    return NETDEV_TX_OK;
}

/* 페일오버 시 호출 — GARP로 스위치 FDB 갱신 */
static void bond_change_active_slave(struct bonding *bond,
                                     struct slave *new_active)
{
    struct slave *old_active = rtnl_dereference(bond->curr_active_slave);

    if (old_active == new_active)
        return;

    /* bond MAC 주소를 새 활성 슬레이브에 설정 */
    if (new_active) {
        bond_set_slave_active_flags(new_active, BOND_SLAVE_NOTIFY_NOW);
        /* ... */
    }

    rcu_assign_pointer(bond->curr_active_slave, new_active);

    /* Gratuitous ARP 전송: 스위치가 새 포트로 MAC 학습 */
    if (netif_running(bond->dev)) {
        bond_send_gratuitous_arp(bond);
        bond_send_unsolicited_na(bond);  /* IPv6 NA */
    }
}
primary 옵션: primary eth0을 설정하면 eth0이 복구되었을 때 자동으로 active로 복귀합니다. primary_reselect 옵션으로 복귀 정책을 제어합니다: always(기본: 항상 복귀), better(더 좋은 슬레이브일 때만), failure(현재 active 장애 시만 변경).

Mode 4: 802.3ad (LACP)

IEEE 802.3ad(현재 802.1AX) 표준 기반의 Link Aggregation Control Protocol(LACP)을 사용합니다. 양쪽 장비(호스트+스위치)가 LACPDU를 교환하여 동적으로 링크 그룹을 형성합니다. TX 분산은 bond_3ad_xmit_xor()가 담당하며, xmit_hash_policy에 따라 해시 기반으로 슬레이브를 선택합니다.

/* drivers/net/bonding/bond_3ad.c — LACP 핵심 구조체 */
struct lacpdu {
    u8  subtype;               /* 0x01 = LACP */
    u8  version_number;         /* 0x01 */

    /* Actor (자신) 정보 */
    u8  actor_type;             /* TLV type = 1 */
    u8  actor_length;           /* 20 */
    u16 actor_system_priority;
    u8  actor_system[6];       /* MAC 주소 */
    u16 actor_key;              /* Aggregation Key */
    u16 actor_port_priority;
    u16 actor_port;             /* 포트 번호 */
    u8  actor_state;            /* LACP 상태 비트 */

    /* Partner (상대방) 정보 */
    u8  partner_type;           /* TLV type = 2 */
    u8  partner_length;
    u16 partner_system_priority;
    u8  partner_system[6];
    u16 partner_key;
    u16 partner_port_priority;
    u16 partner_port;
    u8  partner_state;

    u8  collector_type;         /* TLV type = 3 */
    u16 collector_max_delay;
    /* ... padding ... */
};

/* LACP 상태 비트 (actor_state / partner_state) */
#define LACP_STATE_ACTIVITY     0x01  /* Active LACP (vs Passive) */
#define LACP_STATE_TIMEOUT      0x02  /* Short timeout (1s vs 30s) */
#define LACP_STATE_AGGREGATION  0x04  /* Aggregatable */
#define LACP_STATE_SYNCHRONIZATION 0x08 /* In sync with partner */
#define LACP_STATE_COLLECTING   0x10  /* 수집 가능 */
#define LACP_STATE_DISTRIBUTING 0x20  /* 분배 가능 */
#define LACP_STATE_DEFAULTED    0x40  /* 기본값 사용 중 */
#define LACP_STATE_EXPIRED      0x80  /* 만료됨 */

Mode 5: balance-tlb (Adaptive Transmit Load Balancing)

스위치 설정 없이 TX 부하를 분산합니다. 각 슬레이브의 링크 속도에 비례하여 트래픽을 배분합니다. RX는 curr_active_slave로만 수신됩니다. 핵심은 __bond_slave_update_tlb()가 주기적으로 슬레이브별 부하를 측정하고 TX 슬레이브를 재배치(Relocation)하는 것입니다.

/* drivers/net/bonding/bond_alb.c — TLB 슬레이브 선택 */
static struct slave *tlb_choose_channel(
    struct bonding *bond,
    u32 hash_index,
    u32 skb_len)
{
    struct tlb_client_info *hash_entry;

    hash_entry = &(BOND_ALB_INFO(bond).tx_hashtbl[hash_index]);

    /* 이 해시에 이미 할당된 슬레이브가 있고 UP이면 재사용 */
    if (hash_entry->tx_slave && bond_slave_can_tx(hash_entry->tx_slave))
        return hash_entry->tx_slave;

    /* 부하가 가장 낮은 슬레이브 선택 */
    hash_entry->tx_slave = tlb_get_least_loaded_slave(bond);
    return hash_entry->tx_slave;
}

/* 주기적 리밸런싱 — 부하가 편중되면 해시 엔트리를 다른 슬레이브로 이동 */
static void bond_tlb_rebalance(struct bonding *bond)
{
    /* 모든 해시 엔트리 순회 → 과부하 슬레이브의 엔트리를 저부하 슬레이브로 재배치 */
    /* 재배치 주기: lp_interval (기본 1초) */
}

Mode 6: balance-alb (Adaptive Load Balancing)

TLB의 확장으로, TX 분산에 더해 RX도 분산합니다. ARP 응답을 가로채서 각 peer에게 서로 다른 슬레이브의 MAC 주소를 알려주는 방식(rlb_teach_disabled_mac_on_primary())으로 수신 트래픽도 여러 슬레이브에 분산합니다.

/* drivers/net/bonding/bond_alb.c — RLB(Receive Load Balancing) 핵심 */
/* ARP 응답을 가로채서 소스 MAC을 특정 슬레이브의 MAC으로 변경 */
static void rlb_update_client(struct rlb_client_info *client)
{
    /* client->slave에 할당된 슬레이브의 MAC을
     * ARP 응답의 src MAC으로 설정 → peer는 이 MAC으로 패킷 전송
     * → 해당 슬레이브가 직접 RX 수신 */
}

/* RLB 해시 테이블: IP 주소 기반으로 peer별 수신 슬레이브 매핑 */
struct rlb_client_info {
    __be32           ip_src;       /* peer IP */
    __be32           ip_dst;       /* 자신의 IP */
    u8               mac_src[6];   /* peer MAC */
    u8               mac_dst[6];   /* 할당된 슬레이브 MAC */
    struct slave     *slave;       /* 할당된 수신 슬레이브 */
    u32              ntt;          /* need to transmit (ARP 갱신 필요) */
    struct rlb_client_info *next;
    struct rlb_client_info *prev;
};
ALB 제한사항: RLB는 ARP 기반이므로 IPv4에서만 동작합니다. IPv6 환경에서는 TLB 부분만 작동하고 RX 분산은 불가합니다. 또한 VLAN 태그가 있는 ARP 패킷의 경우 일부 스위치에서 MAC 학습 문제가 발생할 수 있습니다.

802.3ad LACP 프로토콜

LACP(Link Aggregation Control Protocol)는 양쪽 장비가 주기적으로 LACPDU(LACP Data Unit)를 교환하여 링크 집계 그룹(LAG)을 동적으로 형성하고 유지합니다. 커널 구현은 drivers/net/bonding/bond_3ad.c에 있습니다.

LACP 상태 머신 및 LACPDU 교환 Linux Host (Actor) INITIALIZE PORT_DISABLED EXPIRED LACP_ACTIVE sync+collect COLLECTING DISTRIBUTING Aggregator actor_system: MAC actor_key: speed+duplex port_count: N is_active: true LACPDU 전송 fast: 매 1초 slow: 매 30초 dst: 01:80:C2:00:00:02 Network Switch (Partner) LACP Engine partner_system partner_key Port Channel (LAG) Port 1 Port 2 Port 3 LACPDU LACPDU Timeout 처리 fast: 3초 (3×1s) slow: 90초 (3×30s) timeout → EXPIRED Partner Timeout 미응답 → 포트 제거
LACP 상태 머신: Initialize → Port Disabled → Expired → Active → Collecting → Distributing

LACP 상태 머신

Linux bonding의 LACP 구현은 IEEE 802.3ad 부속서 43에 정의된 3개의 상태 머신을 구현합니다:

상태 머신역할커널 함수
Receive MachineLACPDU 수신 처리, Partner 정보 갱신ad_rx_machine()
Periodic TX Machine주기적 LACPDU 전송 (fast/slow)ad_periodic_machine()
Mux Machine포트의 Collecting/Distributing 상태 전환ad_mux_machine()
/* drivers/net/bonding/bond_3ad.c — Mux 상태 머신 */
static void ad_mux_machine(struct port *port)
{
    switch (port->sm_mux_state) {
    case AD_MUX_DETACHED:
        /* Aggregator에 연결되면 WAITING으로 전환 */
        if (port->selected == BOND_AD_SELECTED)
            port->sm_mux_state = AD_MUX_WAITING;
        break;

    case AD_MUX_WAITING:
        /* wait_while_timer 만료 후 ATTACHED로 */
        if (port->sm_mux_timer_counter == 0)
            port->sm_mux_state = AD_MUX_ATTACHED;
        break;

    case AD_MUX_ATTACHED:
        /* Partner가 sync+collecting이면 COLLECTING_DISTRIBUTING */
        if (port->partner_oper.port_state & LACP_STATE_SYNCHRONIZATION)
            port->sm_mux_state = AD_MUX_COLLECTING_DISTRIBUTING;
        break;

    case AD_MUX_COLLECTING_DISTRIBUTING:
        /* 정상 동작 상태 — TX/RX 모두 가능 */
        if (!(port->partner_oper.port_state & LACP_STATE_SYNCHRONIZATION))
            port->sm_mux_state = AD_MUX_ATTACHED;
        break;
    }
}

/* Periodic TX Machine — LACPDU 전송 주기 결정 */
static void ad_periodic_machine(struct port *port)
{
    /* lacp_rate fast  → 매 1초 (AD_FAST_PERIODIC_TIME)
     * lacp_rate slow  → 매 30초 (AD_SLOW_PERIODIC_TIME)
     * Partner가 Activity=0이고 자신도 Activity=0이면 전송 안 함 */

    if (port->sm_periodic_timer_counter == 0) {
        port->ntt = true;  /* Need To Transmit */
        /* ad_lacpdu_send()로 LACPDU 전송 */
    }
}

Aggregator와 Key 개념

Aggregation Key는 동일한 LAG에 속할 수 있는 포트를 결정하는 값입니다. 커널은 속도(speed)와 듀플렉스(duplex)를 조합하여 Key를 생성합니다. Key가 동일한 포트만 같은 Aggregator에 배치됩니다.

/* drivers/net/bonding/bond_3ad.c — Key 생성 */
static u16 __get_link_speed(struct port *port)
{
    /* ethtool로 가져온 속도를 LACP 속도 상수로 변환 */
    /* 10M=1, 100M=2, 1G=3, 2.5G=4, 5G=5, 10G=6, ... */
}

/* ad_key = (duplex << 8) | speed
 * 같은 speed+duplex인 포트만 같은 Aggregator에 소속 */
static u32 __get_agg_selection_mode(struct port *port)
{
    struct bonding *bond = __get_bond_by_port(port);
    /* ad_select 옵션:
     * stable   — 가장 많은 포트를 가진 aggregator 우선 (기본)
     * bandwidth — 총 대역폭이 가장 큰 aggregator 우선
     * count    — 포트 수가 가장 많은 aggregator 우선 */
    return bond->params.ad_select;
}
LACP fast vs slow:
  • lacp_rate fast: LACPDU를 매 1초 전송, 타임아웃 3초. 장애 감지가 빠르지만 CPU/대역폭 소비 약간 증가
  • lacp_rate slow: LACPDU를 매 30초 전송, 타임아웃 90초. 리소스 절약되지만 장애 감지 느림
  • 프로덕션 환경에서는 fast를 권장 — 스위치 장애 시 3초 이내 감지 가능

장애 감지와 페일오버

Bonding은 두 가지 링크 모니터링 방식을 제공합니다: MII 모니터링(L1/L2 수준)과 ARP 모니터링(L3 수준). 두 방식은 동시 사용이 불가합니다.

Bonding 페일오버 시퀀스 (active-backup) 시간 정상 상태 eth0=ACTIVE (curr_active_slave), eth1=BACKUP | MII miimon=100ms 주기 폴링 | BOND_LINK_UP T+0: 링크 장애 감지 bond_mii_monitor() → netif_carrier_ok(eth0)=false → eth0 상태: BOND_LINK_FAIL downdelay 타이머 시작 (기본 0ms → 즉시 진행, 권장: 200ms로 flapping 방지) T+downdelay: 슬레이브 비활성화 eth0: BOND_LINK_FAIL → BOND_LINK_DOWN → bond_set_slave_inactive_flags() bond_select_active_slave() → eth1을 새 curr_active_slave로 선택 T+downdelay+1tick: 페일오버 완료 bond_change_active_slave(eth1) → bond MAC 주소를 eth1에 설정 bond_send_gratuitous_arp() → GARP 전송 (num_grat_arp회) → 스위치 FDB 갱신 T+복구: eth0 링크 복구 BOND_LINK_BACK → updelay 대기 → BOND_LINK_UP | primary_reselect 정책에 따라 active 복귀 여부 결정
MII 모니터링 기반 active-backup 페일오버 시퀀스

MII 모니터링

MII(Media Independent Interface) 모니터링은 NIC의 물리적 링크 상태(carrier)를 주기적으로 확인합니다. miimon 간격(밀리초)마다 bond_mii_monitor()가 실행됩니다.

/* drivers/net/bonding/bond_main.c — MII 모니터링 */
static void bond_mii_monitor(struct work_struct *work)
{
    struct bonding *bond = container_of(work, struct bonding,
                                         mii_work.work);
    struct slave *slave;

    bond_for_each_slave_rcu(bond, slave, iter) {
        u8 link = bond_check_dev_link(bond, slave->dev);

        switch (slave->link) {
        case BOND_LINK_UP:
            if (link != BMSR_LSTATUS) {
                /* 링크 다운 감지 → FAIL 상태로 전환 */
                slave->link = BOND_LINK_FAIL;
                slave->delay = bond->params.downdelay;
            }
            break;

        case BOND_LINK_FAIL:
            if (link == BMSR_LSTATUS) {
                /* 복구됨 → 다시 UP */
                slave->link = BOND_LINK_UP;
            } else if (slave->delay <= 0) {
                /* downdelay 만료 → 진짜 DOWN */
                slave->link = BOND_LINK_DOWN;
                bond_set_slave_inactive_flags(slave, ...);
            } else {
                slave->delay--;
            }
            break;

        case BOND_LINK_DOWN:
            if (link == BMSR_LSTATUS) {
                slave->link = BOND_LINK_BACK;
                slave->delay = bond->params.updelay;
            }
            break;

        case BOND_LINK_BACK:
            if (link != BMSR_LSTATUS) {
                slave->link = BOND_LINK_DOWN;
            } else if (slave->delay <= 0) {
                slave->link = BOND_LINK_UP;
                bond_set_slave_active_flags(slave, ...);
            } else {
                slave->delay--;
            }
            break;
        }
    }

    /* 활성 슬레이브가 없으면 새로 선택 */
    if (bond_should_change_active(bond))
        bond_select_active_slave(bond);

    /* 다음 모니터링 예약 */
    queue_delayed_work(bond->wq, &bond->mii_work,
                       msecs_to_jiffies(bond->params.miimon));
}

ARP 모니터링

ARP 모니터링은 지정된 IP(arp_ip_target)에 ARP 요청을 보내고 응답 수신 여부로 링크 상태를 판단합니다. MII가 감지하지 못하는 스위치 장애, VLAN 미스매치, 상위 경로 장애를 감지할 수 있습니다.

# ARP 모니터링 설정 (MII와 동시 사용 불가)
ip link add bond0 type bond mode active-backup \
    arp_interval 200 \
    arp_ip_target 10.0.0.1,10.0.0.254 \
    arp_validate active \
    arp_all_targets any

# arp_validate 옵션:
#   none    — ARP 응답 검증 안 함 (기본)
#   active  — 활성 슬레이브의 ARP 응답만 검증
#   backup  — 백업 슬레이브의 ARP 응답만 검증
#   all     — 모든 슬레이브의 ARP 응답 검증
#   filter  — ARP 타겟 이외의 소스에서 온 ARP도 수신 카운트
#   filter_active — filter + active 조합
#   filter_backup — filter + backup 조합

# arp_all_targets 옵션:
#   any  — 하나라도 응답하면 링크 UP (기본)
#   all  — 모든 타겟이 응답해야 링크 UP
MII vs ARP 모니터링 비교:
항목MIIARP
감지 계층L1/L2 (물리 링크)L3 (IP 연결성)
감지 속도빠름 (miimon ms)느림 (arp_interval × miss_max)
스위치 장애 감지불가 (물리 포트 UP 유지)가능
네트워크 부하없음ARP 패킷 발생
VLAN 미스매치 감지불가가능
권장 사용처직접 연결, 일반 환경다중 홉, 복잡한 토폴로지(Topology)
페일오버 시간 최적화:
  • miimon=100 + downdelay=0: 최대 100ms 이내 감지 (최소값)
  • miimon=100 + downdelay=200: 200~300ms (flapping 방지 추천)
  • updelay=200: 복구 시 200ms 대기 (스위치 STP convergence 대기)
  • 802.3ad + lacp_rate fast: LACP 타임아웃 3초 이내 감지
  • num_grat_arp=2: GARP를 2회 전송하여 스위치 FDB 갱신 신뢰성 향상

해시 정책과 부하 분산 알고리즘

xmit_hash_policy는 Mode 2(balance-xor), Mode 4(802.3ad)에서 패킷을 어떤 슬레이브로 보낼지 결정하는 해시 함수를 선택합니다. 해시 결과를 슬레이브 수로 모듈러 연산하여 대상 슬레이브를 결정합니다.

xmit_hash_policy 비교 layer2 (기본값) hash = src_mac XOR dst_mac 동일 MAC 쌍 → 항상 같은 슬레이브 장점: 단순, 비IP 트래픽 지원 단점: 게이트웨이 1개면 분산 안됨 사용: L2 only 환경 layer2+3 hash = src_mac XOR dst_mac XOR src_ip XOR dst_ip 장점: IP별 분산, VLAN 호환 단점: 동일 IP쌍은 분산 안됨 사용: 다수 클라이언트 환경 layer3+4 (권장) hash = src_ip XOR dst_ip XOR src_port XOR dst_port 장점: 플로우별 최적 분산 단점: 비TCP/UDP는 L3로 fallback 사용: 대부분의 프로덕션 환경 encap2+3 / encap3+4 (터널 환경) VXLAN/GRE/GENEVE 내부 패킷의 L2+L3 또는 L3+L4로 해시 skb_flow_dissect()으로 터널 헤더 파싱 → 내부 필드 추출 vlan+srcmac (커널 5.0+) hash = vlan_id XOR src_mac (VLAN 환경 특화) 브리지 환경에서 VLAN별 분산에 유리 커널 해시 함수: bond_xmit_hash() bond_xmit_hash() → bond_eth_hash() [L2] | bond_l23_hash() [L2+3] | bond_l34_hash() [L3+4] | bond_encap_hash() [encap] 최종: slave_index = hash % slave_count → bond_dev_queue_xmit(bond, skb, slave[slave_index]->dev)
xmit_hash_policy 옵션별 해시 입력 필드와 분산 특성 비교
/* drivers/net/bonding/bond_main.c — 해시 함수 구현 */
static u32 bond_xmit_hash(struct bonding *bond,
                          struct sk_buff *skb)
{
    struct flow_keys flow;
    u32 hash;

    switch (bond->params.xmit_hash_policy) {
    case BOND_XMIT_POLICY_LAYER2:
        return bond_eth_hash(skb);

    case BOND_XMIT_POLICY_LAYER23:
        hash = bond_eth_hash(skb);
        if (skb_flow_dissect_flow_keys(skb, &flow, 0))
            return bond_l23_hash(hash, &flow);
        return hash;

    case BOND_XMIT_POLICY_LAYER34:
        if (skb_flow_dissect_flow_keys(skb, &flow, 0))
            return bond_l34_hash(skb, &flow);
        return bond_eth_hash(skb);  /* fallback to L2 */

    case BOND_XMIT_POLICY_ENCAP23:
    case BOND_XMIT_POLICY_ENCAP34:
        return bond_encap_hash(skb, bond->params.xmit_hash_policy);

    default:
        return 0;
    }
}

/* L3+4 해시: src/dst IP + src/dst Port */
static inline u32 bond_l34_hash(struct sk_buff *skb,
                                 struct flow_keys *flow)
{
    u32 hash = flow_get_u32_src(flow) ^ flow_get_u32_dst(flow);
    hash ^= (u32)flow->ports.src ^ ((u32)flow->ports.dst << 16);
    hash ^= hash >> 16;
    hash ^= hash >> 8;
    return hash >> 1;  /* 최종 해시 → % slave_cnt로 슬레이브 선택 */
}
해시 정책의 함정: 해시 기반 분산은 플로우(flow) 단위입니다. 단일 대용량 플로우(예: iperf 단일 TCP 연결)는 아무리 좋은 해시 정책을 써도 하나의 슬레이브만 사용합니다. 실질적 대역폭 향상을 보려면 다수의 동시 연결이 필요합니다. 단일 연결 대역폭이 중요하면 balance-rr이 유일한 옵션이지만, TCP 재정렬 부작용을 감수해야 합니다.

Bonding 흔한 실수와 트러블슈팅

증상원인해결
802.3ad에서 한 슬레이브만 사용됨스위치 LACP 미설정 또는 해시 정책 문제스위치에서 LACP 활성화, xmit_hash_policy layer3+4 설정
페일오버 후 통신 두절 10~30초스위치 FDB 갱신 지연, GARP 미전송num_grat_arp=3, num_unsol_na=3 설정
active-backup에서 양쪽 모두 active스위치 포트 미러링 또는 설정 오류cat /proc/net/bonding/bond0으로 상태 확인
balance-rr에서 TCP 성능 저하패킷 재정렬로 인한 재전송balance-xor 또는 802.3ad로 변경
LACP에서 슬레이브가 aggregator에 안 들어감속도/듀플렉스 불일치 (Key 다름)모든 슬레이브 동일 속도/듀플렉스 확인
링크 flapping (UP↔DOWN 반복)케이블 불량, downdelay 미설정downdelay=200, updelay=200 설정
bond0에 IP 할당 후 통신 불가슬레이브에 IP가 남아있음슬레이브의 IP 제거: ip addr flush dev eth0
ALB/TLB에서 RX가 분산 안 됨TLB는 RX 분산 없음, ALB는 IPv4만 지원RX 분산 필요시 ALB + IPv4 사용, 또는 802.3ad
NetworkManager가 bond를 방해NM이 슬레이브를 개별 관리NM으로 bond 구성하거나 NM_CONTROLLED=no
# Bonding 디버깅 명령어 모음

# 1. 기본 상태 확인
cat /proc/net/bonding/bond0

# 2. 슬레이브별 상세 정보
ip -d link show bond0
ip -d link show eth0
ip -d link show eth1

# 3. LACP 상태 확인 (802.3ad)
cat /proc/net/bonding/bond0 | grep -A 20 "802.3ad"

# 4. 해시 분산 확인 — 각 슬레이브의 TX/RX 패킷 수 비교
ip -s link show eth0 | grep -A 1 "TX:"
ip -s link show eth1 | grep -A 1 "TX:"

# 5. 커널 로그에서 bonding 이벤트 확인
dmesg | grep -i bond
journalctl -k | grep -i bond

# 6. LACPDU 패킷 캡처
tcpdump -i eth0 -nn ether proto 0x8809 -v

# 7. 강제 페일오버 테스트
ip link set eth0 down   # 활성 슬레이브 강제 다운
cat /proc/net/bonding/bond0 | grep "Currently Active"
ip link set eth0 up     # 복구

# 8. sysfs 파라미터 확인/변경
cat /sys/class/net/bond0/bonding/mode
cat /sys/class/net/bond0/bonding/xmit_hash_policy
cat /sys/class/net/bond0/bonding/lacp_rate
cat /sys/class/net/bond0/bonding/ad_actor_sys_prio
echo "layer3+4" > /sys/class/net/bond0/bonding/xmit_hash_policy

Team 드라이버

Team은 bonding의 현대적 대안으로, Netlink 기반 사용자 공간(User Space) 제어와 모듈러 아키텍처를 제공합니다. 커널 모듈(drivers/net/team/)은 최소한의 프레임워크만 제공하고, 실제 정책 로직은 teamd 데몬과 libteam 라이브러리에서 구현됩니다. RHEL/CentOS 7+에서 bonding의 권장 대안으로 소개되었습니다.

Team 드라이버 아키텍처 User Space teamd Runner: activebackup lacp, loadbalance, ... libteam Netlink 통신 옵션 관리 teamdctl CLI 제어 도구 상태 조회/변경 D-Bus NM 통합 JSON 설정 파일 Netlink (TEAM_GENL) Kernel Space team_core.c (team module) struct team + team_mode_ops team_mode_* roundrobin, broadcast, random, activebackup net_device team0 (가상 NIC) eth0 (port) eth1 (port) eth2 (port) Runner = 정책 모듈 (커널 or 유저스페이스) activebackup, lacp, loadbalance, broadcast, random
Team 아키텍처: teamd(유저스페이스) ↔ Netlink ↔ team_core(커널) ↔ 포트(물리 NIC)
항목BondingTeam
설정 인터페이스sysfs / module paramsNetlink / D-Bus / JSON
런타임 재설정제한적완전 지원
사용자 공간 제어없음teamd 데몬
TX 해시 확장고정 5가지사용자 정의 가능
LACP 구현커널 내teamd (libteam)
NetworkManager지원지원 (더 나은 통합)
# Team 인터페이스 생성 (ip 명령)
ip link add team0 type team

# teamd를 이용한 active-backup 구성
teamd -d -t team0 -c '{
    "runner": {"name": "activebackup"},
    "link_watch": {"name": "ethtool"},
    "ports": {
        "eth0": {"prio": 100},
        "eth1": {"prio": 50}
    }
}'

# teamd를 이용한 LACP 구성
teamd -d -t team0 -c '{
    "runner": {
        "name": "lacp",
        "active": true,
        "fast_rate": true,
        "tx_hash": ["eth", "ipv4", "ipv6", "tcp", "udp"]
    },
    "link_watch": {"name": "ethtool"},
    "ports": {
        "eth0": {},
        "eth1": {}
    }
}'

# 상태 확인
teamdctl team0 state
teamdctl team0 state view

# 런타임 포트 추가/제거
teamdctl team0 port add eth2
teamdctl team0 port remove eth2
/* drivers/net/team/team_core.c — Team 핵심 구조체 */
struct team {
    struct net_device       *dev;
    struct team_pcpu_stats __percpu *pcpu_stats;

    const struct team_mode  *mode;            /* runner 모드 */
    struct list_head        port_list;        /* 포트 목록 */
    unsigned int            port_count;

    struct list_head        option_list;      /* 설정 옵션 */
    struct list_head        option_inst_list;

    const struct team_mode_ops *ops;
    bool                    user_carrier_enabled;
    /* ... */
};

/* Team mode 인터페이스 */
struct team_mode_ops {
    int  (*init)(struct team *team);
    void (*exit)(struct team *team);
    bool (*transmit)(struct team *team, struct sk_buff *skb);
    rx_handler_result_t (*receive)(struct team *team,
                                    struct team_port *port,
                                    struct sk_buff *skb);
    /* ... */
};

MACVLAN / IPVLAN

MACVLAN은 하나의 물리 NIC 위에 각기 다른 MAC 주소를 가진 가상 인터페이스를 생성합니다. IPVLAN은 동일한 MAC 주소를 공유하면서 IP 주소로 트래픽을 구분합니다. 둘 다 컨테이너(Container)/VM 네트워킹에서 브리지(Bridge) 없이 고성능 네트워크 연결을 제공하는 경량 가상 NIC입니다.

MACVLAN 모드별 트래픽 흐름 비교 bridge 모드 macvlan0 macvlan1 직접 eth0 (lower device) External Network MACVLAN간 직접 통신 O vepa 모드 macvlan0 macvlan1 eth0 (lower device) Switch (hairpin) 모든 트래픽 스위치 경유 private 모드 macvlan0 macvlan1 X eth0 (lower device) External Network MACVLAN간 통신 완전 차단 IPVLAN L2 / L3 / L3S 모드 L2 MAC 공유 ARP/NDP 처리 L3 라우팅 기반 no netfilter L3S L3 + conntrack + netfilter 모든 IPVLAN 인터페이스가 동일 MAC 주소 공유 → 스위치 MAC 테이블 부담 없음 → 클라우드 환경(MAC 제한)에 적합 L3S: Kubernetes에서 kube-proxy 대체 시 사용 MACVLAN vs IPVLAN 결정 트리 MAC 주소 제한 있는 환경? YES → IPVLAN (MAC 공유) NO → 계속... 고유 MAC 필요? (DHCP, 802.1X) YES → MACVLAN NO → 계속... Netfilter/conntrack 필요? YES → IPVLAN L3S
MACVLAN 3가지 모드의 트래픽 흐름과 IPVLAN L2/L3/L3S 비교

MACVLAN 모드

모드MACVLAN 간 통신외부 통신설명
bridgeO (직접)OMACVLAN 간 내부 브리징 지원
vepaO (외부 스위치 경유)O모든 트래픽이 외부 스위치를 통과
privateXOMACVLAN 간 완전 격리(Isolation)
passthru-O물리 NIC의 MAC을 직접 사용 (단일)
source필터 기반O허용된 소스 MAC만 수신
# MACVLAN 생성 (bridge 모드)
ip link add macvlan0 link eth0 type macvlan mode bridge
ip addr add 192.168.1.100/24 dev macvlan0
ip link set macvlan0 up

# MACVLAN private 모드 (컨테이너 격리에 적합)
ip link add macvlan1 link eth0 type macvlan mode private

# IPVLAN L2 모드 (기본)
ip link add ipvlan0 link eth0 type ipvlan mode l2
ip addr add 192.168.1.200/24 dev ipvlan0
ip link set ipvlan0 up

# IPVLAN L3 모드 (라우팅 기반)
ip link add ipvlan1 link eth0 type ipvlan mode l3

# IPVLAN L3S 모드 (L3 + Netfilter/conntrack 통합)
ip link add ipvlan2 link eth0 type ipvlan mode l3s
/* drivers/net/macvlan.c — MACVLAN 패킷 수신 */
static rx_handler_result_t macvlan_handle_frame(
    struct sk_buff **pskb)
{
    struct macvlan_port *port;
    struct macvlan_dev *vlan;
    const struct ethhdr *eth = eth_hdr(skb);

    port = macvlan_port_get_rcu(skb->dev);

    /* 목적지 MAC으로 MACVLAN 디바이스 탐색 */
    if (macvlan_passthru(port))
        vlan = list_first_or_null_rcu(&port->vlans, ...);
    else
        vlan = macvlan_hash_lookup(port, eth->h_dest);

    if (!vlan)
        return RX_HANDLER_PASS;  /* 물리 디바이스로 전달 */

    /* MACVLAN 디바이스로 패킷 전달 */
    skb->dev = vlan->dev;
    skb->pkt_type = PACKET_HOST;
    netif_rx(skb);
    return RX_HANDLER_CONSUMED;
}
MACVLAN vs IPVLAN 선택 기준:
  • MACVLAN: 각 컨테이너에 고유 MAC이 필요한 경우, 802.1Q VLAN과 결합할 때
  • IPVLAN: MAC 주소 수 제한이 있는 환경 (일부 클라우드), Netfilter 규칙 공유 시 (L3S 모드)

veth pair (가상 이더넷 쌍)

veth는 항상 쌍(pair)으로 생성되는 가상 이더넷 디바이스입니다. 한쪽에 전송된 패킷은 반대쪽에서 수신됩니다. Docker, Kubernetes, LXC 등 컨테이너 네트워킹의 기본 빌딩 블록이며, 네트워크 네임스페이스 간 통신의 핵심입니다.

veth pair: 네임스페이스 연결과 컨테이너 네트워킹 Host Network Namespace (default) Network Stack (routing, iptables, tc qdisc) br0 (Linux Bridge) eth0 veth0 veth2 XDP on veth (커널 5.0+) veth_xdp_rcv() → peer에서 수신 시 XDP 프로그램 실행 Container Network Namespace (ns1) Network Stack (독립된 routing, iptables) eth0 (veth1) 10.0.0.2/24 Container ns2 eth0 (veth3) 10.0.0.3/24 veth pair veth pair GRO/TSO 지원 (veth_features) → 고성능 가상 링크
veth pair가 호스트 네임스페이스(br0)와 컨테이너 네임스페이스를 연결하는 전형적 구조
# veth 쌍 생성
ip link add veth0 type veth peer name veth1

# 한쪽을 네트워크 네임스페이스로 이동
ip netns add ns1
ip link set veth1 netns ns1

# 호스트 측 설정
ip addr add 10.0.0.1/24 dev veth0
ip link set veth0 up

# 네임스페이스 측 설정
ip netns exec ns1 ip addr add 10.0.0.2/24 dev veth1
ip netns exec ns1 ip link set veth1 up
ip netns exec ns1 ip link set lo up

# 브리지에 veth 연결 (컨테이너 → 외부 통신)
ip link set veth0 master br0
/* drivers/net/veth.c — veth 패킷 전송 */
static netdev_tx_t veth_xmit(struct sk_buff *skb,
                              struct net_device *dev)
{
    struct veth_priv *rcv_priv, *priv = netdev_priv(dev);
    struct net_device *rcv;

    /* peer 디바이스 참조 획득 */
    rcv = rcu_dereference(priv->peer);
    if (unlikely(!rcv)) {
        kfree_skb(skb);
        goto drop;
    }

    /* XDP 프로그램이 있으면 실행 */
    if (likely(veth_forward_skb(rcv, skb, rq, rcv_xdp) == NET_RX_SUCCESS)) {
        /* 패킷을 peer 디바이스의 수신 큐에 전달 */
        struct pcpu_lstats *stats = this_cpu_ptr(dev->lstats);
        u64_stats_update_begin(&stats->syncp);
        stats->bytes += length;
        stats->packets++;
        u64_stats_update_end(&stats->syncp);
    }
    return NETDEV_TX_OK;
}
성능 최적화: veth는 기본적으로 XDP 를 지원합니다. veth_xdp_rcv() 경로를 통해 peer로부터 수신된 패킷에 XDP 프로그램을 적용할 수 있어, 컨테이너 네트워킹에서 고성능 패킷 처리가 가능합니다. 또한 GRO(Generic Receive Offload)도 지원합니다.

TUN/TAP

TUN은 L3(IP) 수준, TAP은 L2(이더넷) 수준에서 유저스페이스와 커널 간 패킷 I/O를 제공합니다. VPN(OpenVPN, WireGuard), 가상화(QEMU/KVM), 네트워크 시뮬레이션에 사용됩니다.

항목TUNTAP
계층L3 (IP 패킷)L2 (이더넷 프레임)
용도라우팅(Routing) 기반 VPN브리징, VM NIC
디바이스 파일/dev/net/tun
생성 플래그IFF_TUNIFF_TAP

TUN/TAP 아키텍처

TUN/TAP 디바이스는 drivers/net/tun.c에서 구현됩니다. 유저스페이스 프로세스(Process)가 /dev/net/tun 캐릭터 디바이스를 열고 TUNSETIFF ioctl로 가상 인터페이스를 생성하면, 커널은 tun_struct와 연결된 net_device를 등록합니다. 이후 유저스페이스는 파일 디스크립터(File Descriptor)의 read()/write()를 통해 커널 네트워크 스택과 직접 패킷을 교환합니다.

TUN/TAP 데이터 흐름 User Space Application (VPN, QEMU) fd = open + ioctl write() read() /dev/net/tun (misc device, minor 200) tun_get_user() tun_do_read() tun_struct (net_device) tun_file[] (per-queue) sk_receive_queue netif_rx() Kernel Network Stack (routing, netfilter, bridge ...) Physical NIC / Other Virtual Devices
TUN/TAP 디바이스의 데이터 흐름: 유저스페이스 ↔ /dev/net/tun ↔ tun_struct ↔ 커널 네트워크 스택

커널 자료 구조

TUN/TAP의 핵심 자료 구조는 tun_struct(디바이스 전체 관리)와 tun_file(각 큐/fd 관리)입니다. 멀티큐 지원을 위해 하나의 tun_struct에 여러 tun_file이 연결됩니다.

/* drivers/net/tun.c — 디바이스 전체를 관리하는 구조체 */
struct tun_struct {
    struct tun_file __rcu  *tfiles[MAX_TAP_QUEUES]; /* 큐별 tun_file 배열 */
    unsigned int            numqueues;             /* 활성 큐 수 */
    unsigned int            flags;                 /* IFF_TUN / IFF_TAP 등 */
    kuid_t                  owner;                 /* TUNSETOWNER로 설정 */
    kgid_t                  group;                 /* TUNSETGROUP으로 설정 */

    struct net_device       *dev;                  /* 연결된 net_device */
    struct net_device_stats stats;                 /* 패킷/바이트 통계 */

    struct tap_filter       txflt;                 /* TX 필터 (MAC 기반) */
    int                     sndbuf;                /* 소켓 송신 버퍼 크기 */
    int                     vnet_hdr_sz;           /* virtio-net 헤더 크기 */
};
/* drivers/net/tun.c — 큐(파일 디스크립터)별 관리 구조체 */
struct tun_file {
    struct tun_struct __rcu *tun;       /* 소속 tun_struct 역참조 */
    struct socket           socket;     /* vhost-net 연동용 소켓 */
    struct tun_page         tpage;      /* XDP용 페이지 풀 */
    struct xdp_rxq_info     xdp_rxq;    /* XDP 수신 큐 정보 */
    struct napi_struct      napi;       /* NAPI 폴링 (napi_gro_receive) */
    int                     queue_index; /* 멀티큐 인덱스 */
    struct sk_buff_head     sk_receive_queue; /* 수신 패킷 큐 (TX→유저) */
};
멀티큐 아키텍처: MAX_TAP_QUEUES는 기본 256입니다. QEMU는 vCPU당 하나의 큐를 할당하여 병렬 패킷 처리를 수행합니다. 각 큐는 독립적인 tun_filesk_receive_queue를 가지므로 락 경합(Contention) 없이 동시 I/O가 가능합니다.

주요 플래그 및 ioctl

/dev/net/tun을 열고 TUNSETIFF ioctl을 호출할 때 ifreq.ifr_flags에 설정하는 플래그로 디바이스 동작을 제어합니다.

플래그설명
IFF_TUN0x0001L3 (IP) TUN 디바이스 생성
IFF_TAP0x0002L2 (Ethernet) TAP 디바이스 생성
IFF_NO_PI0x1000PI(Packet Info) 헤더 생략 — 순수 패킷만 전달
IFF_VNET_HDR0x4000virtio-net 헤더 포함 (GSO/checksum offload)
IFF_MULTI_QUEUE0x0100멀티큐 모드 활성화
IFF_PERSIST0x0800fd 닫아도 디바이스 유지
IFF_NOFILTER0x1000패킷 필터 비활성화
PI(Packet Info) 헤더: IFF_NO_PI를 설정하지 않으면 각 패킷 앞에 4바이트 struct tun_pi가 붙습니다 — flags(2바이트, 예: TUN_PKT_STRIP)와 proto(2바이트, ETH_P_IP 등). 대부분의 애플리케이션은 IFF_NO_PI를 설정하여 순수 패킷만 교환합니다.

디바이스 생성 후 추가 설정을 위한 ioctl 명령입니다.

ioctl 명령인자설명
TUNSETIFFstruct ifreq *디바이스 생성/연결 (이름 + 플래그 설정)
TUNSETOWNERuid_t디바이스 소유자 UID 설정
TUNSETGROUPgid_t디바이스 그룹 GID 설정
TUNSETPERSISTint0: 비영구, 1: fd 닫아도 디바이스 유지
TUNSETOFFLOADunsigned longoffload 기능 설정 (TUN_F_CSUM, TUN_F_TSO4 등)
TUNSETVNETHDRSZintvirtio-net 헤더 크기 설정 (기본 10 또는 12)
/* 유저스페이스에서 TUN/TAP 디바이스 생성 */
#include <linux/if_tun.h>
#include <sys/ioctl.h>

int tun_alloc(char *dev, int flags)
{
    struct ifreq ifr;
    int fd, err;

    /* /dev/net/tun 열기 */
    fd = open("/dev/net/tun", O_RDWR);
    if (fd < 0)
        return fd;

    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = flags;   /* IFF_TUN 또는 IFF_TAP | IFF_NO_PI */

    if (*dev)
        strncpy(ifr.ifr_name, dev, IFNAMSIZ);

    /* TUNSETIFF ioctl로 디바이스 생성 */
    err = ioctl(fd, TUNSETIFF, (void *)&ifr);
    if (err < 0) {
        close(fd);
        return err;
    }

    strcpy(dev, ifr.ifr_name);
    return fd;  /* read()/write()로 패킷 I/O */
}

/* 사용 예 */
char tun_name[IFNAMSIZ] = "tun0";
int tun_fd = tun_alloc(tun_name, IFF_TUN | IFF_NO_PI);

/* 패킷 읽기 (커널 → 유저스페이스) */
char buf[2048];
int nread = read(tun_fd, buf, sizeof(buf));

/* 패킷 쓰기 (유저스페이스 → 커널) */
write(tun_fd, packet, pkt_len);
# ip 명령으로 TUN/TAP 생성
ip tuntap add dev tun0 mode tun user $(whoami)
ip tuntap add dev tap0 mode tap user $(whoami)

# 멀티큐 TUN/TAP (QEMU vhost-net에 활용)
ip tuntap add dev tap0 mode tap multi_queue vnet_hdr

# 삭제
ip tuntap del dev tun0 mode tun

패킷 흐름

TUN/TAP의 패킷 흐름은 크게 세 가지 경로로 나뉩니다: 유저스페이스 → 커널(Write), 커널 → 유저스페이스(Read), 커널 네트워크 스택 → TUN/TAP(TX).

/* drivers/net/tun.c — Write 경로: 유저가 write() 시 커널로 패킷 주입 */
static ssize_t tun_chr_write_iter(
    struct kiocb *iocb,
    struct iov_iter *from)
{
    struct tun_file *tfile = file->private_data;
    struct tun_struct *tun = tun_get(tfile);

    /* iov_iter → sk_buff 변환 후 네트워크 스택 진입 */
    result = tun_get_user(tun, tfile, ...);
    return result;
}

/* tun_get_user() 세부 로직:
 * 1. IFF_VNET_HDR → virtio_net_hdr 파싱 (GSO 메타데이터)
 * 2. !IFF_NO_PI   → struct tun_pi 파싱 (proto 추출)
 * 3. alloc_skb() + skb_copy_datagram_from_iter()
 * 4. IFF_TUN      → skb->protocol = tun_pi.proto (또는 IP 버전 감지)
 *    IFF_TAP      → eth_type_trans() 호출
 * 5. netif_rx() → 커널 네트워크 스택 진입
 */
/* drivers/net/tun.c — Read 경로: 커널에서 유저스페이스로 패킷 전달 */
static ssize_t tun_do_read(
    struct tun_struct *tun,
    struct tun_file *tfile,
    struct iov_iter *to)
{
    struct sk_buff *skb;

    /* sk_receive_queue에서 패킷 대기/dequeue */
    skb = skb_dequeue(&tfile->sk_receive_queue);

    /* !IFF_NO_PI → tun_pi 헤더 먼저 복사 */
    /* IFF_VNET_HDR → virtio_net_hdr 먼저 복사 */
    /* skb_copy_datagram_iter() → 유저 버퍼에 패킷 복사 */
    ret = tun_put_user(tun, tfile, skb, to);

    consume_skb(skb);
    return ret;
}
/* drivers/net/tun.c — TX 경로: 네트워크 스택이 TUN/TAP으로 패킷 전송 */
static netdev_tx_t tun_net_xmit(
    struct sk_buff *skb,
    struct net_device *dev)
{
    struct tun_struct *tun = netdev_priv(dev);
    struct tun_file *tfile;

    /* 멀티큐: skb의 queue_mapping으로 tfile 선택 */
    tfile = rcu_dereference(tun->tfiles[skb_get_queue_mapping(skb)]);

    /* sndbuf 초과 시 드롭 */
    if (skb_queue_len(&tfile->sk_receive_queue) >= dev->tx_queue_len)
        goto drop;

    /* sk_receive_queue에 enqueue → 유저스페이스 read() 대기 깨움 */
    skb_queue_tail(&tfile->sk_receive_queue, skb);
    wake_up_interruptible_poll(&tfile->socket.wq.wait, ...);

    return NETDEV_TX_OK;
}
비동기 I/O: TUN/TAP fd는 poll()/epoll()/select()를 완벽히 지원합니다. POLLINsk_receive_queue에 패킷이 있을 때, POLLOUT은 sndbuf 여유가 있을 때 발생합니다. 고성능 VPN/가상화 애플리케이션은 epoll 기반 이벤트 루프(Event Loop)로 TUN/TAP fd를 관리합니다.

멀티큐 및 vhost-net

멀티큐 TUN/TAP은 가상화 환경에서 네트워크 처리량(Throughput)을 크게 향상시킵니다. IFF_MULTI_QUEUE 플래그로 생성한 디바이스에 여러 fd를 attach하면 각 큐가 독립적으로 패킷을 처리합니다.

/* 멀티큐 TUN/TAP 설정 — 큐마다 fd를 하나씩 열어 attach */
int tun_alloc_mq(char *dev, int queues, int *fds)
{
    struct ifreq ifr;
    int fd, i;

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, dev, IFNAMSIZ);
    ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR | IFF_MULTI_QUEUE;

    for (i = 0; i < queues; i++) {
        fd = open("/dev/net/tun", O_RDWR);
        ioctl(fd, TUNSETIFF, &ifr);  /* 동일 이름 → 큐 추가 */
        fds[i] = fd;
    }
    return 0;
}

vhost-net은 TAP 디바이스의 I/O 경로를 최적화하는 커널 모듈입니다. 일반적인 TAP은 유저스페이스(QEMU)를 거쳐 패킷을 중계하지만, vhost-net은 커널 내부의 vhost 워커 스레드(Thread)가 virtio ring과 TAP 소켓(Socket)을 직접 연결하여 컨텍스트 스위칭(Context Switching)을 제거합니다.

# QEMU에서 vhost-net 활용 (멀티큐 TAP)
# 1. TAP 디바이스 생성
ip tuntap add dev tap0 mode tap multi_queue vnet_hdr user qemu

# 2. QEMU 실행 (4큐 + vhost-net)
qemu-system-x86_64 \
  -netdev tap,id=net0,ifname=tap0,script=no,downscript=no,\
vhost=on,queues=4 \
  -device virtio-net-pci,netdev=net0,mq=on,vectors=10

# 3. 게스트 내부에서 멀티큐 활성화
ethtool -L eth0 combined 4
vhost-net 성능 차이: vhost-net 없이 TAP을 사용하면 모든 패킷이 QEMU 유저스페이스 프로세스를 거칩니다 (VM exit → QEMU read/write → VM enter). vhost-net 사용 시 커널 내 직접 전달로 2~5배 처리량 향상이 가능합니다. /dev/vhost-net 디바이스 파일과 vhost_net 모듈이 필요합니다.

고급 설정 및 디버깅(Debugging)

# Persistent TUN/TAP — fd 닫아도 유지
ip tuntap add dev tap0 mode tap
ip link set tap0 up
tunctl -p tap0  # 또는 ioctl(fd, TUNSETPERSIST, 1)

# 소유자/그룹 설정 (비루트 사용자 접근 허용)
ip tuntap add dev tap0 mode tap user nobody group kvm

# offload 기능 설정 (QEMU virtio-net 최적화)
ethtool -K tap0 tx-checksum-ip-generic on
ethtool -K tap0 tso on gso on

# tcpdump로 TUN/TAP 트래픽 캡처
tcpdump -i tap0 -nn -e -v

# sysfs를 통한 파라미터 확인
cat /sys/class/net/tap0/tun_flags
cat /sys/class/net/tap0/type         # 1=이더넷(TAP), 65534=TUN
cat /sys/class/net/tap0/owner
cat /sys/class/net/tap0/group

# 통계 확인
ip -s link show tap0
cat /sys/class/net/tap0/statistics/tx_packets
cat /sys/class/net/tap0/statistics/rx_dropped
트러블슈팅 체크리스트:
  • NO-CARRIER: TUN/TAP fd를 열고 있는 프로세스가 없으면 인터페이스가 NO-CARRIER 상태가 됩니다. persistent 모드에서도 fd를 닫으면 carrier가 내려갑니다. ip link show tap0으로 확인하세요.
  • MTU 불일치: TUN/TAP의 기본 MTU는 1500입니다. VPN 캡슐화(IPsec, GRE) 시 오버헤드(Overhead)를 고려하여 ip link set tun0 mtu 1400으로 조정하세요.
  • Permission denied: /dev/net/tun은 기본 root:root 0666이지만, 일부 배포판에서 0660으로 제한됩니다. TUNSETOWNER/TUNSETGROUP 또는 udev 규칙으로 해결합니다.

IPVLAN 모드별 동작 상세

IPVLAN은 3가지 모드를 지원하며, 각 모드는 패킷 처리 계층이 다릅니다. 모든 IPVLAN 인터페이스는 하위 디바이스(parent)의 MAC 주소를 공유합니다.

항목L2L3L3S
패킷 처리 계층L2 (이더넷)L3 (IP 라우팅)L3 + Netfilter
ARP/NDP 처리각 IPVLAN이 응답커널 라우팅 테이블(Routing Table) 기반커널 라우팅 테이블 기반
브로드캐스트/멀티캐스트지원미지원 (L3 only)미지원
Netfilter (iptables/nftables)미지원미지원지원 (conntrack 포함)
IPVLAN 간 통신L2 브리징라우팅 기반라우팅 기반
DHCP지원 (L2이므로)미지원미지원
사용 사례일반 컨테이너고성능 라우팅Kubernetes, 서비스 메시
/* drivers/net/ipvlan/ipvlan_core.c — IPVLAN RX 모드 분기 */
rx_handler_result_t ipvlan_handle_frame(struct sk_buff **pskb)
{
    struct ipvl_port *port = ipvlan_port_get_rcu(skb->dev);

    switch (port->mode) {
    case IPVLAN_MODE_L2:
        return ipvlan_handle_mode_l2(pskb, port);
        /* dst MAC 확인 → 브로드캐스트면 모든 IPVLAN에 복제
         * 유니캐스트면 IP로 IPVLAN 디바이스 탐색 → 전달 */

    case IPVLAN_MODE_L3:
        return ipvlan_handle_mode_l3(pskb, port);
        /* L3 헤더만 확인 → dst IP로 IPVLAN 디바이스 탐색
         * netfilter 통과 안 함 → 고성능 */

    case IPVLAN_MODE_L3S:
        return ipvlan_handle_mode_l3(pskb, port);
        /* L3와 동일하지만 NF_HOOK()을 통과 → iptables/conntrack 적용
         * skb->skb_iif를 IPVLAN 디바이스로 설정하여 per-device 규칙 적용 */
    }
    return RX_HANDLER_PASS;
}
IPVLAN + 네임스페이스 실습:
# IPVLAN L3S로 컨테이너 격리 + iptables 사용
ip netns add container1
ip link add ipvl0 link eth0 type ipvlan mode l3s
ip link set ipvl0 netns container1
ip netns exec container1 ip addr add 192.168.1.10/32 dev ipvl0
ip netns exec container1 ip link set ipvl0 up
ip netns exec container1 ip route add default dev ipvl0

# 호스트에서 라우팅 추가
ip route add 192.168.1.10/32 dev eth0

# container1 내부에서 iptables 사용 가능 (L3S이므로)
ip netns exec container1 iptables -A INPUT -p tcp --dport 80 -j ACCEPT

veth XDP와 성능 최적화

커널 5.0부터 veth는 native XDP를 지원합니다. peer에서 전송한 패킷이 네트워크 스택에 진입하기 전에 XDP 프로그램을 실행할 수 있어, 컨테이너 간 고성능 패킷 처리(로드밸런싱, 필터링, 리다이렉트)가 가능합니다.

/* drivers/net/veth.c — veth XDP 수신 경로 */
static int veth_xdp_rcv(struct veth_rq *rq,
                        int budget,
                        struct veth_xdp_tx_bq *bq)
{
    int i, done = 0;

    for (i = 0; i < budget; i++) {
        struct xdp_frame *frame = veth_xdp_rcv_one(rq, ...);

        /* XDP 프로그램 실행 */
        act = bpf_prog_run_xdp(xdp_prog, &xdp);

        switch (act) {
        case XDP_PASS:
            /* 정상 — sk_buff로 변환 후 네트워크 스택 진입 */
            napi_gro_receive(&rq->xdp_napi, skb);
            break;
        case XDP_TX:
            /* peer로 다시 전송 (bounce back) */
            veth_xdp_tx(rq, &xdp, bq);
            break;
        case XDP_REDIRECT:
            /* 다른 인터페이스로 리다이렉트 (BPF map 기반) */
            xdp_do_redirect(rq->dev, &xdp, xdp_prog);
            break;
        case XDP_DROP:
            /* 패킷 드롭 — 네트워크 스택 진입 없이 즉시 해제 */
            xdp_return_frame(frame);
            break;
        }
        done++;
    }
    return done;
}
# veth에 XDP 프로그램 로드 (컨테이너 측 인터페이스에 attach)
ip link set veth1 xdpgeneric obj xdp_drop.o sec xdp

# native XDP (더 빠름 — veth 전용 드라이버 지원)
ip link set veth1 xdp obj xdp_prog.o sec xdp

# XDP 상태 확인
ip link show veth1 | grep xdp

# veth 성능 튜닝
# 1. GRO 활성화 (기본 on)
ethtool -K veth0 gro on

# 2. TSO 활성화
ethtool -K veth0 tso on

# 3. TX queue length 조정 (대량 트래픽 시)
ip link set veth0 txqueuelen 10000

# 4. 체크섬 오프로드 (veth 내부에서는 불필요하므로 off 가능)
ethtool -K veth0 tx-checksum-ip-generic off
veth 성능 특성:
  • 일반 경로: ~3~5 Mpps (NAPI 미사용, softirq 기반)
  • GRO 활성화: ~8~12 Mpps (대용량 패킷 병합)
  • XDP: ~15~24 Mpps (커널 스택 우회, 제로카피)
  • XDP_REDIRECT: 컨테이너 간 직접 전달로 브리지 오버헤드 제거
  • Cilium, Calico 등 CNI 플러그인이 veth+XDP 조합을 활용

LACP 상태머신

LACP 프로토콜은 IEEE 802.3ad(현재 802.1AX) 표준에 정의된 3개의 상태 머신3개의 타이머(Timer)로 구성됩니다. 커널의 bond_3ad.c는 이 상태 머신을 ad_rx_machine(), ad_periodic_machine(), ad_mux_machine() 세 함수로 구현합니다. 각 포트는 독립적인 상태 머신 인스턴스를 가지며, Actor(자신)와 Partner(상대방)의 상태를 추적합니다.

Actor/Partner 상태 비트

actor_statepartner_state는 8비트 필드로, 각 비트가 LACP 동작을 제어합니다. 양쪽이 LACPDU를 교환하면서 상대방의 상태를 학습하고, 자신의 상태를 결정합니다.

비트이름의미커널 상수
0Activity0x01Active LACP (1) vs Passive (0)LACP_STATE_ACTIVITY
1Timeout0x02Short timeout=1s (1) vs Long=30s (0)LACP_STATE_TIMEOUT
2Aggregation0x04이 포트가 집계 가능 (1)LACP_STATE_AGGREGATION
3Synchronization0x08Aggregator에 동기화됨 (1)LACP_STATE_SYNCHRONIZATION
4Collecting0x10수신 프레임 수집 가능 (1)LACP_STATE_COLLECTING
5Distributing0x20송신 프레임 분배 가능 (1)LACP_STATE_DISTRIBUTING
6Defaulted0x40Partner 정보가 기본값 사용 중 (1)LACP_STATE_DEFAULTED
7Expired0x80Partner 타임아웃으로 만료됨 (1)LACP_STATE_EXPIRED
Active vs Passive LACP:
  • Active (Activity=1): 자발적으로 LACPDU 전송. Linux bonding에서 lacp_rate 설정에 따라 주기 결정
  • Passive (Activity=0): 상대방이 Active일 때만 응답. 양쪽 모두 Passive이면 LACP 협상이 시작되지 않음
  • 프로덕션에서는 최소 한쪽을 Active로 설정 (Linux bonding은 기본 Active)

LACPDU 프레임 구조

LACPDU는 IEEE 802.3 Slow Protocols 프레임(EtherType 0x8809)으로, 목적지 MAC 01:80:C2:00:00:02(멀티캐스트)에 전송됩니다. 프레임 크기는 정확히 110바이트입니다.

/* LACPDU 프레임 전체 구조 (110 bytes payload) */
struct lacpdu {
    /* Slow Protocol Header */
    u8  subtype;                /* 0x01 = LACP */
    u8  version;                /* 0x01 */

    /* Actor TLV (Type=1, Length=20) */
    u8  actor_type;              /* 0x01 */
    u8  actor_length;            /* 0x14 (20) */
    __be16 actor_sys_priority;   /* 시스템 우선순위 (기본 65535) */
    u8  actor_system[6];        /* 시스템 MAC (bond MAC) */
    __be16 actor_key;            /* Aggregation Key (speed|duplex) */
    __be16 actor_port_priority;  /* 포트 우선순위 (기본 255) */
    __be16 actor_port;           /* 포트 번호 (0x0001~) */
    u8  actor_state;             /* 8비트 상태 필드 */
    u8  actor_reserved[3];

    /* Partner TLV (Type=2, Length=20) */
    u8  partner_type;            /* 0x02 */
    u8  partner_length;          /* 0x14 (20) */
    __be16 partner_sys_priority;
    u8  partner_system[6];
    __be16 partner_key;
    __be16 partner_port_priority;
    __be16 partner_port;
    u8  partner_state;
    u8  partner_reserved[3];

    /* Collector TLV (Type=3, Length=16) */
    u8  collector_type;          /* 0x03 */
    u8  collector_length;        /* 0x10 (16) */
    __be16 collector_max_delay;  /* Collector Max Delay */
    u8  collector_reserved[12];

    /* Terminator TLV (Type=0, Length=0) + padding */
    u8  terminator_type;         /* 0x00 */
    u8  terminator_length;       /* 0x00 */
    u8  reserved[50];
};

LACP 타이머와 타임아웃

타이머Fast 모드Slow 모드역할
Periodic Timer1초30초LACPDU 전송 주기
Current While Timer3초 (3x1s)90초 (3x30s)Partner LACPDU 수신 타임아웃
Wait While Timer2초 (고정)Aggregator 선택 대기
Churn Detection60초 (고정)상태 불안정(churn) 감지
/* drivers/net/bonding/bond_3ad.h — LACP 타이머 상수 */
#define AD_FAST_PERIODIC_TIME      1     /* fast: 1초 */
#define AD_SLOW_PERIODIC_TIME      30    /* slow: 30초 */
#define AD_SHORT_TIMEOUT           3     /* fast timeout: 3초 */
#define AD_LONG_TIMEOUT            90    /* slow timeout: 90초 */
#define AD_CHURN_DETECTION_TIME    60    /* churn 감지: 60초 */
#define AD_AGGREGATE_WAIT_TIME     2     /* aggregator 대기: 2초 */

/* Receive Machine에서 타임아웃 처리 */
static void ad_rx_machine(struct lacpdu *lacpdu,
                          struct port *port)
{
    if (port->sm_rx_state == AD_RX_CURRENT) {
        /* current_while_timer가 만료되면 EXPIRED로 전환 */
        if (port->sm_rx_timer_counter == 0) {
            port->sm_rx_state = AD_RX_EXPIRED;
            port->partner_oper.port_state |= LACP_STATE_EXPIRED;
            port->partner_oper.port_state &= ~LACP_STATE_SYNCHRONIZATION;
            /* timeout을 Short로 변경하여 빠른 복구 시도 */
            port->partner_oper.port_state |= LACP_STATE_TIMEOUT;
            port->sm_rx_timer_counter = AD_SHORT_TIMEOUT;
        }
    }

    if (port->sm_rx_state == AD_RX_EXPIRED) {
        /* Short timeout도 만료 → DEFAULTED (Partner 정보 초기화) */
        if (port->sm_rx_timer_counter == 0)
            port->sm_rx_state = AD_RX_DEFAULTED;
    }
}

Aggregator 선택 알고리즘

하나의 bond 디바이스에 여러 Aggregator가 존재할 수 있습니다 (속도/듀플렉스가 다른 포트 그룹). ad_select 옵션으로 어떤 Aggregator를 활성화할지 결정합니다.

/* drivers/net/bonding/bond_3ad.c — Aggregator 선택 */
static void ad_agg_selection_logic(struct aggregator *agg,
                                   bool *update)
{
    struct aggregator *best = NULL;
    struct aggregator *active;
    struct bonding *bond = agg->slave->bond;

    switch (bond->params.ad_select) {
    case BOND_AD_STABLE:
        /* 기본값: 현재 활성 aggregator를 유지
         * 새 aggregator가 더 많은 포트를 가져도 변경하지 않음
         * → 안정적이지만 최적 대역폭 활용 안 될 수 있음 */
        if (active && active->num_of_ports > 0)
            best = active;
        break;

    case BOND_AD_BANDWIDTH:
        /* 총 대역폭(ports × speed)이 가장 큰 aggregator 선택
         * → 동적으로 최적 대역폭 제공 */
        bond_for_each_slave(bond, slave, iter) {
            agg = &(SLAVE_AD_INFO(slave)->aggregator);
            if (__agg_bandwidth(agg) > __agg_bandwidth(best))
                best = agg;
        }
        break;

    case BOND_AD_COUNT:
        /* 포트 수가 가장 많은 aggregator 선택
         * → 포트 수 기준 단순 비교 */
        bond_for_each_slave(bond, slave, iter) {
            agg = &(SLAVE_AD_INFO(slave)->aggregator);
            if (agg->num_of_ports > best->num_of_ports)
                best = agg;
        }
        break;
    }

    /* 활성 aggregator 변경 시 모든 포트의 Mux 상태 재평가 */
    if (best != active)
        *update = true;
}
LACP Mux 상태머신 전이도 DETACHED selected=SELECTED WAITING wait_while_timer=0 ATTACHED partner.sync=1 & partner.collect=1 COLLECTING partner.distributing=1 COLLECTING_DISTRIBUTING TX + RX 모두 활성 (정상 동작 상태) partner.sync=0 selected=UNSELECTED RX Machine INITIALIZE PORT_DISABLED EXPIRED (3s/short) DEFAULTED (partner lost) CURRENT (LACPDU OK) ad_rx_machine() Periodic TX Machine NO_PERIODIC FAST_PERIODIC (1s) SLOW_PERIODIC (30s) ad_periodic_machine() 정상 전이 장애/비동기 역전이 ad_mux_machine() — Mux 상태머신이 TX/RX 활성화 제어
LACP Mux 상태머신 전이도: DETACHED → WAITING → ATTACHED → COLLECTING → COLLECTING_DISTRIBUTING

Mux Machine 내부 구현

Mux Machine은 포트의 실제 TX/RX 활성 상태를 제어합니다. ad_mux_machine()에서 Aggregator 선택 결과와 Partner의 동기화 상태를 기반으로 DETACHED → WAITING → ATTACHED → COLLECTING → COLLECTING_DISTRIBUTING 전이를 수행합니다.

/* drivers/net/bonding/bond_3ad.c — Mux Machine 핵심 로직 */
static void ad_mux_machine(struct port *port, bool *update_slave_arr)
{
    switch (port->sm_mux_state) {
    case AD_MUX_DETACHED:
        if (port->selected == SELECTED ||
            port->selected == STANDBY)
            port->sm_mux_state = AD_MUX_WAITING;
        break;

    case AD_MUX_WAITING:
        if (port->selected == UNSELECTED)
            port->sm_mux_state = AD_MUX_DETACHED;
        else if (port->sm_mux_timer_counter == 0)
            port->sm_mux_state = AD_MUX_ATTACHED;
        break;

    case AD_MUX_ATTACHED:
        if (port->selected == UNSELECTED)
            port->sm_mux_state = AD_MUX_DETACHED;
        else if (port->partner_oper.port_state &
                 LACP_STATE_SYNCHRONIZATION)
            port->sm_mux_state = AD_MUX_COLLECTING_DISTRIBUTING;
        break;

    case AD_MUX_COLLECTING_DISTRIBUTING:
        if (!(port->partner_oper.port_state &
              LACP_STATE_SYNCHRONIZATION)) {
            /* Partner가 동기화 해제 → 다시 ATTACHED로 역전이 */
            port->sm_mux_state = AD_MUX_ATTACHED;
            /* Collecting/Distributing 비트 해제 */
            port->actor_oper_port_state &=
                ~(LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING);
        }
        break;
    }
}

802.3ad LACP 협상 시퀀스

LACP 협상은 양쪽이 LACPDU를 교환하면서 단계적으로 진행됩니다. 초기 링크 업부터 COLLECTING_DISTRIBUTING 상태까지의 전체 시퀀스를 추적합니다.

LACP 핸드셰이크 시퀀스 Linux Host (Actor) Switch (Partner) T=0 T+1s T+1s T+2s T+2s T+3s T+3s Link Up 감지 Link Up 감지 LACPDU (Actor: Activity=1, Sync=0) state=0x07 (Activity+Timeout+Aggregation) LACPDU (Partner: Activity=1, Sync=1) state=0x0F (+ Synchronization) LACPDU (Actor: Sync=1, Collecting=1) state=0x1F (+ Collecting) LACPDU (Partner: Collecting=1, Distributing=1) state=0x3F (+ Distributing) LACPDU (Actor: Distributing=1) state=0x3F (FULL — TX+RX 활성) COLLECT_DISTRIB Port Channel UP 주기적 LACPDU (fast=1s / slow=30s) 주기적 LACPDU 응답 3x timeout 미응답 시 → EXPIRED → DEFAULTED → 포트 제거
LACP 핸드셰이크: 양쪽 장비가 LACPDU를 교환하며 Activity → Sync → Collecting → Distributing 순서로 협상
LACP 협상 실패 원인:
  • 양쪽 모두 Passive 모드 — LACPDU를 먼저 보내는 쪽이 없어 협상 시작 불가
  • Key 불일치 — 한쪽 포트가 1G, 다른 쪽이 10G이면 같은 Aggregator에 소속 불가
  • System Priority 충돌 — 양쪽 시스템 우선순위(Priority)가 같고 MAC도 같은 극단적 경우 (가상환경에서 발생)
  • LACPDU 필터링 — 중간 스위치나 방화벽(Firewall)이 멀티캐스트 MAC 01:80:C2:00:00:02를 차단

Bonding 드라이버 내부 구조

Bonding 드라이버의 핵심은 struct bonding(bond_dev_priv)과 struct slave 리스트입니다. 모드별로 다른 xmit 함수가 호출되며, 모든 모드에서 공통적으로 RX handler(bond_handle_frame())가 수신 패킷을 처리합니다.

/* bond_dev_priv 접근 — netdev_priv()로 bonding 구조체 획득 */
struct bonding *bond = netdev_priv(bond_dev);

/* slave 리스트 순회 매크로 */
#define bond_for_each_slave_rcu(bond, slave, iter) \
    list_for_each_entry_rcu(slave, &(bond)->slave_list, list)

/* 모드별 xmit 함수 디스패치 */
static const struct net_device_ops bond_netdev_ops = {
    .ndo_start_xmit     = bond_start_xmit,
    .ndo_select_queue    = bond_select_queue,
    .ndo_get_stats64     = bond_get_stats,
    .ndo_set_mac_address = bond_set_mac_address,
    .ndo_change_mtu      = bond_change_mtu,
    .ndo_fix_features    = bond_fix_features,
    /* ... */
};

/* bond_start_xmit() 내부에서 모드별 분기 */
static netdev_tx_t bond_start_xmit(struct sk_buff *skb,
                                    struct net_device *dev)
{
    struct bonding *bond = netdev_priv(dev);

    switch (BOND_MODE(bond)) {
    case BOND_MODE_ROUNDROBIN:
        return bond_xmit_roundrobin(skb, dev);
    case BOND_MODE_ACTIVEBACKUP:
        return bond_xmit_activebackup(skb, dev);
    case BOND_MODE_XOR:
        return bond_xmit_xor(skb, dev);
    case BOND_MODE_BROADCAST:
        return bond_xmit_broadcast(skb, dev);
    case BOND_MODE_8023AD:
        return bond_3ad_xmit_xor(skb, dev);
    case BOND_MODE_TLB:
    case BOND_MODE_ALB:
        return bond_alb_xmit(skb, dev);
    default:
        bond_tx_drop(dev, skb);
        return NETDEV_TX_OK;
    }
}

RX Handler와 수신 경로

Bonding 슬레이브에 도착한 패킷은 bond_handle_frame() RX handler를 통해 bond 디바이스로 전달됩니다. 모드에 따라 패킷 수신 정책이 다릅니다.

/* drivers/net/bonding/bond_main.c — RX handler */
static rx_handler_result_t bond_handle_frame(
    struct sk_buff **pskb)
{
    struct sk_buff *skb = *pskb;
    struct slave *slave = bond_slave_get_rcu(skb->dev);
    struct bonding *bond = slave->bond;

    /* LACPDU 수신 처리 (802.3ad 모드) */
    if (skb->protocol == htons(ETH_P_SLOW))
        return bond_3ad_lacpdu_recv(skb, slave);

    /* active-backup: 백업 슬레이브의 패킷은 드롭 */
    if (BOND_MODE(bond) == BOND_MODE_ACTIVEBACKUP) {
        if (slave != rcu_dereference(bond->curr_active_slave))
            return RX_HANDLER_EXACT;  /* 백업 슬레이브 → 드롭 */
    }

    /* ALB: 모든 슬레이브에서 수신 (RX 분산 위해) */
    /* XOR/RR/802.3ad: 모든 슬레이브에서 수신 허용 */

    skb->dev = bond->dev;  /* skb를 bond 디바이스로 재설정 */
    return RX_HANDLER_ANOTHER;
}

Active-Backup

Active-Backup(Mode 1)은 가장 널리 사용되는 모드입니다. 단순하지만 primary_reselect, fail_over_mac, Gratuitous ARP/NA 전송 등 다양한 세부 정책이 있어 실무에서 정확한 이해가 중요합니다.

primary_reselect 정책

이름동작사용 시나리오
0alwaysprimary 슬레이브가 복구되면 항상 active로 전환특정 NIC가 반드시 주 경로여야 하는 경우
1better복구된 primary가 현재 active보다 우선순위가 높을 때만 전환다중 백업 슬레이브, 우선순위 기반 선택
2failure현재 active가 장애일 때만 primary로 전환불필요한 페일오버 최소화 (안정성 최우선)

fail_over_mac 옵션

기본적으로 모든 슬레이브는 bond 디바이스의 MAC 주소를 공유합니다. 그러나 일부 NIC(특히 Broadcom bnx2)는 MAC 변경을 지원하지 않습니다. fail_over_mac 옵션으로 MAC 처리 방식을 변경할 수 있습니다.

이름동작
0none기본: 모든 슬레이브에 bond MAC 설정 (GARP로 스위치 FDB 갱신)
1active활성 슬레이브의 MAC만 bond MAC으로 설정, 페일오버 시 새 슬레이브에 이전 MAC 설정
2followbond MAC이 활성 슬레이브의 원래 MAC을 따라감 (bond MAC 자체가 변경됨)

Gratuitous ARP/NA 전송

페일오버 시 스위치의 FDB(Forwarding Database)를 갱신하기 위해 Gratuitous ARP(IPv4)와 Unsolicited Neighbor Advertisement(IPv6)를 전송합니다.

/* drivers/net/bonding/bond_main.c — GARP 전송 */
static void bond_send_gratuitous_arp(struct bonding *bond)
{
    struct slave *slave = rcu_dereference(bond->curr_active_slave);

    /* bond 디바이스의 모든 IPv4 주소에 대해 GARP 전송 */
    bond_arp_send_all(bond, slave);
    /* GARP: sender IP = target IP = bond의 IP
     * sender MAC = bond의 MAC → 스위치가 이 MAC의 포트를 학습 */
}

static void bond_send_unsolicited_na(struct bonding *bond)
{
    /* IPv6 Unsolicited NA: 새 슬레이브의 포트로
     * bond의 link-local, global 주소에 대한 NA 전송
     * → 이웃 노드의 Neighbor Cache 갱신 */
    bond_ns_send_all(bond, slave);
}

/* num_grat_arp / num_unsol_na: GARP/NA 전송 횟수 (기본 1)
 * 값을 높이면 (2~3) FDB 갱신 신뢰성 향상
 * peer_notify_delay: 연속 전송 간 지연 (밀리초) */
Active-Backup 페일오버 상세 시퀀스 bond0 eth0 (Active) eth1 (Backup) Switch 정상 상태: eth0=ACTIVE, eth1=BACKUP | MII miimon=100ms 폴링 T=0 Link Down! T+100ms bond_mii_monitor() carrier_ok=false → BOND_LINK_FAIL T+ddelay BOND_LINK_DOWN bond_set_slave_inactive_flags() T+ddelay bond_select_active_slave() → eth1 ACTIVE! T+ddelay bond_change_active_slave(eth1) bond MAC → eth1에 설정 GARP Gratuitous ARP (x num_grat_arp) 스위치 FDB 갱신: bond MAC → eth1 포트 Unsolicited NA (IPv6, x num_unsol_na) 페일오버 완료: eth1=ACTIVE | eth0 복구 시 primary_reselect 정책에 따라 결정
Active-Backup 페일오버 상세: Link Down 감지 → downdelay → 슬레이브 전환 → GARP/NA 전송 → FDB 갱신

페일오버 타이밍 분석

Active-Backup 모드의 전체 페일오버 시간은 여러 구성 요소의 합입니다. 최적 설정으로 100ms 이내, 기본 설정으로 200~500ms의 절체 시간이 발생합니다.

구성 요소기본값최적값설명
링크 다운 감지miimon=100msmiimon=50msMII 폴링(Polling) 주기 (최소 감지 시간)
downdelay0ms100~200ms링크 flapping 방지 (miimon 배수)
슬레이브 전환<1ms<1msbond_change_active_slave() 실행
GARP 전송1회3회 (100ms 간격)스위치 FDB 갱신 시간
스위치 FDB 학습~10ms~10ms스위치가 GARP을 처리하는 시간
STP convergence30~50초0 (PortFast)STP가 있으면 추가 지연, PortFast 필수
# Active-Backup 최적 페일오버 설정 (총 ~150ms 목표)
ip link add bond0 type bond \
    mode active-backup \
    miimon 50 \
    downdelay 100 \
    updelay 200 \
    primary eth0 \
    primary_reselect failure \
    fail_over_mac active \
    num_grat_arp 3 \
    peer_notify_delay 50 \
    num_unsol_na 3

# ARP 모니터링으로 L3 연결성까지 확인 (MII 대신/추가)
# 주의: arp_interval과 miimon은 동시 사용 불가
ip link add bond0 type bond \
    mode active-backup \
    arp_interval 100 \
    arp_ip_target 10.0.0.1,10.0.0.2 \
    arp_validate active \
    arp_all_targets any \
    primary eth0

# arp_validate 옵션:
# none:   ARP 응답 검증 안 함 (기본)
# active: active 슬레이브의 ARP 응답만 검증
# backup: backup 슬레이브의 ARP 응답도 검증
# all:    모든 슬레이브의 ARP 응답 검증
# filter: ARP target이 아닌 소스에서 온 ARP도 검증에 활용

# arp_all_targets 옵션:
# any:    하나라도 응답하면 링크 UP 유지 (기본)
# all:    모든 target이 응답해야 링크 UP 유지
Active-Backup 최적화 팁:
  • primary eth0 primary_reselect failure: 불필요한 페일백 방지 (현재 active가 정상이면 유지)
  • fail_over_mac active: MAC 변경 불가 NIC(Broadcom bnx2 등) 사용 시 필수
  • num_grat_arp 3 peer_notify_delay 50: GARP 3회, 50ms 간격 전송으로 신뢰성 확보
  • downdelay 100 updelay 200: 링크 flapping 방지, STP convergence 대기
  • ARP 모니터링 vs MII: MII는 물리 링크만 감지, ARP는 L3 연결성까지 확인 (스위치 내부 장애도 감지)
  • 스위치 포트에 PortFast(STP)를 반드시 설정하여 STP convergence 지연 제거

Balance-XOR / Balance-RR

Balance-RR 재순서화 문제

Balance-RR(Mode 0)은 패킷을 라운드 로빈으로 슬레이브에 분산합니다. per-packet 분산이므로 단일 TCP 연결의 패킷이 서로 다른 물리 경로를 타고, 경로별 지연 차이로 인해 수신 측에서 패킷 재정렬(out-of-order)이 발생합니다.

파라미터기본값설명
packets_per_slave1한 슬레이브에 연속 전송할 패킷 수. 0=랜덤, 1=순수 RR
/* packets_per_slave 동작 */
static struct slave *bond_xmit_roundrobin_slave_get(
    struct bonding *bond, struct sk_buff *skb)
{
    struct slave *slave;
    int slave_cnt = READ_ONCE(bond->slave_cnt);

    if (bond->params.packets_per_slave == 0) {
        /* 랜덤 선택: prandom_u32() % slave_cnt */
        slave = bond_get_slave_by_id(bond,
                    prandom_u32() % slave_cnt);
    } else {
        /* rr_tx_counter를 packets_per_slave 단위로 증가
         * packets_per_slave=1: 패킷마다 슬레이브 교체 (최대 분산)
         * packets_per_slave=N: N개 패킷 후 다음 슬레이브
         * → 값이 클수록 재정렬 감소, 분산 효율 감소 */
        int idx = bond->rr_tx_counter++ / bond->params.packets_per_slave;
        slave = bond_get_slave_by_id(bond, idx % slave_cnt);
    }
    return slave;
}
TCP 재정렬의 실질적 영향:
  • TCP가 3개 이상의 중복 ACK(duplicate ACK)를 수신하면 Fast Retransmit 발동 → 불필요한 재전송
  • tcp_reordering 커널 파라미터(기본 3)를 높이면 완화되지만 실제 패킷 손실 감지가 느려짐
  • UDP는 재정렬에 민감하지 않으므로 RR이 적합한 경우도 있음 (DNS, 미디어 스트리밍)
  • 실무에서는 balance-xor layer3+4802.3ad layer3+4가 대부분의 워크로드에 더 적합

Balance-XOR 해시 분배 상세

Balance-XOR(Mode 2)는 해시 기반 per-flow 분산입니다. 동일 플로우(src/dst IP+Port)는 항상 같은 슬레이브를 사용하므로 TCP 재정렬 문제가 없습니다. 단, 해시 분포의 균등성은 플로우 수와 다양성에 의존합니다.

Balance-XOR 해시 기반 플로우 분배 TCP/UDP 플로우 Flow A: 10.0→20.0:80 Flow B: 10.0→30.0:443 Flow C: 10.0→40.0:22 Flow D: 10.0→50.0:8080 Flow E: 10.0→60.0:3306 bond_xmit_hash() xmit_hash_policy: layer3+4 hash = src_ip ^ dst_ip ^ src_port ^ dst_port slave = hash % slave_cnt eth0 (slave 0) Flow A (hash%2=0) Flow C (hash%2=0) eth1 (slave 1) Flow B (hash%2=1) Flow D (hash%2=1) Flow E → eth0 해시 분배의 함정: 불균등 분산 문제 1: 단일 대용량 플로우(예: iperf 1 connection) → 하나의 슬레이브만 사용 → N×대역폭 달성 불가 문제 2: 소수의 플로우만 활성 → 해시 충돌로 특정 슬레이브에 편중 (elephant flow 문제) 문제 3: 스위치 측 해시와 Linux 측 해시가 다르면 TX는 분산되어도 RX는 단일 포트 → 비대칭 해결: 다수의 동시 연결 유지 (100+ flows) | 스위치-호스트 동일 해시 정책 | balance-rr은 UDP에만 사용 확인: ip -s link show ethN으로 TX bytes 비교 → 편차 20% 이상이면 해시 정책 변경 검토
Balance-XOR: 해시 함수로 플로우별 슬레이브 선택, 동일 플로우는 항상 같은 슬레이브 사용

xmit_hash_policy 해시 함수 상세

해시 정책은 bond_xmit_hash()에서 패킷의 어떤 필드를 해시 입력으로 사용할지 결정합니다. 802.3ad와 balance-xor 모드 모두 동일한 해시 함수를 사용합니다.

정책해시 입력적합 워크로드분산 균등성
layer2src_mac XOR dst_mac비-IP 트래픽, L2 환경낮음 (소수 피어 시 편중)
layer2+3src_mac XOR dst_mac XOR src_ip XOR dst_ip동일 MAC, 다수 IP 피어중간
layer3+4src_ip XOR dst_ip XOR src_port XOR dst_port범용 TCP/UDP (권장)높음
encap2+3내부 L2+L3 (VXLAN/GRE/GENEVE 터널(Tunnel) 내부)오버레이(Overlay) 네트워크높음
encap3+4내부 L3+L4 (터널 내부 IP+Port)오버레이 + 다수 플로우매우 높음
vlan+srcmacVLAN ID XOR src_mac (커널 5.0+)다중 VLAN, 동일 서브넷VLAN 수에 비례
/* drivers/net/bonding/bond_main.c — 해시 함수 구현 */
static u32 bond_xmit_hash(struct bonding *bond,
                           struct sk_buff *skb)
{
    switch (bond->params.xmit_hash_policy) {
    case BOND_XMIT_POLICY_LAYER2:
        return bond_eth_hash(skb);
        /* hash = src_mac[5] ^ dst_mac[5] */

    case BOND_XMIT_POLICY_LAYER23:
        return bond_eth_hash(skb) ^
               bond_l3_hash(skb);
        /* hash = (mac_hash) ^ (src_ip ^ dst_ip) */

    case BOND_XMIT_POLICY_LAYER34:
        return bond_l34_hash(skb);
        /* hash = src_ip ^ dst_ip ^ src_port ^ dst_port
         * 비-TCP/UDP 패킷은 L3만 사용 (ICMP, ARP 등) */

    case BOND_XMIT_POLICY_ENCAP23:
    case BOND_XMIT_POLICY_ENCAP34:
        return bond_encap_hash(skb, bond);
        /* VXLAN/GRE/GENEVE: 외부 헤더를 벗기고 내부 헤더로 해시
         * skb_flow_dissect_flow_keys()로 터널 내부 파싱 */

    case BOND_XMIT_POLICY_VLAN_SRCMAC:
        return bond_vlan_srcmac_hash(skb);
        /* hash = vlan_id ^ src_mac (멀티 VLAN 환경 최적화) */
    }
}
encap 정책과 터널 분산:
  • VXLAN/GRE 터널에서 layer3+4를 사용하면 외부 헤더만 해시되어 모든 터널 트래픽이 하나의 슬레이브로 편중
  • encap3+4는 내부 패킷의 L3+L4를 해시하여 터널 내부 플로우별 분산 달성
  • OVS VXLAN, Kubernetes Calico VXLAN, Docker overlay 환경에서는 encap3+4 필수
  • 스위치 측에서도 inner header hashing을 지원해야 양방향 균등 분산 가능

Bonding + VLAN 스택

Bonding과 VLAN은 함께 사용되는 가장 일반적인 조합입니다. 스택 순서(bond-over-VLAN vs VLAN-over-bond)에 따라 동작이 크게 달라지며, MTU 관리에 주의가 필요합니다.

VLAN over Bond (권장)

물리 NIC들을 먼저 bond로 묶고, bond 디바이스 위에 VLAN 인터페이스를 생성합니다. 가장 일반적이고 권장되는 구성입니다.

# VLAN over Bond: bond0 위에 여러 VLAN 생성
ip link add bond0 type bond mode 802.3ad miimon 100 lacp_rate fast \
    xmit_hash_policy layer3+4
ip link set eth0 master bond0
ip link set eth1 master bond0
ip link set bond0 up

# bond0 위에 VLAN 인터페이스 생성
ip link add link bond0 name bond0.100 type vlan id 100
ip link add link bond0 name bond0.200 type vlan id 200
ip addr add 10.1.0.1/24 dev bond0.100
ip addr add 10.2.0.1/24 dev bond0.200
ip link set bond0.100 up
ip link set bond0.200 up

# 구조: Application → bond0.100/200 → bond0 → eth0/eth1 → Switch(trunk)

Bond over VLAN (특수 용도)

VLAN 인터페이스를 먼저 만들고, VLAN 인터페이스를 bond의 슬레이브로 사용합니다. 서로 다른 스위치의 서로 다른 VLAN을 하나의 bond로 묶는 특수한 시나리오에 사용됩니다.

# Bond over VLAN: 이미 존재하는 VLAN 인터페이스를 슬레이브로 사용
# 주의: 이 구성은 802.3ad를 사용할 수 없음 (LACPDU가 VLAN 태그 통과 문제)
ip link add link eth0 name eth0.100 type vlan id 100
ip link add link eth1 name eth1.100 type vlan id 100
ip link set eth0.100 up
ip link set eth1.100 up

ip link add bond0 type bond mode active-backup miimon 100
ip link set eth0.100 master bond0
ip link set eth1.100 master bond0
ip link set bond0 up

# 구조: Application → bond0 → eth0.100/eth1.100 → eth0/eth1

MTU 관리

구성MTU 설정주의사항
VLAN over Bondbond0에 MTU 설정 → VLAN이 상속VLAN 태그(4B)로 인해 물리 NIC은 MTU+4 지원 필요
Bond over VLAN각 VLAN에 MTU 설정 → bond가 최소값 사용슬레이브 간 MTU 불일치 시 bond가 최소값으로 제한
Jumbo Frameip link set bond0 mtu 9000스위치, 모든 슬레이브, 경로상 모든 장비 MTU 일치 필요
# Jumbo Frame 설정 (VLAN over Bond)
ip link set eth0 mtu 9000
ip link set eth1 mtu 9000
ip link set bond0 mtu 9000
# VLAN MTU는 bond MTU를 초과할 수 없음
ip link set bond0.100 mtu 9000  # OK
ip link set bond0.100 mtu 9200  # ERROR: mtu > bond mtu

# MTU 확인
ip -d link show bond0 | grep mtu
ip -d link show eth0 | grep mtu

802.1ad Q-in-Q 구성

서비스 프로바이더 환경에서는 Q-in-Q(IEEE 802.1ad)를 사용하여 고객별 VLAN을 외부 S-VLAN으로 캡슐화합니다. Bond 위에 S-VLAN(외부)을 생성하고, S-VLAN 위에 C-VLAN(내부)을 스택합니다.

# 802.1ad Q-in-Q over Bond 구성
# 구조: Application → C-VLAN(100) → S-VLAN(1000) → bond0 → eth0/eth1

# 1. Bond 생성
ip link add bond0 type bond mode 802.3ad miimon 100 lacp_rate fast
ip link set eth0 master bond0
ip link set eth1 master bond0
ip link set bond0 up

# 2. S-VLAN (802.1ad, EtherType 0x88a8) 생성
ip link add link bond0 name bond0.s1000 type vlan id 1000 protocol 802.1ad
ip link set bond0.s1000 up

# 3. C-VLAN (802.1Q, EtherType 0x8100) 생성 — S-VLAN 위에
ip link add link bond0.s1000 name bond0.s1000.c100 type vlan id 100
ip addr add 10.100.0.1/24 dev bond0.s1000.c100
ip link set bond0.s1000.c100 up

# 결과 프레임 구조:
# [Ethernet][S-VLAN:0x88a8,ID=1000][C-VLAN:0x8100,ID=100][IP][Payload]
# MTU 주의: 외부 태그(4B) + 내부 태그(4B) = 8B 추가 오버헤드
ip link set eth0 mtu 9008   # 9000 + 8 (double tag)
ip link set eth1 mtu 9008
ip link set bond0 mtu 9008

VLAN Filtering 상호작용

커널 4.0+에서 bond 디바이스는 NETIF_F_HW_VLAN_CTAG_FILTER 기능을 통해 슬레이브 NIC의 하드웨어 VLAN 필터링을 활용합니다. Bond 위에 VLAN을 추가하면 해당 VLAN ID가 모든 슬레이브의 하드웨어 필터에 자동으로 등록됩니다.

/* drivers/net/bonding/bond_main.c — VLAN 추가 시 슬레이브 동기화 */
static int bond_vlan_rx_add_vid(struct net_device *bond_dev,
                                 __be16 proto, u16 vid)
{
    struct bonding *bond = netdev_priv(bond_dev);
    struct slave *slave;
    struct list_head *iter;

    /* 모든 슬레이브에 VLAN 필터 등록 */
    bond_for_each_slave(bond, slave, iter) {
        vlan_vid_add(slave->dev, proto, vid);
        /* 슬레이브 NIC가 HW VLAN filter를 지원하면
         * NIC의 MAC 필터 테이블에 VID가 추가됨
         * → 해당 VLAN의 프레임만 NIC에서 호스트로 전달 */
    }
    return 0;
}

/* 슬레이브 추가 시 기존 VLAN 전체 동기화 */
static int bond_enslave(struct net_device *bond_dev,
                        struct net_device *slave_dev, ...)
{
    /* ... 슬레이브 설정 ... */
    /* bond에 이미 등록된 모든 VLAN을 새 슬레이브에 복제 */
    vlan_vids_add_by_dev(slave_dev, bond_dev);
    /* ... */
}
VLAN + Bonding 주의사항:
  • 802.3ad(LACP) + VLAN: 스위치에서 trunk 포트로 설정하고, LACP는 native VLAN에서 동작
  • ALB/TLB + VLAN: RLB의 ARP 가로채기가 VLAN 태그가 있는 ARP 패킷에서 실패할 수 있음
  • Bond over VLAN에서 LACP는 LACPDU가 VLAN 태그를 통과해야 하므로 스위치에서 지원 여부 확인 필수
  • QinQ(802.1ad) 환경에서는 외부 VLAN을 bond 위에, 내부 VLAN을 외부 VLAN 위에 스택
  • VLAN filtering 미지원 NIC 사용 시 bond0에서 promiscuous 모드가 활성화되어 불필요한 프레임까지 커널에 전달 (CPU 부담 증가)

Team 드라이버 vs Bonding 비교

Team은 bonding의 차세대 대안으로 설계되었지만, 두 기술은 각각 장단점이 있습니다. RHEL 8에서 Team을 권장했으나, RHEL 9에서는 다시 bonding으로 복귀하는 추세입니다.

Team Runner 상세

Runner대응 Bonding 모드구현 위치특징
activebackupMode 1커널link_watch 이벤트 기반 전환, priority 기반 선택
lacpMode 4teamd (유저스페이스)유저스페이스 LACP 구현, tx_hash 커스텀 가능
loadbalanceMode 2/5/6teamdBPF 기반 TX 해시, tx_balancer로 동적 재분배
roundrobinMode 0커널단순 라운드 로빈, 동일한 재정렬 문제
broadcastMode 3커널모든 포트로 복제 전송
random없음커널랜덤 선택 (bonding에 없는 모드)
# teamdctl 주요 명령어
teamdctl team0 state                    # 전체 상태 조회
teamdctl team0 state item get setup.runner_name  # runner 이름
teamdctl team0 port add eth2            # 런타임 포트 추가
teamdctl team0 port remove eth2         # 런타임 포트 제거
teamdctl team0 port config update eth0 '{"prio": 100}'  # 포트 설정 변경

# JSON 설정 파일 예시 (loadbalance runner)
teamd -d -t team0 -c '{
    "runner": {
        "name": "loadbalance",
        "tx_hash": ["eth", "ipv4", "ipv6", "tcp", "udp"],
        "tx_balancer": {
            "name": "basic",
            "balancing_interval": 100
        }
    },
    "link_watch": {
        "name": "ethtool",
        "delay_up": 200,
        "delay_down": 200
    },
    "ports": {
        "eth0": {"prio": 100, "sticky": true},
        "eth1": {"prio": 50}
    }
}'

# NetworkManager로 Team 구성 (nmcli)
nmcli con add type team con-name team0 ifname team0 \
    team.runner activebackup
nmcli con add type team-slave con-name team0-eth0 \
    ifname eth0 master team0
nmcli con add type team-slave con-name team0-eth1 \
    ifname eth1 master team0

Team 런타임 모드 변경

Team의 가장 큰 차별점은 런타임 중 모드(runner) 변경이 가능하다는 점입니다. Bonding에서는 모드 변경 시 인터페이스를 down해야 하지만, Team은 Netlink API를 통해 트래픽 중단 없이 전환할 수 있습니다.

# Team 런타임 모드 변경 예시
# activebackup → loadbalance로 변경 (트래픽 최소 중단)
teamdctl team0 state item set setup.runner_name loadbalance

# 런타임 포트 우선순위 변경
teamdctl team0 port config update eth0 '{"prio": 200}'
teamdctl team0 port config update eth1 '{"prio": 100}'

# 런타임 link_watch 변경
teamdctl team0 state item set setup.link_watch.name arp_ping
teamdctl team0 state item set setup.link_watch.target_host 10.0.0.1

# Bonding은 모드 변경 시 반드시 down 필요
ip link set bond0 down
ip link set bond0 type bond mode balance-xor  # down 상태에서만 변경 가능
ip link set bond0 up

기능 매트릭스 비교

기능BondingTeam
LACP 구현 위치커널 (bond_3ad.c)유저스페이스 (teamd)
런타임 모드 변경불가 (down 필요)가능 (Netlink)
런타임 포트 추가/제거가능 (ip link set)가능 (teamdctl)
커스텀 해시 (BPF)불가가능
D-Bus API없음지원
최대 슬레이브 수무제한무제한
커널 모듈 수1 (bonding)2+ (team, team_mode_*)
IPv6 NA 페일오버지원지원
ethtool link watchMII monitor (커널)ethtool/arp_ping/nsna_ping
RHEL 9 상태권장deprecated
Ubuntu/Debian기본 지원libteam 별도 설치
Bonding vs Team 선택 가이드 (2024 기준):
  • Bonding 선택: LACP가 필요한 경우 (커널 내 구현으로 더 안정적), 레거시 환경, 대부분의 프로덕션
  • Team 선택: 런타임 재설정이 빈번한 환경, BPF 기반 커스텀 해시가 필요한 경우
  • RHEL 9 참고: RHEL 9에서 Team은 deprecated로 표시, bonding 사용 권장
  • 두 기술 모두 ip link, nmcli, systemd-networkd로 관리 가능

MACVLAN/IPVLAN L2/L3/L3S

MACVLAN과 IPVLAN은 컨테이너 네트워킹에서 브리지 오버헤드 없이 고성능 네트워크 연결을 제공합니다. 패킷 경로, 성능 특성, 컨테이너 런타임과의 통합 방법을 상세히 비교합니다.

MACVLAN 패킷 경로 상세

/* drivers/net/macvlan.c — MACVLAN TX 경로 */
static netdev_tx_t macvlan_start_xmit(struct sk_buff *skb,
                                       struct net_device *dev)
{
    struct macvlan_dev *vlan = netdev_priv(dev);
    struct macvlan_port *port = vlan->port;
    const struct ethhdr *eth = (void *)skb->data;

    /* bridge 모드: dst MAC이 다른 MACVLAN이면 직접 전달 */
    if (vlan->mode == MACVLAN_MODE_BRIDGE) {
        struct macvlan_dev *dest;
        dest = macvlan_hash_lookup(port, eth->h_dest);
        if (dest && dest != vlan) {
            /* 같은 호스트의 다른 MACVLAN → 물리 NIC을 거치지 않고 직접 전달 */
            skb->dev = dest->dev;
            dev_forward_skb(dest->dev, skb);
            return NET_XMIT_SUCCESS;
        }
    }

    /* vepa/private 모드: 항상 물리 NIC으로 전송 */
    skb->dev = vlan->lowerdev;
    return dev_queue_xmit(skb);
}

IPVLAN L2/L3/L3S 패킷 경로 비교

MACVLAN vs IPVLAN L2/L3 RX 패킷 경로 비교 eth0 (Physical NIC) RX 패킷 도착 rx_handler (macvlan or ipvlan) MACVLAN 1. dst MAC 확인 2. macvlan_hash_lookup() MAC → MACVLAN 디바이스 매핑 3. skb->dev = macvlan_dev 4. netif_rx(skb) 네트워크 스택 (L3+) 고유 MAC → L2 수준 격리 IPVLAN L2 1. 공유 MAC (parent 동일) 2. ipvlan_handle_mode_l2() dst IP → IPVLAN 디바이스 3. broadcast → 모든 IPVLAN 복제 unicast → IP로 매칭 4. ARP/NDP: IPVLAN이 응답 네트워크 스택 (L3+) MAC 공유 → 스위치 부담 없음 IPVLAN L3 / L3S 1. L3 헤더만 확인 (no L2) 2. ipvlan_handle_mode_l3() dst IP → 라우팅 테이블 조회 3. broadcast/multicast 미지원 DHCP 불가 (static IP만) 4. L3S만: NF_HOOK() 통과 iptables/conntrack 적용 네트워크 스택 (L4+) 최소 오버헤드, 라우팅 기반 성능 순위 (Throughput 기준, 높을수록 빠름) IPVLAN L3 (최고) > MACVLAN passthru > MACVLAN bridge > IPVLAN L3S > IPVLAN L2 > veth+bridge (최저)
MACVLAN vs IPVLAN L2/L3/L3S: RX 패킷 경로 비교 -- MAC 기반 매칭 vs IP 기반 라우팅

컨테이너에서의 MACVLAN/IPVLAN 활용

# Docker에서 MACVLAN 네트워크 생성
docker network create -d macvlan \
    --subnet=192.168.1.0/24 \
    --gateway=192.168.1.1 \
    -o parent=eth0 \
    -o macvlan_mode=bridge \
    my_macvlan

docker run --network=my_macvlan --ip=192.168.1.100 nginx

# Docker에서 IPVLAN 네트워크 생성
docker network create -d ipvlan \
    --subnet=192.168.1.0/24 \
    --gateway=192.168.1.1 \
    -o parent=eth0 \
    -o ipvlan_mode=l3s \
    my_ipvlan

# MACVLAN의 한계: 호스트 ↔ 컨테이너 직접 통신 불가
# 해결: 호스트에도 별도의 MACVLAN 인터페이스 생성
ip link add macvlan-host link eth0 type macvlan mode bridge
ip addr add 192.168.1.250/32 dev macvlan-host
ip link set macvlan-host up
ip route add 192.168.1.100/32 dev macvlan-host

# 성능 비교 (iperf3 기준, 일반적 수치)
# MACVLAN bridge:  ~95% of line rate (최소 오버헤드)
# IPVLAN L3:       ~97% of line rate (L3 직접 전달)
# IPVLAN L3S:      ~93% (netfilter 통과 오버헤드)
# veth + bridge:   ~85% (브리지 + netfilter 오버헤드)

MACVLAN/IPVLAN 성능 비교 상세

모드Throughput (10G 기준)PPS (64B)LatencyCPU 사용률
MACVLAN bridge9.4 Gbps (~94%)~12 Mpps<10us 추가낮음
MACVLAN passthru9.7 Gbps (~97%)~14 Mpps<5us 추가최소
MACVLAN private9.4 Gbps (~94%)~12 Mpps<10us 추가낮음
MACVLAN vepa9.0 Gbps (~90%)~11 Mpps<15us 추가중간 (헤어핀)
IPVLAN L29.2 Gbps (~92%)~11 Mpps<12us 추가낮음
IPVLAN L39.6 Gbps (~96%)~13 Mpps<8us 추가최소
IPVLAN L3S9.1 Gbps (~91%)~10 Mpps<15us 추가중간 (netfilter)
veth + bridge8.2 Gbps (~82%)~8 Mpps<25us 추가높음
Kubernetes CNI와 MACVLAN/IPVLAN 선택:
  • Calico IPVLAN: L3S 모드로 iptables/eBPF policy 적용 가능, DHCP 불필요 환경에 적합
  • Multus MACVLAN: 보조 네트워크(스토리지, 관리)에 물리 네트워크 직접 연결 시 사용
  • MACVLAN 한계: 호스트 ↔ 컨테이너 직접 통신 불가 (동일 parent에서 hairpin 미지원)
  • IPVLAN L3 한계: broadcast/multicast 미지원으로 DHCP, mDNS 사용 불가
  • MAC 주소 제한 환경: 클라우드 VM에서 MAC 수 제한이 있으면 IPVLAN 필수 (MAC 공유)

SR-IOV + Bonding

SR-IOV(Single Root I/O Virtualization)는 하나의 물리 NIC를 여러 Virtual Function(VF)으로 분할하여 VM이나 컨테이너에 직접 할당합니다. VF를 Bonding 슬레이브로 사용하면 SR-IOV의 하드웨어 가속과 Bonding의 이중화를 동시에 얻을 수 있습니다.

SR-IOV + Bonding 아키텍처 VM / Container bond0 (active-backup) VF0 (enp1s0f0v0) PCI passthrough to VM VF0 (enp2s0f0v0) PCI passthrough to VM IOMMU (VT-d / AMD-Vi) — DMA 격리 PF0 (enp1s0f0) — NIC Port 1 VF0 VF1 VF2 ... sriov_numvfs=N | switchdev mode PF1 (enp2s0f0) — NIC Port 2 VF0 VF1 VF2 ... sriov_numvfs=N | switchdev mode Physical Switch (LACP / MLAG) VF Bonding: 서로 다른 PF의 VF를 bond → NIC 이중화 + SR-IOV 성능 (line rate에 근접) + HW 오프로드
SR-IOV + Bonding: 서로 다른 PF의 VF를 VM 내부에서 bond하여 NIC 이중화와 하드웨어 가속 동시 달성

SR-IOV + Bonding 구성

# 1. SR-IOV VF 생성 (호스트에서)
echo 4 > /sys/class/net/enp1s0f0/device/sriov_numvfs
echo 4 > /sys/class/net/enp2s0f0/device/sriov_numvfs

# 2. VF를 VM에 PCI passthrough로 할당 (libvirt/QEMU)
# <hostdev mode='subsystem' type='pci'>
#   <source><address domain='0x0000' bus='0x01' slot='0x10' function='0x0'/></source>
# </hostdev>

# 3. VM 내부에서 VF Bond 구성
ip link add bond0 type bond mode active-backup miimon 100 \
    primary enp1s0f0v0 fail_over_mac active
ip link set enp1s0f0v0 master bond0   # PF0의 VF
ip link set enp2s0f0v0 master bond0   # PF1의 VF
ip addr add 10.0.0.10/24 dev bond0
ip link set bond0 up

# 4. switchdev 모드 (하드웨어 오프로드, Mellanox ConnectX-5+)
devlink dev eswitch set pci/0000:01:00.0 mode switchdev
# switchdev 모드에서는 VF의 representer가 호스트에 생성되어
# OVS/tc flower 규칙을 HW로 오프로드 가능

# 5. VF 상태 확인
ip link show enp1s0f0  # PF에서 VF 목록 확인
ip link set enp1s0f0 vf 0 mac 00:11:22:33:44:55  # VF MAC 설정
ip link set enp1s0f0 vf 0 vlan 100                 # VF에 VLAN 할당
ip link set enp1s0f0 vf 0 spoofchk on             # MAC/VLAN 스푸핑 방지

하드웨어 LAG 오프로드 (mlx5/ice)

최신 NIC 드라이버(Mellanox ConnectX-5+의 mlx5, Intel E810의 ice)는 bonding 처리를 하드웨어로 오프로드하여 CPU 부담 없이 LAG를 구현합니다. 커널의 NETDEV_CHANGEUPPER netdev 이벤트를 통해 NIC 드라이버가 bonding 구성 변경을 감지합니다.

/* 하드웨어 LAG 오프로드 흐름 */

/* 1. bond에 슬레이브 추가 시 netdev 이벤트 발생 */
/* drivers/net/bonding/bond_main.c */
call_netdevice_notifiers(NETDEV_CHANGEUPPER, slave_dev);

/* 2. NIC 드라이버가 이벤트 수신하여 HW LAG 활성화 */
/* drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c */
static int mlx5_lag_netdev_event(struct notifier_block *nb,
                                  unsigned long event, void *ptr)
{
    struct net_device *ndev = netdev_notifier_info_to_dev(ptr);

    if (event == NETDEV_CHANGEUPPER) {
        struct netdev_notifier_changeupper_info *info = ptr;
        if (netif_is_bond_master(info->upper_dev)) {
            /* 같은 HCA의 두 포트가 bond에 참여 → HW LAG 활성화 */
            mlx5_lag_add_netdev(ldev, ndev);
            /* FW에 LAG 포트 매핑 설정, 하드웨어 해시 테이블 구성 */
        }
    }
}

/* 3. HW LAG 활성 시 bond_dev_queue_xmit() 경로:
 *    - 소프트웨어 해시 대신 NIC FW가 패킷을 물리 포트에 분배
 *    - TC flower / OVS offload 규칙이 두 포트에 동시 적용
 *    - 페일오버도 FW 레벨에서 처리 (us 단위 절체) */
NIC드라이버HW LAG 모드지원 Bonding 모드비고
Mellanox ConnectX-5/6/7mlx5LAG + TC offload802.3ad, active-backup동일 HCA 듀얼 포트 필수
Intel E810iceActive-backup offloadactive-backupSRIOV + bond HW 절체
Broadcom P2100bnxt_enTC offload LAG802.3ad커널 5.18+
SR-IOV + Bonding 실무 팁:
  • fail_over_mac active: VF는 MAC 변경이 제한될 수 있으므로 반드시 설정
  • 서로 다른 PF의 VF를 bond: 동일 PF의 VF를 bond하면 물리적 이중화가 안 됨
  • mode 1(active-backup) 권장: VF에서 802.3ad LACP는 스위치 설정이 복잡하고 VF 특성상 제약이 있음
  • DPDK + Bond: DPDK 환경에서는 rte_eth_bond로 PMD 레벨 bonding 사용
  • 성능: VF bond는 일반 bond 대비 CPU 부담이 적음 (하드웨어 기반 패킷 분류)
  • HW LAG 확인: dmesg | grep "lag\|bond" 또는 devlink dev info로 HW LAG 활성 여부 확인

네트워크 네임스페이스와 Bonding

Bonding과 네트워크 네임스페이스(netns)의 조합은 컨테이너 환경에서 자주 발생하지만, 몇 가지 중요한 제약이 있습니다.

netns 이동 제약

인터페이스netns 이동제약사항
bond 디바이스가능슬레이브가 같은 netns에 있어야 함
bond 슬레이브불가bond에 연결된 상태에서는 이동 불가, 먼저 분리 필요
MACVLAN on bond가능parent(bond)는 원래 netns에 유지, MACVLAN만 이동
IPVLAN on bond가능parent(bond)는 원래 netns에 유지, IPVLAN만 이동
# 패턴 1: 호스트에서 bond → MACVLAN → 컨테이너 netns로 이동
# 물리 NIC 이중화 + 컨테이너 격리 동시 달성
ip link add bond0 type bond mode 802.3ad miimon 100
ip link set eth0 master bond0
ip link set eth1 master bond0
ip link set bond0 up

# bond 위에 MACVLAN 생성 후 컨테이너 netns로 이동
ip netns add container1
ip link add macvlan0 link bond0 type macvlan mode bridge
ip link set macvlan0 netns container1
ip netns exec container1 ip addr add 192.168.1.100/24 dev macvlan0
ip netns exec container1 ip link set macvlan0 up
ip netns exec container1 ip route add default via 192.168.1.1

# 패턴 2: bond 자체를 netns로 이동 (테스트/격리 목적)
# 주의: 슬레이브도 같은 netns에 있어야 하므로 먼저 슬레이브 이동
ip netns add isolated
ip link set eth0 netns isolated
ip link set eth1 netns isolated
ip netns exec isolated ip link add bond0 type bond mode active-backup miimon 100
ip netns exec isolated ip link set eth0 master bond0
ip netns exec isolated ip link set eth1 master bond0
ip netns exec isolated ip link set bond0 up

# 패턴 3: IPVLAN L3S + bond (Kubernetes Calico 스타일)
ip link add ipvl0 link bond0 type ipvlan mode l3s
ip link set ipvl0 netns container1
ip netns exec container1 ip addr add 10.244.1.10/32 dev ipvl0
ip netns exec container1 ip link set ipvl0 up
ip netns exec container1 ip route add default dev ipvl0
# 호스트에서 라우팅 추가
ip route add 10.244.1.10/32 dev bond0

컨테이너 런타임 연동

컨테이너 런타임(Docker, containerd, CRI-O)에서 bonding을 활용하는 패턴과 CNI(Container Network Interface) 플러그인 연동 방법입니다.

# CNI bond 플러그인 설정 (/etc/cni/net.d/10-bond.conflist)
# containernetworking-plugins에 포함된 bond CNI 사용
{
    "cniVersion": "0.4.0",
    "name": "bond-net",
    "plugins": [
        {
            "type": "bond",
            "ifname": "bond0",
            "mode": "active-backup",
            "miimon": "100",
            "failOverMac": 1,
            "linksInContainer": true,
            "links": [
                {"name": "net1"},
                {"name": "net2"}
            ],
            "ipam": {
                "type": "host-local",
                "subnet": "10.244.0.0/16"
            }
        }
    ]
}

# Kubernetes Multus + Bond CNI: Pod에 이중화된 보조 네트워크 할당
# NetworkAttachmentDefinition 리소스로 bond 네트워크 정의 후
# Pod annotation으로 연결:
# k8s.v1.cni.cncf.io/networks: bond-net

# 호스트 bond + Docker macvlan 조합
# 호스트에서 bond0 구성 후 Docker가 bond0 위에 macvlan 생성
docker network create -d macvlan \
    --subnet=10.0.0.0/24 --gateway=10.0.0.1 \
    -o parent=bond0 -o macvlan_mode=bridge \
    bond-macvlan

# CRI-O + IPVLAN on bond: 호스트 bond0 위에 IPVLAN L3S
# /etc/cni/net.d/20-ipvlan-bond.conf
{
    "cniVersion": "0.4.0",
    "type": "ipvlan",
    "name": "ipvlan-bond",
    "master": "bond0",
    "mode": "l3s",
    "ipam": {"type": "host-local", "subnet": "10.244.0.0/16"}
}
컨테이너 환경에서의 Bonding 패턴 요약:
  • 호스트 레벨 bond + 컨테이너 MACVLAN/IPVLAN: 가장 일반적, 호스트 NIC 이중화 + 컨테이너 격리
  • Pod 내부 bond (Multus + bond CNI): SR-IOV VF 이중화, DPDK 워크로드
  • VM 내부 bond: KVM/QEMU에서 virtio-net 또는 VF passthrough 슬레이브
  • netns 제약 주의: bond 슬레이브는 bond와 같은 netns에 있어야 하므로, CNI가 인터페이스를 올바른 순서로 이동해야 함

대규모 운영과 모니터링

프로덕션 환경에서 다수의 bond 인터페이스, 4x25G LACP, 100G+ 집계 대역폭을 운영할 때의 모니터링, 튜닝, 운영 지침입니다.

다중 Bond 구성

# 다중 Bond 구성 예시: 관리 + 데이터 트래픽 분리

# Bond 1: 관리 네트워크 (active-backup, 안정성 우선)
ip link add bond-mgmt type bond mode active-backup miimon 100 \
    primary eth0 primary_reselect failure
ip link set eth0 master bond-mgmt
ip link set eth1 master bond-mgmt
ip addr add 10.0.0.1/24 dev bond-mgmt
ip link set bond-mgmt up

# Bond 2: 데이터 네트워크 (802.3ad, 대역폭 우선)
ip link add bond-data type bond mode 802.3ad miimon 100 \
    lacp_rate fast xmit_hash_policy layer3+4
ip link set eth2 master bond-data
ip link set eth3 master bond-data
ip link set eth4 master bond-data
ip link set eth5 master bond-data
ip link set bond-data up

# bond-data 위에 VLAN 분리
ip link add link bond-data name bond-data.100 type vlan id 100  # Storage
ip link add link bond-data name bond-data.200 type vlan id 200  # VM traffic
ip addr add 10.1.0.1/24 dev bond-data.100
ip addr add 10.2.0.1/24 dev bond-data.200

# 4x25G LACP 구성 (총 100Gbps 집계 대역폭)
ip link add bond-100g type bond mode 802.3ad miimon 100 \
    lacp_rate fast xmit_hash_policy encap3+4 \
    ad_actor_sys_prio 65535
ip link set enp1s0f0 master bond-100g  # 25G port 1
ip link set enp1s0f1 master bond-100g  # 25G port 2
ip link set enp2s0f0 master bond-100g  # 25G port 3
ip link set enp2s0f1 master bond-100g  # 25G port 4
ip link set bond-100g up

모니터링

# === /proc/net/bonding/ 상세 출력 ===
cat /proc/net/bonding/bond0
# 주요 확인 항목:
# - Bonding Mode: IEEE 802.3ad Dynamic link aggregation
# - Transmit Hash Policy: layer3+4
# - MII Status: up (각 슬레이브)
# - LACP Rate: fast
# - Aggregator ID: (모든 슬레이브가 동일한지 확인)
# - Partner Mac Address: (스위치의 MAC — 스위치 장애 시 변경됨)

# === ethtool 슬레이브 상태 ===
ethtool eth0 | grep -E "Speed|Duplex|Link"
# Speed: 25000Mb/s / Duplex: Full / Link detected: yes

# === 슬레이브별 트래픽 분산 모니터링 ===
# TX/RX bytes를 비교하여 해시 분산 균등성 확인
watch -n 1 'for dev in eth0 eth1 eth2 eth3; do
    echo "$dev: $(cat /sys/class/net/$dev/statistics/tx_bytes) TX, \
$(cat /sys/class/net/$dev/statistics/rx_bytes) RX"
done'

# === LACP 파트너 정보 확인 ===
# 파트너 MAC이 변경되면 스위치 장애/교체를 의미
grep -A 5 "Partner" /proc/net/bonding/bond0

# === sysfs를 통한 실시간 파라미터 조회 ===
cat /sys/class/net/bond0/bonding/slaves       # 슬레이브 목록
cat /sys/class/net/bond0/bonding/active_slave  # 활성 슬레이브 (mode 1)
cat /sys/class/net/bond0/bonding/ad_aggregator # Aggregator ID (mode 4)
cat /sys/class/net/bond0/bonding/ad_partner_mac # 파트너 MAC

# === LACPDU 실시간 캡처 ===
tcpdump -i eth0 -nn -v ether proto 0x8809 2>&1 | head -50

# === Prometheus/Node Exporter 연동 ===
# node_exporter의 bonding collector가 자동으로 수집:
# node_bonding_active (활성 슬레이브 수)
# node_bonding_slaves (총 슬레이브 수)
# AlertManager 규칙 예시:
# alert: BondingSlavesDown
#   expr: node_bonding_active < node_bonding_slaves
#   for: 5m

SNMP ifTable과 Bonding

SNMP 기반 모니터링 시스템(Zabbix, Nagios, Cacti)에서 bonding 인터페이스를 모니터링할 때, /proc/net/dev에 bond 디바이스와 각 슬레이브가 독립된 인터페이스로 표시됩니다. net-snmpifTable에서 bond 집계 대역폭을 정확히 산출하려면 슬레이브 통계를 합산해야 합니다.

# SNMP ifTable에서 bonding 인터페이스 확인
snmpwalk -v2c -c public localhost IF-MIB::ifDescr
# IF-MIB::ifDescr.2 = STRING: bond0
# IF-MIB::ifDescr.3 = STRING: eth0
# IF-MIB::ifDescr.4 = STRING: eth1

# bond0의 ifSpeed는 슬레이브 합산 속도
snmpget -v2c -c public localhost IF-MIB::ifSpeed.2
# 802.3ad 2×25G → ifSpeed = 50000000000 (50Gbps)

# 슬레이브별 카운터로 분산율 확인
snmpget -v2c -c public localhost IF-MIB::ifHCOutOctets.3  # eth0 TX
snmpget -v2c -c public localhost IF-MIB::ifHCOutOctets.4  # eth1 TX

# Prometheus node_exporter 메트릭 상세
# node_bonding_active{master="bond0"} 2         → 활성 슬레이브 수
# node_bonding_slaves{master="bond0"} 2          → 총 슬레이브 수
# node_network_transmit_bytes_total{device="eth0"} → 슬레이브별 TX
# node_network_receive_bytes_total{device="eth0"}  → 슬레이브별 RX

# Prometheus AlertManager 규칙 예시
# groups:
# - name: bonding
#   rules:
#   - alert: BondingSlavesDown
#     expr: node_bonding_active < node_bonding_slaves
#     for: 5m
#     labels: {severity: critical}
#     annotations:
#       summary: "Bond {{ $labels.master }} has degraded slaves"
#
#   - alert: BondSlaveTrafficImbalance
#     expr: |
#       max by (master) (rate(node_network_transmit_bytes_total{device=~"eth.*"}[5m]))
#       / min by (master) (rate(node_network_transmit_bytes_total{device=~"eth.*"}[5m]))
#       > 3
#     for: 15m
#     annotations:
#       summary: "Bond slaves TX imbalance > 3:1 ratio"
대규모 운영 체크리스트:
  • IRQ affinity: 각 슬레이브 NIC의 IRQ를 서로 다른 CPU 코어에 배정 (irqbalance 또는 수동 설정)
  • Ring buffer: 고대역폭 환경에서 ethtool -G ethN rx 4096 tx 4096으로 버퍼(Buffer) 확대
  • XPS/RPS: 멀티큐 NIC에서 /sys/class/net/ethN/queues/tx-N/xps_cpus 설정
  • NAPI budget: /proc/sys/net/core/netdev_budget 조정 (기본 300 → 필요시 600)
  • Adaptive Coalescing: ethtool -C ethN adaptive-rx on adaptive-tx on
  • LACPDU 모니터링: LACP Partner MAC 변경 감지 → 스위치 장애 알림
  • 슬레이브 분산 감시: TX bytes 편차 > 30%이면 해시 정책 또는 워크로드 패턴 점검
  • SNMP polling 주기: ifHCOutOctets/ifHCInOctets는 64비트 카운터, 300초 주기 충분
  • ethtool -S: NIC별 하드웨어 카운터(rx_drops, tx_errors) 직접 확인 — 커널 통계에 반영 안 되는 HW 오류 감지

가상 NIC 성능 비교

각 가상 네트워크 인터페이스의 성능과 오버헤드를 비교합니다. 실제 성능은 하드웨어, 커널 버전, 워크로드에 따라 달라지지만, 상대적 순위와 특성은 일관됩니다.

인터페이스TX 오버헤드RX 오버헤드Throughput (상대)LatencyXDP 지원
MACVLAN매우 낮음MAC hash lookup95~98%최소O (passthru)
IPVLAN L3낮음IP lookup93~97%최소O
veth + bridge중간bridge FDB lookup80~90%낮음O
veth + XDP낮음XDP native90~95%최소O (native)
TAP + vhost-net중간vhost worker70~85%중간X
TAP (userspace)높음유저 read()40~60%높음X
TUN높음유저 read()40~60%높음X
Bonding 모드별 성능 특성:
모드단일 플로우 대역폭다중 플로우 대역폭CPU 오버헤드페일오버 속도
active-backup1×link1×link최소100ms~300ms
balance-rrN×link (재정렬 있음)N×link낮음100ms~300ms
balance-xor1×linkN×link낮음 (해시)100ms~300ms
802.3ad1×linkN×link중간 (LACP)1s~3s (LACP)
balance-tlb1×linkN×link (TX)중간 (리밸런싱)100ms~300ms
balance-alb1×linkN×link (TX+RX)중간~높음100ms~300ms

실무 시나리오

시나리오 1: 고가용성 서버 (active-backup + ARP 모니터링)

# 서버 NIC 이중화 — 스위치 설정 없이 HA 구성
# eth0: 1G NIC (슬롯1), eth1: 1G NIC (슬롯2, 다른 스위치 연결 권장)

# bond 생성 (active-backup + ARP 모니터링)
ip link add bond0 type bond \
    mode active-backup \
    primary eth0 \
    primary_reselect always \
    arp_interval 200 \
    arp_ip_target 10.0.0.1 \
    arp_validate active \
    num_grat_arp 3 \
    fail_over_mac active

# 슬레이브 추가
ip link set eth0 down
ip link set eth1 down
ip link set eth0 master bond0
ip link set eth1 master bond0

# IP 설정
ip addr add 10.0.0.100/24 dev bond0
ip link set bond0 up

# 기본 게이트웨이
ip route add default via 10.0.0.1 dev bond0

# systemd-networkd로 영구 설정 (/etc/systemd/network/)
# 10-bond0.netdev, 20-bond0.network, 30-eth0.network, 30-eth1.network

시나리오 2: 고처리량 서버 (802.3ad LACP 4×10G)

# 4×10G NIC LACP 구성 — 스위치 LACP 활성화 필수

ip link add bond0 type bond \
    mode 802.3ad \
    miimon 100 \
    downdelay 200 \
    updelay 200 \
    lacp_rate fast \
    xmit_hash_policy layer3+4 \
    ad_select bandwidth \
    min_links 2

# 4개 슬레이브 추가
for i in 0 1 2 3; do
    ip link set eth${i} down
    ip link set eth${i} master bond0
done

ip addr add 10.0.0.100/24 dev bond0
ip link set bond0 up

# min_links=2: 최소 2개 슬레이브 활성이어야 bond0 UP 유지
# ad_select=bandwidth: 총 대역폭이 가장 큰 aggregator 선택

# 스위치 측 설정 예시 (Cisco)
# interface port-channel 1
#   switchport mode trunk
# interface range GigabitEthernet0/1-4
#   channel-group 1 mode active
#   channel-protocol lacp

시나리오 3: 컨테이너 네트워킹 (veth + bridge)

# Docker 스타일 컨테이너 네트워킹 수동 구성

# 1. 브리지 생성
ip link add br0 type bridge
ip addr add 172.17.0.1/16 dev br0
ip link set br0 up

# 2. NAT 설정 (컨테이너 → 외부)
iptables -t nat -A POSTROUTING -s 172.17.0.0/16 ! -o br0 -j MASQUERADE
echo 1 > /proc/sys/net/ipv4/ip_forward

# 3. 컨테이너 네임스페이스 생성
ip netns add container1

# 4. veth 쌍 생성 및 연결
ip link add veth-host type veth peer name veth-cont
ip link set veth-host master br0
ip link set veth-host up
ip link set veth-cont netns container1

# 5. 컨테이너 내부 설정
ip netns exec container1 bash -c "
    ip link set lo up
    ip link set veth-cont name eth0
    ip addr add 172.17.0.2/16 dev eth0
    ip link set eth0 up
    ip route add default via 172.17.0.1
"

# 6. 테스트
ip netns exec container1 ping -c 3 8.8.8.8

시나리오 4: MACVLAN 기반 컨테이너 (브리지 없는 고성능)

# MACVLAN bridge 모드 — 브리지 없이 물리 네트워크에 직접 연결

# 1. MACVLAN 생성 → 네임스페이스에 배치
ip netns add vm1
ip link add macvlan-vm1 link eth0 type macvlan mode bridge
ip link set macvlan-vm1 netns vm1

# 2. 네임스페이스 내부 설정 (물리 네트워크의 IP 대역 사용)
ip netns exec vm1 bash -c "
    ip link set lo up
    ip link set macvlan-vm1 name eth0
    ip addr add 192.168.1.50/24 dev eth0
    ip link set eth0 up
    ip route add default via 192.168.1.1
"

# 3. 외부에서 직접 접근 가능 (별도 MAC 주소)
ping 192.168.1.50

# 주의: 호스트 eth0 ↔ MACVLAN 컨테이너 간 직접 통신은 불가
# (같은 물리 인터페이스 위의 MACVLAN은 호스트와 직접 통신 못 함)
# 해결: 호스트에도 macvlan 인터페이스를 만들어 사용

시나리오 5: TUN 기반 사용자 VPN 구현

/* 간단한 TUN 기반 VPN 프레임워크 (개념 코드) */
#include <linux/if_tun.h>
#include <sys/epoll.h>

int main(void) {
    char dev[IFNAMSIZ] = "tun-vpn";
    int tun_fd = tun_alloc(dev, IFF_TUN | IFF_NO_PI);

    /* TUN 인터페이스에 IP 할당 */
    system("ip addr add 10.8.0.1/24 dev tun-vpn");
    system("ip link set tun-vpn up");
    system("ip link set tun-vpn mtu 1400");  /* 캡슐화 오버헤드 고려 */

    /* UDP 소켓으로 원격 피어 연결 */
    int udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
    /* bind + connect to remote peer ... */

    /* epoll 이벤트 루프 */
    int epfd = epoll_create1(0);
    /* tun_fd와 udp_fd를 epoll에 등록 */

    while (1) {
        struct epoll_event events[2];
        int n = epoll_wait(epfd, events, 2, -1);

        for (int i = 0; i < n; i++) {
            if (events[i].data.fd == tun_fd) {
                /* TUN → UDP: 로컬 패킷을 읽어서 암호화 후 원격으로 전송 */
                int len = read(tun_fd, buf, sizeof(buf));
                encrypt(buf, len, encrypted);
                sendto(udp_fd, encrypted, enc_len, 0, ...);
            }
            if (events[i].data.fd == udp_fd) {
                /* UDP → TUN: 원격 패킷을 읽어서 복호화 후 TUN에 주입 */
                int len = recvfrom(udp_fd, buf, sizeof(buf), 0, ...);
                decrypt(buf, len, decrypted);
                write(tun_fd, decrypted, dec_len);
            }
        }
    }
}

기술 종합 비교

기술목적계층MAC 주소네임스페이스 이동주요 사용처
BondingNIC 이중화/집계L2단일 (bond)가능서버 HA, 대역폭 집계
TeamNIC 이중화/집계L2단일 (team)가능bonding 대안 (유연한 관리)
MACVLAN가상 NIC (MAC 분리)L2고유 MAC/인터페이스가능컨테이너, VM NIC
IPVLAN가상 NIC (IP 분리)L2/L3공유 (parent MAC)가능클라우드, MAC 제한 환경
veth네임스페이스 연결L2고유 MAC/쌍가능컨테이너 네트워킹 (Docker, K8s)
TUN유저스페이스 L3 I/OL3없음가능VPN (OpenVPN, WireGuard)
TAP유저스페이스 L2 I/OL2고유 MAC가능VM NIC (QEMU/KVM)
조합 활용 패턴:
  • HA 서버: Bonding(802.3ad) + VLAN
  • Docker: veth pair + Linux Bridge + iptables NAT
  • Kubernetes: veth pair + XDP(Cilium) 또는 IPVLAN L3S(Calico)
  • VM (KVM): TAP + vhost-net + macvtap 또는 Bridge
  • VPN 게이트웨이: TUN + Bonding(active-backup) + 라우팅
  • 고성능 컨테이너: MACVLAN(bridge) 또는 IPVLAN(L3) — 브리지 오버헤드 없음
  • 멀티테넌트 격리: MACVLAN(private) 또는 IPVLAN + 네트워크 네임스페이스

참고자료

이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.