Bonding/Team/MACVLAN/veth/TUN-TAP 심화

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

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

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 모드

모드이름설명스위치 설정최대 대역폭
0balance-rr라운드 로빈: 패킷을 순서대로 분산필요 (정적 EtherChannel)N × link speed (TX+RX)
1active-backupActive-Standby: 활성 슬레이브 하나만 사용불필요1 × link speed
2balance-xorXOR 해시: 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 패킷 분산 방식과 커널 함수 매핑

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 플로우의 패킷이 서로 다른 물리 경로를 타므로 지연 차이에 의한 재정렬이 빈번합니다. 이는 TCP 성능 저하(불필요한 재전송, 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 슬레이브를 재배치하는 것입니다.

/* 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 미스매치 감지불가가능
권장 사용처직접 연결, 일반 환경다중 홉, 복잡한 토폴로지
페일오버 시간 최적화:
  • 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 기반 사용자 공간 제어와 모듈러 아키텍처를 제공합니다. 커널 모듈(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 주소로 트래픽을 구분합니다. 둘 다 컨테이너/VM 네트워킹에서 브리지 없이 고성능 네트워크 연결을 제공하는 경량 가상 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 간 완전 격리
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 (이더넷 프레임)
용도라우팅 기반 VPN브리징, VM NIC
디바이스 파일/dev/net/tun
생성 플래그IFF_TUNIFF_TAP

TUN/TAP 아키텍처

TUN/TAP 디바이스는 drivers/net/tun.c에서 구현됩니다. 유저스페이스 프로세스가 /dev/net/tun 캐릭터 디바이스를 열고 TUNSETIFF ioctl로 가상 인터페이스를 생성하면, 커널은 tun_struct와 연결된 net_device를 등록합니다. 이후 유저스페이스는 파일 디스크립터의 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를 가지므로 락 경합 없이 동시 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 기반 이벤트 루프로 TUN/TAP fd를 관리합니다.

멀티큐 및 vhost-net

멀티큐 TUN/TAP은 가상화 환경에서 네트워크 처리량을 크게 향상시킵니다. 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 워커 스레드가 virtio ring과 TAP 소켓을 직접 연결하여 컨텍스트 스위칭을 제거합니다.

# 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 모듈이 필요합니다.

고급 설정 및 디버깅

# 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) 시 오버헤드를 고려하여 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이 응답커널 라우팅 테이블 기반커널 라우팅 테이블 기반
브로드캐스트/멀티캐스트지원미지원 (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 조합을 활용

가상 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 + 네트워크 네임스페이스

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