가상 네트워크 디바이스 (Bridge/VLAN/Bonding)

Linux 커널 가상 네트워크 디바이스 심층 분석: Bridge, VLAN(802.1Q), Bonding/Teaming, MACVLAN/IPVLAN, veth, TUN/TAP, VXLAN, switchdev 프레임워크 종합 가이드.

관련 표준: IEEE 802.1D (Bridging), IEEE 802.1Q (VLAN), IEEE 802.3ad (LACP), RFC 7348 (VXLAN), IEEE 802.1w (RSTP) — 이 문서에서 다루는 가상 네트워크 디바이스가 구현하는 핵심 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

가상 네트워크 디바이스 개요

Linux 커널은 물리적 NIC 없이도 다양한 네트워크 기능을 제공하는 가상 네트워크 디바이스를 지원합니다. 이들은 모두 struct net_device를 기반으로 구현되며, 물리 디바이스와 동일한 네트워크 스택 경로를 통과합니다.

핵심 개념: 가상 네트워크 디바이스는 커널 내부의 소프트웨어 구현체입니다. ip link add 명령으로 생성하며, 물리 NIC와 동일하게 ifconfig/ip addr로 관리합니다. 컨테이너, VM, VPN, 오버레이 네트워크의 기반 기술입니다.
Linux 가상 네트워크 디바이스 계층 User Space ip link / bridge / tc / teamd / openvswitch Socket Layer / Netlink (RTNL) Bridge VLAN Bonding MACVLAN veth TUN/TAP VXLAN Team net_device (dev_queue_xmit / netif_receive_skb) Physical NIC (eth0, ens33, ...) / switchdev HW offload
가상 네트워크 디바이스: 유저 스페이스에서 물리 NIC까지의 계층 구조
디바이스 종류커널 모듈주요 용도커널 소스
BridgebridgeL2 스위칭, VM/컨테이너 연결net/bridge/
VLAN (802.1Q)8021q논리적 네트워크 분리net/8021q/
BondingbondingNIC 이중화/부하 분산drivers/net/bonding/
Teamteambonding 대체 (Netlink 기반)drivers/net/team/
MACVLANmacvlanMAC 기반 가상 NICdrivers/net/macvlan.c
IPVLANipvlanIP 기반 가상 NICdrivers/net/ipvlan/
vethveth네임스페이스 간 연결drivers/net/veth.c
TUN/TAPtun유저스페이스 패킷 I/Odrivers/net/tun.c
VXLANvxlanL2 over L3 오버레이drivers/net/vxlan/

Linux Bridge

Linux Bridge는 IEEE 802.1D 표준을 구현하는 소프트웨어 L2 스위치입니다. 물리/가상 인터페이스를 포트로 추가하여 동일 브로드캐스트 도메인으로 연결하며, MAC 주소 학습(FDB), STP/RSTP, VLAN 필터링을 지원합니다.

Bridge 아키텍처 (net_bridge)

커널 내부에서 브리지는 struct net_bridge로 표현됩니다. 각 포트는 struct net_bridge_port로 관리되며, FDB(Forwarding Database)는 해시 테이블 기반입니다.

/* net/bridge/br_private.h — 브리지 핵심 구조체 */
struct net_bridge {
    spinlock_t              lock;
    struct list_head        port_list;        /* 브리지 포트 목록 */
    struct net_device       *dev;             /* 브리지 net_device */
    struct pcpu_sw_netstats __percpu *stats;

    struct hlist_head       fdb_list;         /* FDB 엔트리 목록 */
    struct rhashtable       fdb_hash_tbl;     /* FDB 해시 테이블 */
    unsigned long           ageing_time;      /* FDB 에이징 타이머 (기본 300초) */

    u16                     group_fwd_mask;
    u16                     default_pvid;     /* VLAN-aware 모드: 기본 PVID */

    struct bridge_mcast_own_query ip4_own_query;
    struct bridge_mcast_own_query ip6_own_query;

    bool                    vlan_enabled;     /* VLAN filtering 활성화 */
    u16                     vlan_proto;       /* ETH_P_8021Q 또는 ETH_P_8021AD */

    struct net_bridge_vlan_group __rcu *vlgrp;  /* VLAN 그룹 */

    /* STP 관련 필드 */
    bridge_id               designated_root;
    bridge_id               bridge_id;
    unsigned char           topology_change;
    u32                     root_path_cost;
    struct timer_list       hello_timer;
    struct timer_list       tcn_timer;
    struct timer_list       topology_change_timer;
    /* ... */
};
/* net/bridge/br_private.h — 브리지 포트 구조체 */
struct net_bridge_port {
    struct net_bridge       *br;              /* 소속 브리지 */
    struct net_device       *dev;             /* 포트의 net_device */
    struct list_head        list;

    unsigned long           flags;
    port_id                 port_id;          /* STP 포트 ID */
    u8                      state;            /* BR_STATE_DISABLED/LISTENING/... */
    u8                      priority;

    struct net_bridge_vlan_group __rcu *vlgrp;  /* 포트별 VLAN 그룹 */

    u16                     backup_port;
    /* ... */
};

FDB (Forwarding Database)

FDB는 MAC 주소와 포트 매핑을 저장하는 테이블입니다. 수신 패킷의 소스 MAC을 학습(learning)하고, 목적지 MAC으로 출력 포트를 결정(forwarding)합니다.

/* net/bridge/br_private.h — FDB 엔트리 */
struct net_bridge_fdb_entry {
    struct rhash_head       rhnode;
    struct net_bridge_port  *dst;             /* 출력 포트 (NULL=로컬) */

    struct {
        unsigned char       addr[6];          /* MAC 주소 */
        u16                 vlan_id;          /* VLAN ID (0=untagged) */
    } key;

    unsigned long           flags;
#define BR_FDB_LOCAL    0                         /* 브리지 자체 MAC */
#define BR_FDB_STATIC   1                         /* 정적 엔트리 */
#define BR_FDB_ADDED_BY_USER 2                  /* 사용자가 추가 */

    unsigned long           updated;          /* 마지막 갱신 시간 (jiffies) */
    unsigned long           used;             /* 마지막 참조 시간 */
    struct rcu_head         rcu;
};
# FDB 테이블 조회
bridge fdb show dev br0

# 정적 FDB 엔트리 추가
bridge fdb add 00:11:22:33:44:55 dev eth0 master static

# FDB 에이징 타임 설정 (초)
ip link set br0 type bridge ageing_time 30000

STP / RSTP

STP(Spanning Tree Protocol, IEEE 802.1D)는 브리지 루프를 방지합니다. RSTP(Rapid STP, IEEE 802.1w)는 수렴 시간을 크게 단축합니다.

포트 상태상수데이터 전달MAC 학습설명
DisabledBR_STATE_DISABLED (0)XX포트 비활성
ListeningBR_STATE_LISTENING (1)XXBPDU 수신 대기
LearningBR_STATE_LEARNING (2)XOFDB 학습 중
ForwardingBR_STATE_FORWARDING (3)OO정상 동작
BlockingBR_STATE_BLOCKING (4)XX루프 차단
# 브리지 생성 및 포트 추가
ip link add name br0 type bridge
ip link set br0 up

ip link set eth0 master br0
ip link set eth1 master br0

# STP 활성화
ip link set br0 type bridge stp_state 1

# STP 상태 확인
bridge link show
cat /sys/class/net/br0/bridge/stp_state

# 브리지 우선순위 설정 (루트 브리지 선출)
ip link set br0 type bridge priority 4096

# 포트별 STP 비용 설정
ip link set eth0 type bridge_slave cost 100

브리지 패킷 처리 흐름

브리지로 수신된 패킷은 br_handle_frame()에서 처리됩니다. BPDU인지 확인 후, FDB 룩업을 통해 unicast forwarding 또는 flooding을 수행합니다.

/* net/bridge/br_input.c — 브리지 패킷 수신 흐름 */

/* 1. NIC 드라이버 → netif_receive_skb() → br_handle_frame() */
static rx_handler_result_t br_handle_frame(
    struct sk_buff **pskb)
{
    struct net_bridge_port *p = br_port_get_rcu(skb->dev);

    /* STP BPDU 처리 */
    if (unlikely(is_link_local_ether_addr(dest)))
        return br_handle_local_finish(skb);

    /* 포트 상태가 FORWARDING이 아니면 드롭 */
    if (p->state == BR_STATE_FORWARDING) {
        /* 2. 소스 MAC 학습 → FDB 갱신 */
        br_fdb_update(br, p, eth_hdr(skb)->h_source, vid, ...);

        /* 3. VLAN 필터링 (vlan_enabled 시) */
        if (!br_allowed_ingress(br, ...))
            goto drop;

        /* 4. 목적지 MAC으로 FDB 룩업 */
        dst = br_fdb_find_rcu(br, dest, vid);
        if (dst) {
            /* unicast: 해당 포트로 전달 */
            br_forward(dst->dst, skb, ...);
        } else {
            /* unknown unicast: 모든 포트로 flooding */
            br_flood(br, skb, ...);
        }
    }
}
성능 주의: 브리지는 소프트웨어 L2 스위칭이므로 대량 트래픽에서는 CPU 부하가 발생합니다. 고성능 환경에서는 switchdev 프레임워크를 통한 하드웨어 오프로드 또는 Open vSwitch(DPDK 가속)를 검토하세요.

VLAN (802.1Q)

VLAN(Virtual LAN)은 IEEE 802.1Q 표준에 따라 이더넷 프레임에 4바이트 태그를 삽입하여 논리적 네트워크 분리를 구현합니다. Linux에서는 8021q 모듈이 VLAN 서브인터페이스를 생성합니다.

802.1Q 태그 포맷

+------------------+-------+-----+------------+--------+
| Dest MAC (6B)    | Src MAC (6B) | TPID  | TCI     | Type  |
+------------------+--------------+-------+---------+-------+
                                   0x8100  PCP|DEI|VID  (원래 EtherType)

TPID (Tag Protocol Identifier): 0x8100 (802.1Q), 0x88A8 (802.1ad QinQ)
TCI  (Tag Control Information):
  - PCP (3 bits): Priority Code Point (CoS 0~7)
  - DEI (1 bit) : Drop Eligible Indicator
  - VID (12 bits): VLAN Identifier (0~4095, 0=priority-only, 4095=reserved)
/* include/linux/if_vlan.h — VLAN 헤더 구조 */
struct vlan_hdr {
    __be16  h_vlan_TCI;        /* PCP(3) | DEI(1) | VID(12) */
    __be16  h_vlan_encapsulated_proto;  /* 원래 EtherType */
};

/* VLAN 디바이스 정보 */
struct vlan_dev_priv {
    unsigned int            nr_ingress_mappings;
    u32                     ingress_priority_map[8];
    unsigned int            nr_egress_mappings;
    struct vlan_priority_tce_mapping *egress_priority_map[16];

    __be16                  vlan_proto;    /* ETH_P_8021Q */
    u16                     vlan_id;       /* VLAN ID (1~4094) */
    u16                     flags;

    struct net_device       *real_dev;     /* 하위 물리 디바이스 */
    unsigned char           real_dev_addr[6];
    struct proc_dir_entry  *dent;
    /* ... */
};

VLAN 구성

# 8021q 모듈 로드
modprobe 8021q

# VLAN 서브인터페이스 생성 (eth0에 VLAN ID 10)
ip link add link eth0 name eth0.10 type vlan id 10

# IP 주소 할당 및 활성화
ip addr add 192.168.10.1/24 dev eth0.10
ip link set eth0.10 up

# VLAN 정보 확인
cat /proc/net/vlan/eth0.10
ip -d link show eth0.10

# QoS: ingress priority 매핑 (802.1p → Linux skb priority)
ip link set eth0.10 type vlan ingress-qos-map 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7

# QoS: egress priority 매핑 (skb priority → 802.1p PCP)
ip link set eth0.10 type vlan egress-qos-map 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7

# 802.1ad (QinQ) VLAN 생성
ip link add link eth0 name eth0.100 type vlan proto 802.1ad id 100
ip link add link eth0.100 name eth0.100.10 type vlan proto 802.1q id 10

# VLAN 삭제
ip link del eth0.10

VLAN 패킷 처리 경로

/* net/8021q/vlan_core.c — 수신 경로 */

/* NIC 드라이버가 VLAN 태그를 하드웨어 가속으로 추출한 경우 */
static inline void __vlan_hwaccel_put_tag(
    struct sk_buff *skb,
    __be16 vlan_proto, u16 vlan_tci)
{
    skb->vlan_proto = vlan_proto;
    skb->vlan_tci = VLAN_TAG_PRESENT | vlan_tci;
}

/* 소프트웨어 경로: VLAN 헤더 파싱 */
struct sk_buff *skb_vlan_untag(struct sk_buff *skb)
{
    struct vlan_hdr *vhdr;
    u16 vlan_tci;

    vhdr = (struct vlan_hdr *) skb->data;
    vlan_tci = ntohs(vhdr->h_vlan_TCI);

    /* skb metadata에 VLAN 정보 저장 후 헤더 제거 */
    __vlan_hwaccel_put_tag(skb, skb->protocol, vlan_tci);
    skb_pull_rcsum(skb, VLAN_HLEN);
    /* ... */
}

Bridge VLAN Filtering

VLAN-aware 브리지는 포트별로 허용 VLAN을 필터링하며, 각 포트에 PVID(Port VLAN ID)와 tagged/untagged 설정을 적용할 수 있습니다. 이 기능은 물리 스위치의 access/trunk 포트 개념과 동일합니다.

# VLAN filtering 활성화
ip link set br0 type bridge vlan_filtering 1

# 기본 PVID 설정 (기본값: 1)
ip link set br0 type bridge vlan_default_pvid 1

# 포트에 VLAN 추가 (tagged)
bridge vlan add vid 10 dev eth0
bridge vlan add vid 20 dev eth0

# 포트에 PVID + untagged VLAN 설정 (access 포트처럼 동작)
bridge vlan add vid 10 dev eth1 pvid untagged

# 브리지 자체에 VLAN 추가 (self: 브리지가 직접 IP를 가질 VLAN)
bridge vlan add vid 10 dev br0 self

# VLAN 설정 확인
bridge vlan show
bridge vlan show dev eth0

# 802.1ad VLAN-aware 브리지
ip link set br0 type bridge vlan_protocol 802.1ad

# VLAN 삭제
bridge vlan del vid 20 dev eth0
PVID vs Tagged/Untagged:
  • PVID (Port VLAN ID): untagged 프레임이 수신되면 이 VLAN에 할당합니다. 포트당 하나만 설정 가능합니다.
  • Untagged: 해당 VLAN의 프레임을 이 포트로 송신할 때 VLAN 태그를 제거합니다 (access 포트).
  • Tagged: VLAN 태그를 유지한 채 송신합니다 (trunk 포트).
/* net/bridge/br_vlan.c — VLAN ingress 필터링 */
bool br_allowed_ingress(const struct net_bridge *br,
                        struct net_bridge_vlan_group *vg,
                        struct sk_buff *skb,
                        u16 *vid, u8 *state)
{
    /* 1. 태그 없는 프레임 → PVID 할당 */
    if (!br_vlan_get_tag(skb, vid)) {
        /* 태그가 있는 경우: VLAN 필터 확인 */
        v = br_vlan_find(vg, *vid);
        if (!v || !br_vlan_should_use(v))
            goto drop;
    } else {
        /* 태그 없는 프레임: PVID 사용 */
        *vid = br_get_pvid(vg);
        if (!*vid)
            goto drop;
        __vlan_hwaccel_put_tag(skb, br->vlan_proto, *vid);
    }
    return true;
drop:
    kfree_skb(skb);
    return false;
}

Bonding

Linux Bonding은 여러 물리 NIC를 하나의 논리 인터페이스로 묶어 이중화(redundancy)부하 분산(load balancing)을 제공합니다. bonding 커널 모듈로 구현되며, 7가지 모드를 지원합니다.

Bonding 모드

모드이름설명스위치 설정
0balance-rr라운드 로빈: 패킷을 순서대로 분산필요 (정적 EtherChannel)
1active-backupActive-Standby: 활성 슬레이브 하나만 사용불필요
2balance-xorXOR 해시: src/dst MAC 기반 분산필요 (정적 EtherChannel)
3broadcast모든 슬레이브로 브로드캐스트 전송필요
4802.3adLACP: IEEE 802.3ad 동적 링크 집계필요 (LACP 지원)
5balance-tlb송신 부하 분산 (Adaptive TLB)불필요
6balance-alb송수신 부하 분산 (Adaptive ALB)불필요

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를 활성화하고 동일한 해시 알고리즘을 설정해야 합니다.

Team (teaming 드라이버)

Team은 bonding의 현대적 대안으로, Netlink 기반 사용자 공간 제어와 모듈러 아키텍처를 제공합니다. teamd 데몬과 libteam 라이브러리로 구성됩니다.

항목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 주소로 트래픽을 구분합니다.

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)의 기본 빌딩 블록입니다.

# 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 규칙으로 해결합니다.

VXLAN (Virtual Extensible LAN)

VXLAN(RFC 7348)은 L2 이더넷 프레임을 UDP로 캡슐화하여 L3 네트워크 위에 가상 L2 오버레이를 구성하는 네트워크 가상화 기술입니다. 기존 VLAN이 12비트 VID로 4,094개의 세그먼트만 지원하는 한계를 극복하여, 24비트 VNI로 최대 16,777,216 (224)개의 논리 네트워크를 지원합니다. 데이터센터의 멀티테넌트 환경, 컨테이너 오버레이 네트워크(Flannel, Calico VXLAN 모드), SDN 솔루션의 핵심 기반 기술입니다.

관련 RFC: RFC 7348 (VXLAN), RFC 7432 (BGP EVPN), RFC 8365 (EVPN Overlay), RFC 9136 (VXLAN-GPE). Linux 커널의 VXLAN 구현은 drivers/net/vxlan/ 디렉토리에 위치하며, vxlan_core.c가 핵심 로직을 담당합니다.

VXLAN 아키텍처와 용어

VXLAN 오버레이/언더레이 아키텍처 Overlay Network (VNI 100: 10.200.0.0/24) VM-A / Container 10.200.0.1 MAC: aa:bb:cc:11:22:33 VM-B / Container 10.200.0.2 MAC: dd:ee:ff:44:55:66 L2 연결 (동일 브로드캐스트 도메인) VTEP-A local: 192.168.1.10 vxlan100 + br-vxlan VTEP-B local: 192.168.1.20 vxlan100 + br-vxlan Underlay Network (L3 IP: 192.168.1.0/24) 물리 스위치 / 라우터 — IP 라우팅으로 VTEP 간 연결 UDP:4789 VXLAN Tunnel Outer IP + UDP + VXLAN Header + Inner Frame
용어설명
VNI (VXLAN Network Identifier)24비트 논리 네트워크 식별자 (0~16,777,215). VLAN ID에 해당하며, 동일 VNI = 동일 L2 도메인
VTEP (VXLAN Tunnel Endpoint)VXLAN 캡슐화/역캡슐화를 수행하는 엔드포인트. Linux에서는 vxlan 타입 net_device
Overlay NetworkVXLAN으로 구성된 가상 L2 네트워크. 테넌트/애플리케이션별 격리
Underlay NetworkVTEP 간 IP 연결을 제공하는 물리적 L3 네트워크
BUM 트래픽Broadcast, Unknown unicast, Multicast — 목적지를 모르는 트래픽. 멀티캐스트 그룹 또는 헤드엔드 복제로 처리
FDB (Forwarding Database)내부 MAC 주소 → 원격 VTEP IP 매핑 테이블. bridge fdb로 관리
Headend Replication멀티캐스트 없이 BUM 트래픽을 알려진 모든 VTEP에 유니캐스트로 복제 전송
오버레이 vs 언더레이: VXLAN의 핵심은 분리(separation)입니다. 오버레이 네트워크의 VM/컨테이너는 물리적 토폴로지를 인식하지 않으며, 동일 VNI 내에서 마치 같은 L2 스위치에 연결된 것처럼 통신합니다. 언더레이 네트워크는 순수 IP 라우팅만 처리하면 되므로, 물리 네트워크 설계가 크게 단순화됩니다.

VXLAN 패킷 포맷

+------ Outer ------+--------+---- VXLAN Header ----+------ Inner ------+
| Outer Eth | Outer IP | UDP  | Flags | VNI (24bit)  | Inner Eth | Payload|
|  14 bytes | 20 bytes |8 byte| 8 bytes              | 14 bytes  |  ...   |
+-------------------------------------------+---------+-------------------+
                                  dst port: 4789 (IANA 할당, 기본값)
                                  src port: 해시 기반 (ECMP 분산용)

Outer Ethernet Header (14 bytes):
  dst MAC : 다음 홉 라우터/VTEP의 MAC
  src MAC : 로컬 VTEP의 물리 NIC MAC
  EtherType: 0x0800 (IPv4) 또는 0x86DD (IPv6)

Outer IP Header (20 bytes / 40 bytes for IPv6):
  src IP  : 로컬 VTEP IP (local 파라미터)
  dst IP  : 원격 VTEP IP (FDB 룩업 결과)
  Protocol: 17 (UDP)

UDP Header (8 bytes):
  src port: 내부 프레임 해시 (ECMP 분산을 위한 엔트로피)
  dst port: 4789 (기본)

VXLAN Header (8 bytes):
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |R|R|R|R|I|R|R|R|         Reserved (24 bits)                   |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |             VNI (24 bits)                   |  Reserved (8)  |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  I (bit 4) : VNI 유효 플래그 (반드시 1)
  VNI       : VXLAN Network Identifier (0 ~ 16,777,215)
  Reserved  : 0으로 설정, 수신 시 무시

Inner Ethernet Frame:
  원본 L2 프레임 그대로 (dst MAC, src MAC, EtherType, payload)
  FCS는 포함하지 않음 (외부 프레임의 FCS가 무결성 보장)

총 오버헤드: 50 bytes (Outer Eth 14 + IP 20 + UDP 8 + VXLAN 8)
            IPv6 언더레이 시: 70 bytes (Outer Eth 14 + IPv6 40 + UDP 8 + VXLAN 8)
소스 포트 엔트로피: VXLAN 외부 UDP 소스 포트는 내부 프레임의 L2/L3/L4 헤더를 해시하여 결정합니다. 이는 언더레이 네트워크의 ECMP(Equal-Cost Multi-Path) 라우팅에서 여러 경로로 트래픽을 분산시키는 데 필수적입니다. Linux에서는 skb_get_hash()를 사용하며, 포트 범위는 커널의 net.ipv4.ip_local_port_range 설정을 따릅니다.

커널 내부 자료구조

/* include/net/vxlan.h — VXLAN 핵심 자료구조 */

/* VXLAN 헤더 (on-wire 형식) */
struct vxlanhdr {
    __be32 vx_flags;   /* VXLAN_HF_VNI (bit 27, 0x08000000) 설정 시 VNI 유효 */
    __be32 vx_vni;     /* 상위 24비트: VNI, 하위 8비트: reserved */
};

/* VXLAN 디바이스 구성 파라미터 */
struct vxlan_config {
    union vxlan_addr  remote_ip;     /* 원격 VTEP IP (유니캐스트 시) */
    union vxlan_addr  saddr;         /* 로컬 VTEP IP */
    __be32            vni;           /* VXLAN Network Identifier */
    int               remote_ifindex;/* 송신 디바이스 ifindex */
    __be16            dst_port;      /* UDP 목적지 포트 (기본 4789) */
    __u16             port_min;      /* 소스 포트 범위 하한 */
    __u16             port_max;      /* 소스 포트 범위 상한 */
    __u8              tos;           /* 외부 IP TOS (1=inherit) */
    __u8              ttl;           /* 외부 IP TTL (0=inherit) */
    __u32             flags;         /* VXLAN_F_* 플래그 */
    __u32             label;         /* IPv6 flow label */
    unsigned short    age_interval;  /* FDB 엔트리 만료 시간 (초) */
    unsigned int      addrmax;       /* FDB 최대 엔트리 수 */
    __u8              df;            /* DF 비트 제어 */
};

/* VXLAN 디바이스 — net_device의 private 데이터 */
struct vxlan_dev {
    struct hlist_node  hlist4;      /* vxlan_net의 해시 테이블 (IPv4) */
    struct hlist_node  hlist6;      /* vxlan_net의 해시 테이블 (IPv6) */
    struct list_head   next;        /* 전역 VXLAN 디바이스 리스트 */
    struct vxlan_sock  __rcu *vn4_sock; /* IPv4 UDP 소켓 */
    struct vxlan_sock  __rcu *vn6_sock; /* IPv6 UDP 소켓 */
    struct net_device  *dev;        /* 연관된 net_device */
    struct vxlan_rdst  default_dst; /* 기본 원격 목적지 */
    struct timer_list  age_timer;   /* FDB 에이징 타이머 */
    struct hlist_head  fdb_head[FDB_HASH_SIZE]; /* FDB 해시 테이블 */
    unsigned int       addrcnt;     /* 현재 FDB 엔트리 수 */
    struct vxlan_config cfg;        /* 디바이스 구성 */
};

/* VXLAN FDB 엔트리 — 내부 MAC → 원격 VTEP 매핑 */
struct vxlan_fdb {
    struct hlist_node hlist;        /* vxlan_dev->fdb_head 해시 체인 */
    struct rcu_head   rcu;          /* RCU 해제 */
    unsigned long     updated;      /* 마지막 갱신 jiffies (에이징 기준) */
    unsigned long     used;         /* 마지막 참조 jiffies */
    struct list_head  remotes;      /* vxlan_rdst 리스트 (다중 VTEP 가능) */
    u8                eth_addr[ETH_ALEN]; /* 내부 MAC 주소 */
    u16               state;        /* NUD_REACHABLE, NUD_STALE 등 */
    __be32            vni;          /* collect_metadata 모드 시 VNI */
    u16               flags;        /* NTF_SELF, NTF_VXLAN_ADDED 등 */
};

/* VXLAN 원격 목적지 (하나의 FDB 엔트리에 여러 VTEP 가능) */
struct vxlan_rdst {
    union vxlan_addr  remote_ip;    /* 원격 VTEP IP */
    __be16            remote_port;  /* 원격 UDP 포트 */
    __be32            remote_vni;   /* 원격 VNI (보통 동일) */
    u32               remote_ifindex; /* 송신 인터페이스 */
    struct list_head  list;         /* vxlan_fdb->remotes 리스트 */
    struct rcu_head   rcu;
};

/* VXLAN UDP 소켓 — 여러 VXLAN 디바이스가 공유 가능 */
struct vxlan_sock {
    struct hlist_node  hlist;      /* 전역 소켓 해시 */
    struct socket      *sock;      /* UDP 소켓 */
    struct hlist_head  vni_list[VNI_HASH_SIZE]; /* VNI별 디바이스 룩업 */
    refcount_t        refcnt;     /* 참조 카운트 */
    u32               flags;      /* VXLAN_F_* 플래그 */
};
소켓 공유: 동일 UDP 포트를 사용하는 여러 VXLAN 디바이스는 하나의 vxlan_sock을 공유합니다. 수신 시 vxlan_sock->vni_list를 통해 VNI별로 올바른 vxlan_dev를 찾아 역캡슐화합니다. 이 설계 덕분에 수천 개의 VNI를 하나의 UDP 소켓으로 효율적으로 처리할 수 있습니다.

VXLAN 디바이스 플래그 (VXLAN_F_*)

플래그ip link 옵션설명
VXLAN_F_LEARN(기본 on)수신 패킷의 inner src MAC → outer src IP를 FDB에 자동 학습
VXLAN_F_PROXYproxyARP/NDP proxy — FDB에 있는 MAC의 ARP 요청에 VTEP이 대리 응답
VXLAN_F_L2MISSl2missFDB miss 시 netlink 알림 발생 (외부 컨트롤러 연동)
VXLAN_F_L3MISSl3missARP 테이블 miss 시 netlink 알림 발생
VXLAN_F_RSCrscRoute Short Circuit — ARP 응답의 MAC을 FDB에 학습
VXLAN_F_COLLECT_METADATAexternal메타데이터 모드 — tc/BPF에서 VNI/목적지를 동적 결정
VXLAN_F_UDP_ZERO_CSUM6_TXudp6zerocsumtxIPv6 송신 시 UDP checksum을 0으로 설정
VXLAN_F_UDP_ZERO_CSUM6_RXudp6zerocsumrxIPv6 수신 시 UDP checksum 0을 허용
VXLAN_F_GBPgbpGroup Based Policy — VXLAN 헤더에 정책 태그 삽입
VXLAN_F_GPEgpeGeneric Protocol Extension — 내부 프로토콜 지정 가능

VXLAN 송신 경로 (Tx Path)

애플리케이션 → socket → TCP/IP 스택 → 내부 프레임 생성
    ↓
vxlan_xmit()                       ← net_device_ops->ndo_start_xmit
    ├─ 내부 프레임의 dst MAC으로 FDB 룩업
    │   ├─ FDB hit  → vxlan_rdst에서 원격 VTEP IP 획득
    │   └─ FDB miss → BUM 처리 (default_dst의 remote 리스트 순회)
    │
    ├─ vxlan_xmit_one()             ← 각 목적지 VTEP별 호출
    │   ├─ 소스 포트 계산: skb_get_hash() 기반 UDP 포트
    │   ├─ VXLAN 헤더 push: vx_flags(I=1) + vx_vni
    │   ├─ UDP 헤더 push: src_port + dst_port(4789)
    │   ├─ IP 라우팅: ip_route_output_key() / ip6_route_output()
    │   └─ udp_tunnel_xmit_skb() / udp_tunnel6_xmit_skb()
    │       └─ ip_local_out() → NF_INET_LOCAL_OUT → 언더레이 라우팅
    ↓
물리 NIC → 와이어
/* drivers/net/vxlan/vxlan_core.c — VXLAN 송신 메인 */
static netdev_tx_t vxlan_xmit(struct sk_buff *skb,
                               struct net_device *dev)
{
    struct vxlan_dev *vxlan = netdev_priv(dev);
    struct ethhdr *eth;
    struct vxlan_fdb *f;
    struct vxlan_rdst *rdst, *fdst;

    eth = eth_hdr(skb);

    /* 1. 내부 프레임의 dst MAC으로 FDB 룩업 */
    f = vxlan_find_mac(vxlan, eth->h_dest, vni);

    if (f) {
        /* FDB hit — 알려진 유니캐스트 목적지 */
        fdst = vxlan_fdb_find_rdst(f, &remote_ip, port, vni, ifindex);
        vxlan_xmit_one(skb, dev, vni, fdst, did_rsc);
    } else if (is_multicast_ether_addr(eth->h_dest)) {
        /* 멀티캐스트/브로드캐스트 — 모든 원격 VTEP에 복제 전송 */
        vxlan_xmit_one(skb, dev, vni, &vxlan->default_dst, 0);
    } else {
        /* Unknown unicast (FDB miss) */
        if (vxlan->cfg.flags & VXLAN_F_L2MISS)
            vxlan_fdb_miss(vxlan, eth->h_dest); /* netlink 알림 */
        /* default_dst의 remote 리스트로 flood */
        vxlan_xmit_one(skb, dev, vni, &vxlan->default_dst, 0);
    }
    return NETDEV_TX_OK;
}

/* 단일 VTEP으로의 캡슐화 및 송신 */
static void vxlan_xmit_one(
    struct sk_buff *skb,
    struct net_device *dev,
    __be32 default_vni,
    struct vxlan_rdst *rdst,
    bool did_rsc)
{
    struct vxlan_dev *vxlan = netdev_priv(dev);
    struct vxlanhdr *vxh;
    struct rtable *rt;
    __be16 src_port, dst_port;
    __be32 vni;

    /* 소스 포트: 내부 프레임 해시 기반 (ECMP 분산) */
    src_port = udp_flow_src_port(dev_net(dev), skb,
                                  vxlan->cfg.port_min,
                                  vxlan->cfg.port_max, true);
    dst_port = rdst->remote_port ? : vxlan->cfg.dst_port;
    vni = rdst->remote_vni ? : default_vni;

    /* IP 라우팅 조회 */
    rt = vxlan_get_route(vxlan, dev, sock4, skb, ...);

    /* VXLAN 헤더 추가 */
    vxh = (struct vxlanhdr *)__skb_push(skb, sizeof(*vxh));
    vxh->vx_flags = htonl(VXLAN_HF_VNI);
    vxh->vx_vni = vxlan_vni_field(vni);

    /* UDP 터널 캡슐화 + IP 송신 */
    udp_tunnel_xmit_skb(rt, sock4->sk, skb,
                        saddr, rdst->remote_ip.sin.sin_addr.s_addr,
                        tos, ttl, df, src_port, dst_port,
                        xnet, !net_eq(vxlan->net, dev_net(dev)));
}

VXLAN 수신 경로 (Rx Path)

물리 NIC → NAPI → netif_receive_skb()
    ↓
IP 프로토콜 처리 → UDP 디멀티플렉싱
    ↓
udp_rcv() → udp_queue_rcv_skb()
    ↓
vxlan_rcv() ← vxlan_sock의 UDP 소켓 콜백 (encap_rcv)
    ├─ 1. VXLAN 헤더 유효성 검사 (I 플래그, 최소 길이)
    ├─ 2. VNI 추출 → vxlan_vs_find_vni()로 vxlan_dev 룩업
    ├─ 3. VXLAN 헤더 pull (skb->data를 inner frame으로 이동)
    ├─ 4. inner src MAC 학습 (VXLAN_F_LEARN 설정 시)
    │      vxlan_snoop() → FDB에 src MAC → outer src IP 매핑 추가/갱신
    ├─ 5. skb 메타데이터 설정
    │      skb->dev = vxlan_dev->dev
    │      skb->protocol = eth_type_trans()
    └─ 6. netif_rx() → 내부 프레임을 네트워크 스택에 재주입
              ↓
         브리지 처리 (vxlan이 bridge port인 경우)
              ↓
         로컬 VM/컨테이너에 전달
/* drivers/net/vxlan/vxlan_core.c — VXLAN 수신 */
static int vxlan_rcv(struct sock *sk,
                      struct sk_buff *skb)
{
    struct vxlan_dev *vxlan;
    struct vxlan_sock *vs;
    struct vxlanhdr *vxh;
    __be32 vni;

    vs = rcu_dereference_sk_user_data(sk);

    /* VXLAN 헤더 파싱 */
    vxh = (struct vxlanhdr *)(udp_hdr(skb) + 1);

    /* I 플래그 검증 */
    if (!(ntohl(vxh->vx_flags) & VXLAN_HF_VNI)) {
        pr_warn_ratelimited("invalid vxlan flags %#x\n",
                            ntohl(vxh->vx_flags));
        goto drop;
    }

    /* VNI 추출 및 VXLAN 디바이스 룩업 */
    vni = vxlan_vni(vxh->vx_vni);
    vxlan = vxlan_vs_find_vni(vs, skb->dev->ifindex, vni);
    if (!vxlan)
        goto drop;

    /* VXLAN 헤더 제거, inner frame 노출 */
    skb_pull(skb, VXLAN_HLEN);  /* 8 bytes */

    /* 소스 MAC 학습 (dynamic FDB entry) */
    if (vxlan->cfg.flags & VXLAN_F_LEARN)
        vxlan_snoop(skb->dev, &saddr, eth_hdr(skb)->h_source,
                    skb->ifindex, vni);

    /* inner frame을 네트워크 스택에 재주입 */
    skb->protocol = eth_type_trans(skb, vxlan->dev);
    netif_rx(skb);

    return 0;
drop:
    kfree_skb(skb);
    return 0;
}

FDB (Forwarding Database) 학습과 관리

VXLAN FDB는 내부 MAC 주소를 원격 VTEP IP로 매핑하는 핵심 테이블입니다. 브리지의 FDB와 개념은 같지만, 목적지가 포트 번호가 아닌 원격 VTEP의 IP 주소라는 점이 다릅니다.

VXLAN FDB 학습 메커니즘:

┌─────────────────┬────────────────────────────────────────────────────┐
│ 방식            │ 동작                                               │
├─────────────────┼────────────────────────────────────────────────────┤
│ 동적 학습       │ VXLAN_F_LEARN 설정 시, 수신 패킷의 inner src MAC  │
│ (Data Plane)    │ → outer src IP를 자동 학습. 에이징 타이머 적용.   │
├─────────────────┼────────────────────────────────────────────────────┤
│ 정적 설정       │ bridge fdb add로 수동 등록.                        │
│ (Static)        │ 에이징 없음, 재부팅 시 재설정 필요.               │
├─────────────────┼────────────────────────────────────────────────────┤
│ nolearning +    │ 동적 학습 비활성화, 모든 매핑을 수동 관리.         │
│ 정적 FDB        │ 대규모 환경에서 FDB 폭증 방지, 보안 향상.         │
├─────────────────┼────────────────────────────────────────────────────┤
│ L2MISS/L3MISS   │ FDB miss 시 netlink 알림 → 외부 컨트롤러가       │
│ + 컨트롤러      │ FDB 엔트리를 동적으로 주입 (SDN 패턴).           │
├─────────────────┼────────────────────────────────────────────────────┤
│ BGP EVPN        │ BGP Type-2 Route로 MAC/IP 바인딩을 분산 학습.     │
│ (Control Plane) │ FRR/BIRD 등이 커널 FDB를 자동 관리.              │
└─────────────────┴────────────────────────────────────────────────────┘
# --- FDB 관리 명령 ---

# 전체 FDB 조회
bridge fdb show dev vxlan100

# 00:00:00:00:00:00 = BUM 트래픽 대상 (헤드엔드 복제 목적지)
bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 10.0.0.2
bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 10.0.0.3
bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 10.0.0.4

# 특정 MAC → VTEP 정적 매핑
bridge fdb add aa:bb:cc:dd:ee:ff dev vxlan100 dst 10.0.0.2 self permanent
bridge fdb del aa:bb:cc:dd:ee:ff dev vxlan100 dst 10.0.0.2

# 동적 학습된 엔트리 확인 (상태: offload/self/permanent)
bridge fdb show dev vxlan100 | column -t

# FDB 에이징 시간 설정 (기본 300초)
ip link set vxlan100 type vxlan ageing 600

# FDB 최대 엔트리 수 제한
ip link set vxlan100 type vxlan maxaddress 1024

# 동적 FDB 플러시 (정적 엔트리는 유지)
bridge fdb flush dev vxlan100 dynamic
/* drivers/net/vxlan/vxlan_core.c — FDB 동적 학습 (수신 시) */
static bool vxlan_snoop(struct net_device *dev,
                        union vxlan_addr *src_ip,
                        const u8 *src_mac,
                        u32 src_ifindex, __be32 vni)
{
    struct vxlan_dev *vxlan = netdev_priv(dev);
    struct vxlan_fdb *f;

    f = __vxlan_find_mac(vxlan, src_mac, vni);
    if (likely(f)) {
        struct vxlan_rdst *rdst = first_remote_rcu(f);

        /* 이미 알려진 MAC — VTEP IP가 변경되었으면 갱신 */
        if (vxlan_addr_equal(&rdst->remote_ip, src_ip))
            return false;  /* 변경 없음 */

        /* MAC 이동 감지 — VTEP 변경 */
        rdst->remote_ip = *src_ip;
        f->updated = jiffies;
    } else {
        /* 신규 MAC 학습 */
        vxlan_fdb_create(vxlan, src_mac, src_ip, NUD_REACHABLE,
                        NTF_SELF, vxlan->cfg.dst_port,
                        vni, vni, src_ifindex,
                        NTF_VXLAN_ADDED, &f);
    }
    return true;
}

BUM 트래픽 처리

BUM(Broadcast, Unknown unicast, Multicast) 트래픽은 VXLAN 환경에서 가장 까다로운 문제입니다. 물리 네트워크에서는 스위치가 BUM 프레임을 모든 포트로 flood하지만, VXLAN 오버레이에서는 VTEP이 어떤 원격 VTEP에 전송해야 하는지 결정해야 합니다.

방식설정장점단점
멀티캐스트 그룹 group 239.1.1.1 자동 VTEP 디스커버리, 설정 간단 언더레이에 멀티캐스트 지원 필요, WAN 환경 제약
헤드엔드 복제
(Ingress Replication)
nolearning +
FDB 00:00:00:00:00:00 엔트리
멀티캐스트 불필요, L3 라우팅만으로 충분 VTEP 수에 비례하는 복제 오버헤드, 수동 관리 필요
BGP EVPN FRR/BIRD + EVPN 설정 자동 VTEP 디스커버리 + MAC 학습, ARP suppression BGP 인프라 필요, 설정 복잡도 높음
SDN 컨트롤러 l2miss + netlink 연동 중앙 집중 제어, 정교한 정책 적용 가능 컨트롤러 가용성 의존, 구현 복잡
# === 방법 1: 멀티캐스트 기반 VXLAN ===
# 동일 멀티캐스트 그룹의 모든 VTEP이 BUM 트래픽을 수신
ip link add vxlan100 type vxlan \
    id 100 \
    group 239.1.1.1 \
    dstport 4789 \
    dev eth0 \
    ttl 10

# 멀티캐스트 그룹 참여 확인
ip maddr show dev eth0

# === 방법 2: 헤드엔드 복제 (유니캐스트 flood) ===
# 멀티캐스트 없이, 알려진 모든 VTEP에 유니캐스트로 복제
ip link add vxlan100 type vxlan \
    id 100 \
    local 10.0.0.1 \
    dstport 4789 \
    nolearning \
    proxy

# BUM 대상 VTEP 등록 (00:00:00:00:00:00 = flood 대상)
bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 10.0.0.2
bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 10.0.0.3
bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 10.0.0.4

# VTEP 제거 (노드 해제 시)
bridge fdb del 00:00:00:00:00:00 dev vxlan100 dst 10.0.0.4

# === 방법 3: L2MISS 알림 기반 (SDN 연동) ===
ip link add vxlan100 type vxlan \
    id 100 \
    local 10.0.0.1 \
    dstport 4789 \
    nolearning \
    l2miss \
    l3miss

# netlink 이벤트 모니터링 (컨트롤러가 수신)
ip monitor neigh dev vxlan100
# 출력 예: miss aa:bb:cc:dd:ee:ff STALE
# → 컨트롤러가 bridge fdb add로 매핑 주입

VXLAN 기본 구성

# --- 유니캐스트 VXLAN (point-to-point) ---
ip link add vxlan100 type vxlan \
    id 100 \
    local 10.0.0.1 \
    remote 10.0.0.2 \
    dstport 4789 \
    dev eth0

ip addr add 192.168.100.1/24 dev vxlan100
ip link set vxlan100 up

# --- 멀티캐스트 VXLAN (BUM 트래픽 자동 학습) ---
ip link add vxlan100 type vxlan \
    id 100 \
    group 239.1.1.1 \
    dstport 4789 \
    dev eth0 \
    ttl 10

# --- 브리지 + VXLAN (L2 확장, 가장 일반적) ---
ip link add br-vxlan type bridge
ip link add vxlan100 type vxlan id 100 local 10.0.0.1 dstport 4789 nolearning
ip link set vxlan100 master br-vxlan
ip link set vxlan100 up
ip link set br-vxlan up

# FDB 엔트리 수동 설정 (헤드엔드 복제)
bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 10.0.0.2
bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 10.0.0.3

# 특정 MAC → VTEP 매핑
bridge fdb add aa:bb:cc:dd:ee:ff dev vxlan100 dst 10.0.0.2

# VXLAN 상태 확인
ip -d link show vxlan100
bridge fdb show dev vxlan100

IPv6 언더레이 VXLAN

VXLAN은 IPv6 언더레이를 완전히 지원합니다. IPv6 언더레이를 사용하면 글로벌 주소 공간을 활용한 대규모 오버레이 네트워크 구성이 가능합니다. 단, IPv6 헤더 크기(40바이트)로 인해 오버헤드가 70바이트로 증가합니다.

# IPv6 언더레이 VXLAN 구성
ip link add vxlan100 type vxlan \
    id 100 \
    local fd00::1 \
    remote fd00::2 \
    dstport 4789 \
    dev eth0 \
    udp6zerocsumtx \
    udp6zerocsumrx

ip addr add 10.200.0.1/24 dev vxlan100
ip link set vxlan100 up

# IPv6 멀티캐스트 VXLAN
ip link add vxlan200 type vxlan \
    id 200 \
    group ff05::100 \
    dstport 4789 \
    dev eth0 \
    ttl 10 \
    udp6zerocsumtx \
    udp6zerocsumrx

# IPv6 + 브리지 조합
ip link add br-v6 type bridge
ip link add vxlan300 type vxlan \
    id 300 \
    local fd00::1 \
    dstport 4789 \
    nolearning \
    udp6zerocsumtx \
    udp6zerocsumrx
ip link set vxlan300 master br-v6
ip link set vxlan300 up
ip link set br-v6 up

bridge fdb append 00:00:00:00:00:00 dev vxlan300 dst fd00::2
bridge fdb append 00:00:00:00:00:00 dev vxlan300 dst fd00::3
UDP checksum과 IPv6: IPv4에서 UDP checksum은 선택사항이지만, IPv6에서는 필수입니다(RFC 6935). udp6zerocsumtx/udp6zerocsumrx 옵션은 RFC 6935/6936에 따라 VXLAN 트래픽의 UDP checksum을 생략하여 성능을 향상시킵니다. NIC의 하드웨어 checksum offload가 지원되면 이 옵션 없이도 성능 저하가 미미합니다.

VXLAN-GPE (Generic Protocol Extension)

VXLAN-GPE(RFC 9136)는 표준 VXLAN을 확장하여 내부 페이로드의 프로토콜을 명시적으로 지정할 수 있게 합니다. 표준 VXLAN은 항상 이더넷 프레임을 캡슐화하지만, VXLAN-GPE는 IPv4, IPv6, NSH(Network Service Header), MPLS 등을 직접 캡슐화할 수 있어 SFC(Service Function Chaining)와 같은 고급 네트워크 기능에 사용됩니다.

VXLAN-GPE Header (8 bytes):
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |R|R|Ver|I|P|B|O|       Reserved                |Next Protocol |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |             VNI (24 bits)                   |  Reserved (8)  |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  Ver (2 bits): 버전 (현재 0)
  I           : VNI 유효 플래그
  P           : Next Protocol 유효 플래그
  B           : BUM 트래픽 비트 (복제 필요 표시)
  O           : OAM 비트 (운용 관리 패킷)
  Next Protocol:
    0x01 = IPv4
    0x02 = IPv6
    0x03 = Ethernet
    0x04 = NSH (Network Service Header)
    0x05 = MPLS
# VXLAN-GPE 디바이스 생성
ip link add vxlan-gpe0 type vxlan \
    id 500 \
    local 10.0.0.1 \
    remote 10.0.0.2 \
    dstport 4790 \
    gpe

# GPE는 IANA 포트 4790 사용 (표준 VXLAN은 4789)
# collect_metadata(external) 모드와 결합하여 BPF에서 활용 가능
ip link add vxlan-gpe1 type vxlan \
    external \
    dstport 4790 \
    gpe
/* include/net/vxlan.h — GPE 관련 정의 */
struct vxlanhdr_gpe {
    __u8   vx_flags;       /* Ver=0, I, P, B, O 플래그 */
    __u8   reserved[2];
    __u8   np;             /* Next Protocol */
    __be32 vx_vni;         /* VNI << 8 */
};

#define VXLAN_GPE_NP_IPV4   0x01
#define VXLAN_GPE_NP_IPV6   0x02
#define VXLAN_GPE_NP_ETHERNET 0x03
#define VXLAN_GPE_NP_NSH    0x04
#define VXLAN_GPE_NP_MPLS   0x05

VXLAN GBP (Group Based Policy)

VXLAN GBP(draft-smith-vxlan-group-policy)는 VXLAN 헤더의 예약 필드를 활용하여 보안 그룹 태그를 전달합니다. 소스/목적지 그룹 ID를 기반으로 마이크로세그멘테이션 정책을 적용할 수 있으며, Cilium 등의 CNI에서 네트워크 정책 적용에 사용합니다.

VXLAN GBP Header (8 bytes):
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |G|R|R|R|I|R|R|R|R|D|R|R|A|R|R|R| Group Policy ID (16 bits)   |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |             VNI (24 bits)                   |  Reserved (8)  |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  G  : GBP Extension 활성화
  D  : Don't Learn 비트 (수신측 FDB 학습 억제)
  A  : Policy Applied 비트 (정책 이미 적용됨)
  Group Policy ID: 16비트 보안 그룹 식별자 (0~65535)
# GBP 활성화된 VXLAN 생성
ip link add vxlan-gbp type vxlan \
    id 100 \
    local 10.0.0.1 \
    dstport 4789 \
    gbp

# iptables/nftables에서 GBP 태그 기반 필터링
iptables -A FORWARD -m set --match-set allowed_groups src \
    -j ACCEPT
iptables -A FORWARD -j DROP

VXLAN + EVPN (BGP Control Plane)

EVPN(Ethernet VPN, RFC 7432)은 BGP를 Control Plane으로 사용하여 VXLAN의 MAC/IP 학습, VTEP 디스커버리, BUM 최적화를 자동화합니다. 데이터 플레인의 flood & learn 방식을 대체하여 확장성과 수렴 시간을 크게 개선합니다.

EVPN Route Types (VXLAN 관련):

Type-2 (MAC/IP Advertisement):
  - MAC 주소 + IP 주소 + VNI를 BGP로 광고
  - 수신 VTEP이 FDB에 자동 등록 (data plane 학습 불필요)
  - ARP suppression: VTEP이 알려진 IP의 ARP 요청에 대리 응답

Type-3 (Inclusive Multicast Ethernet Tag):
  - VTEP의 존재를 광고 (BUM 트래픽 대상 목록 자동 구축)
  - Ingress Replication 리스트를 자동 관리

Type-5 (IP Prefix Route):
  - L3 VXLAN (Symmetric IRB): VNI 간 라우팅을 위한 IP prefix 광고
  - 테넌트별 VRF + L3 VNI를 통한 인터-서브넷 라우팅

EVPN 동작 흐름:
  VM-A boots → VTEP-A가 MAC/IP 학습 → Type-2 BGP Update 전파
      → VTEP-B가 수신 → 커널 FDB에 자동 추가
      → VM-B → VM-A 통신 시 data plane flood 불필요
# === FRR (Free Range Routing)을 이용한 EVPN 설정 예시 ===

# 1. 커널: VXLAN + 브리지 구성 (nolearning 필수)
ip link add br100 type bridge
ip link add vxlan100 type vxlan \
    id 100 \
    local 10.0.0.1 \
    dstport 4789 \
    nolearning

ip link set vxlan100 master br100
ip link set vxlan100 up
ip link set br100 up

# L3 VNI (대칭 IRB 라우팅용)
ip link add br-l3vni type bridge
ip link add vxlan-l3 type vxlan \
    id 9999 \
    local 10.0.0.1 \
    dstport 4789 \
    nolearning

ip link set vxlan-l3 master br-l3vni
ip link set vxlan-l3 up
ip link set br-l3vni up

# VRF 생성
ip link add vrf-tenant1 type vrf table 100
ip link set vrf-tenant1 up
ip link set br100 master vrf-tenant1
ip link set br-l3vni master vrf-tenant1
# 2. FRR 설정 (vtysh / frr.conf)

router bgp 65001
  bgp router-id 10.0.0.1
  no bgp default ipv4-unicast
  neighbor SPINE peer-group
  neighbor SPINE remote-as 65100
  neighbor 10.0.0.254 peer-group SPINE
  !
  address-family l2vpn evpn
    neighbor SPINE activate
    advertise-all-vni
  exit-address-family
!
router bgp 65001 vrf tenant1
  !
  address-family ipv4 unicast
    redistribute connected
  exit-address-family
  !
  address-family l2vpn evpn
    advertise ipv4 unicast
  exit-address-family
!
vni 100
  rd 10.0.0.1:100
  route-target import 65001:100
  route-target export 65001:100
# 3. EVPN 상태 확인 (vtysh)
show bgp l2vpn evpn summary
show bgp l2vpn evpn route type macip
show bgp l2vpn evpn route type multicast
show bgp l2vpn evpn route type prefix
show evpn vni
show evpn mac vni 100
show evpn arp-cache vni 100

# 커널 FDB에 BGP가 주입한 엔트리 확인
bridge fdb show dev vxlan100 | grep offload
# 출력 예: aa:bb:cc:dd:ee:ff dev vxlan100 dst 10.0.0.2 self offload
ARP Suppression: EVPN Type-2 경로에 IP가 포함되면, VTEP은 알려진 MAC/IP에 대한 ARP 요청을 로컬에서 대리 응답합니다. 이로써 오버레이 네트워크의 ARP 브로드캐스트 트래픽이 크게 감소하며, 대규모 환경에서 성능이 향상됩니다. bridge link set dev vxlan100 neigh_suppress on으로 활성화합니다.

collect_metadata (external) 모드

collect_metadata(external) 모드는 단일 VXLAN 디바이스로 여러 VNI를 처리할 수 있게 합니다. VNI와 목적지 VTEP을 패킷별로 동적으로 결정하며, tc-flower, BPF, OVS(Open vSwitch) 등의 프로그래머블 데이터 플레인에서 사용됩니다.

# external 모드 VXLAN (VNI/목적지를 tc/BPF에서 결정)
ip link add vxlan-ext type vxlan \
    external \
    dstport 4789

ip link set vxlan-ext up

# tc-flower로 트래픽 제어 (VNI/목적지 동적 설정)
tc qdisc add dev vxlan-ext clsact
tc filter add dev vxlan-ext egress \
    flower dst_mac aa:bb:cc:dd:ee:ff \
    action tunnel_key set \
        id 100 \
        src_ip 10.0.0.1 \
        dst_ip 10.0.0.2 \
        dst_port 4789 \
    action mirred egress redirect dev vxlan-ext
/* BPF에서 VXLAN 메타데이터 설정 (tc BPF 프로그램) */
SEC("tc")
int vxlan_encap(struct __sk_buff *skb)
{
    struct bpf_tunnel_key key = {};

    key.tunnel_id = 100;          /* VNI */
    key.remote_ipv4 = 0x0A000002; /* 10.0.0.2 */
    key.tunnel_tos = 0;
    key.tunnel_ttl = 64;

    bpf_skb_set_tunnel_key(skb, &key, sizeof(key),
                           BPF_F_ZERO_CSUM_TX);
    return TC_ACT_OK;
}

/* 수신 측: 터널 메타데이터 읽기 */
SEC("tc")
int vxlan_decap(struct __sk_buff *skb)
{
    struct bpf_tunnel_key key = {};

    bpf_skb_get_tunnel_key(skb, &key, sizeof(key), 0);
    /* key.tunnel_id = VNI, key.remote_ipv4 = 소스 VTEP */

    /* VNI 기반 정책 적용 */
    if (key.tunnel_id == 100)
        return TC_ACT_OK;
    return TC_ACT_SHOT;
}

성능 최적화

MTU 설정

MTU 주의: VXLAN은 50바이트(IPv4) 또는 70바이트(IPv6)의 오버헤드를 추가합니다. 내부 MTU가 1500이면 물리 NIC의 MTU는 최소 1550(IPv4) 또는 1570(IPv6) 이상이어야 합니다. 점보 프레임을 강력히 권장합니다.
# 권장: 물리 NIC에 점보 프레임 설정
ip link set eth0 mtu 9000

# VXLAN 디바이스 MTU (자동 계산되지만 명시 설정 권장)
ip link set vxlan100 mtu 8950   # 9000 - 50 (VXLAN overhead)

# Path MTU Discovery 문제 시 DF 비트 제어
ip link add vxlan100 type vxlan id 100 ... df unset  # DF=0 (fragmentation 허용)
ip link add vxlan100 type vxlan id 100 ... df set     # DF=1 (PMTUD 활성화)
ip link add vxlan100 type vxlan id 100 ... df inherit # inner IP의 DF 상속

NIC 오프로드

# NIC의 VXLAN 오프로드 지원 확인
ethtool -k eth0 | grep -E "(tx-udp_tnl|rx-udp_tnl|vxlan)"

# 일반적인 오프로드 기능:
#   tx-udp_tnl-segmentation     : VXLAN TSO (내부 TCP 세그멘테이션)
#   tx-udp_tnl-csum-segmentation: TSO + 외부 UDP checksum
#   rx-udp-gro-forwarding       : UDP GRO (VXLAN 수신 병합)

# 오프로드 포트 등록 확인
ethtool --show-tunnels eth0
# 출력 예:
#   port 4789, VXLAN

# GRO 활성화 (수신 성능 향상)
ethtool -K eth0 rx-udp-gro-forwarding on

# VXLAN 통계 확인
ip -s link show dev vxlan100
# ethtool 카운터 (NIC별 상이)
ethtool -S eth0 | grep -i vxlan
/* drivers/net/vxlan/vxlan_core.c — NIC에 오프로드 포트 등록 */
static void vxlan_push_rx_ports(struct net_device *dev)
{
    struct vxlan_sock *vs;

    /* 물리 NIC에 VXLAN UDP 포트 알림 → NIC이 하드웨어 파싱 수행 */
    rcu_read_lock();
    hlist_for_each_entry_rcu(vs, &vn->sock_list[0], hlist)
        udp_tunnel_push_rx_port(dev, vs->sock,
                                UDP_TUNNEL_TYPE_VXLAN);
    rcu_read_unlock();
}

/* NIC 드라이버 예시: VXLAN 오프로드 포트 등록 콜백 */
static void nic_add_vxlan_port(struct net_device *dev,
                                struct udp_tunnel_info *ti)
{
    /* NIC ASIC에 VXLAN 포트 프로그래밍 */
    /* → 하드웨어가 외부 헤더를 파싱하여 inner payload를 직접 처리 */
    /* → RSS (내부 5-tuple 기반), checksum offload, TSO 가능 */
    hw_register_vxlan_port(adapter, ntohs(ti->port));
}

커널 튜닝 파라미터

# VXLAN 관련 sysctl 튜닝

# UDP 수신 버퍼 확대 (대량 VXLAN 트래픽)
sysctl -w net.core.rmem_max=26214400
sysctl -w net.core.rmem_default=26214400

# conntrack이 필요 없는 경우 VXLAN 인터페이스에서 비활성화
iptables -t raw -A PREROUTING -i vxlan+ -j NOTRACK
iptables -t raw -A OUTPUT -o vxlan+ -j NOTRACK

# VXLAN FDB 에이징 최적화
ip link set vxlan100 type vxlan ageing 300   # 기본값

# 네트워크 네임스페이스간 이동 허용
sysctl -w net.core.netns_local=0

VXLAN 트러블슈팅

# === 1단계: VXLAN 디바이스 상태 확인 ===
ip -d link show vxlan100
# 확인 항목: id(VNI), group/remote, local, dstport, learning, proxy

# === 2단계: FDB 테이블 검증 ===
bridge fdb show dev vxlan100
# 필수 확인: 00:00:00:00:00:00 엔트리 (BUM flood 대상)
# permanent = 정적, offload = EVPN/HW 주입

# === 3단계: 언더레이 연결성 확인 ===
# VTEP IP 간 ping (기본 연결 확인)
ping 10.0.0.2

# UDP 4789 도달 가능 여부
ncat -u 10.0.0.2 4789

# 방화벽 확인 (VXLAN UDP 포트)
iptables -L -n -v | grep 4789
nft list ruleset | grep 4789

# === 4단계: 패킷 캡처 ===
# 외부 캡슐화된 VXLAN 패킷 (언더레이)
tcpdump -i eth0 -nn "udp port 4789" -c 10

# 내부 프레임 (오버레이, VTEP에서 역캡슐화 후)
tcpdump -i vxlan100 -nn -c 10

# VXLAN 헤더까지 상세 출력
tcpdump -i eth0 -nn -e -v "udp port 4789"

# === 5단계: 통계 및 카운터 ===
ip -s link show dev vxlan100
# TX/RX packets, bytes, errors, dropped 확인

# VXLAN 전용 통계
nstat -a | grep -i Vxlan
# VxlanInPkts, VxlanOutPkts, VxlanInErrors 등

# ethtool 확장 통계 (NIC별)
ethtool -S eth0 | grep -i vxlan

# === 6단계: 일반적인 문제와 해결 ===

# 문제: ping은 되지만 TCP 연결 안됨 → MTU 문제
# 해결:
ip link set eth0 mtu 9000
ip link set vxlan100 mtu 8950
# 또는 inner의 MSS 조정:
iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
    -j TCPMSS --clamp-mss-to-pmtu

# 문제: 멀티캐스트 VXLAN에서 BUM 전달 안됨
# 확인: 언더레이 멀티캐스트 라우팅
ip mroute show
ip maddr show dev eth0
# 스위치의 IGMP snooping 설정 확인

# 문제: EVPN FDB 동기화 안됨
# 확인: BGP 세션 상태
vtysh -c "show bgp l2vpn evpn summary"
vtysh -c "show bgp l2vpn evpn route type macip"

# 문제: MAC flapping 감지
dmesg | grep -i "moved from"
# bridge: vxlan100: xx:xx:xx:xx:xx:xx moved from port X to port Y
tcpdump 팁: Wireshark에서 VXLAN 패킷을 분석할 때, udp.port == 4789 필터로 캡처한 후 Decode As → VXLAN을 선택하면 내부 이더넷 프레임까지 파싱됩니다. tshark에서는 tshark -d udp.port==4789,vxlan -r capture.pcap으로 디코딩할 수 있습니다.

switchdev (하드웨어 오프로드)

switchdev는 소프트웨어 네트워크 기능(Bridge, VLAN, FDB, 라우팅)을 하드웨어 스위치 ASIC으로 오프로드하는 커널 프레임워크입니다. Memory/Memory, Memory/Memory Mapped, DSA(Distributed Switch Architecture) 방식을 지원합니다.

/* net/switchdev/switchdev.c — switchdev 알림 체인 */

/* 브리지가 FDB를 추가/삭제할 때 switchdev에 통보 */
enum switchdev_notifier_type {
    SWITCHDEV_FDB_ADD_TO_DEVICE,
    SWITCHDEV_FDB_DEL_TO_DEVICE,
    SWITCHDEV_FDB_ADD_TO_BRIDGE,
    SWITCHDEV_FDB_DEL_TO_BRIDGE,
    SWITCHDEV_PORT_OBJ_ADD,       /* VLAN, MDB 등 */
    SWITCHDEV_PORT_OBJ_DEL,
    SWITCHDEV_PORT_ATTR_SET,       /* STP state, learning 등 */
};

/* 드라이버가 구현하는 switchdev 콜백 */
struct switchdev_ops {
    int (*switchdev_port_attr_set)(
        struct net_device *dev,
        const struct switchdev_attr *attr);
    int (*switchdev_port_obj_add)(
        struct net_device *dev,
        const struct switchdev_obj *obj);
    int (*switchdev_port_obj_del)(
        struct net_device *dev,
        const struct switchdev_obj *obj);
};
switchdev 지원 드라이버: Mellanox/NVIDIA mlxsw (Spectrum ASIC), Marvell mv88e6xxx (DSA), Broadcom rocker, Microchip ksz 등이 switchdev를 지원합니다. bridge, 8021q, FDB, 라우팅 테이블을 하드웨어로 오프로드하여 와이어레이트 처리가 가능합니다.

실전 구성 예제

컨테이너 네트워크 (Docker-style)

일반적인 컨테이너 네트워크는 브리지 + veth + iptables NAT 조합으로 구성됩니다.

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

# 2. veth 쌍 생성 → 컨테이너 네임스페이스에 연결
ip link add veth-host type veth peer name veth-ct
ip link set veth-host master docker0
ip link set veth-host up

# 3. 컨테이너 네임스페이스 설정
ip netns add container1
ip link set veth-ct netns container1

ip netns exec container1 ip addr add 172.17.0.2/16 dev veth-ct
ip netns exec container1 ip link set veth-ct up
ip netns exec container1 ip link set lo up
ip netns exec container1 ip route add default via 172.17.0.1

# 4. NAT 설정 (컨테이너 → 외부 인터넷)
iptables -t nat -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
sysctl -w net.ipv4.ip_forward=1

# 5. 포트 포워딩 (외부 → 컨테이너)
iptables -t nat -A PREROUTING -p tcp --dport 8080 \
    -j DNAT --to-destination 172.17.0.2:80

VLAN-aware 브리지 (가상화 환경)

하이퍼바이저에서 VM을 VLAN별로 분리하는 구성입니다.

# 1. VLAN-aware 브리지 생성
ip link add br0 type bridge vlan_filtering 1 vlan_default_pvid 0
ip link set br0 up

# 2. 물리 NIC를 trunk 포트로 (VLAN 10, 20 tagged)
ip link set eth0 master br0
bridge vlan add vid 10 dev eth0
bridge vlan add vid 20 dev eth0

# 3. VM1의 tap을 VLAN 10 access 포트로
ip link set tap-vm1 master br0
bridge vlan add vid 10 dev tap-vm1 pvid untagged

# 4. VM2의 tap을 VLAN 20 access 포트로
ip link set tap-vm2 master br0
bridge vlan add vid 20 dev tap-vm2 pvid untagged

# 5. 브리지에 VLAN IP 할당 (관리용)
bridge vlan add vid 10 dev br0 self
ip addr add 192.168.10.1/24 dev br0

# 확인
bridge vlan show

Bonding + VLAN 구성

# 1. Bonding 생성 (LACP)
ip link add bond0 type bond mode 802.3ad \
    miimon 100 lacp_rate fast xmit_hash_policy layer3+4

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

# 2. Bond 위에 VLAN 서브인터페이스 생성
ip link add link bond0 name bond0.10 type vlan id 10
ip link add link bond0 name bond0.20 type vlan id 20

ip addr add 192.168.10.1/24 dev bond0.10
ip addr add 192.168.20.1/24 dev bond0.20

ip link set bond0.10 up
ip link set bond0.20 up

VXLAN 오버레이 네트워크

# --- 노드 A (10.0.0.1) ---
ip link add br-overlay type bridge
ip link add vxlan42 type vxlan \
    id 42 local 10.0.0.1 dstport 4789 nolearning

ip link set vxlan42 master br-overlay
ip link set vxlan42 up
ip link set br-overlay up

# 원격 VTEP 등록
bridge fdb append 00:00:00:00:00:00 dev vxlan42 dst 10.0.0.2
bridge fdb append 00:00:00:00:00:00 dev vxlan42 dst 10.0.0.3

# 컨테이너 veth를 오버레이 브리지에 연결
ip link add veth-a type veth peer name veth-a-br
ip link set veth-a-br master br-overlay
ip link set veth-a-br up

ip netns add overlay-ns
ip link set veth-a netns overlay-ns
ip netns exec overlay-ns ip addr add 10.200.0.1/24 dev veth-a
ip netns exec overlay-ns ip link set veth-a up

# --- 노드 B (10.0.0.2) ---
# 동일 구성, local=10.0.0.2, IP=10.200.0.2/24

# 결과: 10.200.0.1 ↔ 10.200.0.2가 L3 네트워크를 통해 L2 통신

MACVLAN 컨테이너 네트워킹

# 브리지 없이 컨테이너에 직접 네트워크 연결 (MACVLAN)

# 1. MACVLAN 인터페이스 생성 (bridge 모드)
ip link add macvlan-ct1 link eth0 type macvlan mode bridge
ip link add macvlan-ct2 link eth0 type macvlan mode bridge

# 2. 네임스페이스로 이동
ip netns add ct1
ip netns add ct2

ip link set macvlan-ct1 netns ct1
ip link set macvlan-ct2 netns ct2

# 3. IP 설정 (물리 네트워크와 동일 서브넷)
ip netns exec ct1 ip addr add 192.168.1.101/24 dev macvlan-ct1
ip netns exec ct1 ip link set macvlan-ct1 up
ip netns exec ct1 ip route add default via 192.168.1.1

ip netns exec ct2 ip addr add 192.168.1.102/24 dev macvlan-ct2
ip netns exec ct2 ip link set macvlan-ct2 up
ip netns exec ct2 ip route add default via 192.168.1.1

# 장점: 브리지 오버헤드 없이 물리 네트워크에 직접 연결
# 단점: 호스트 eth0 ↔ MACVLAN 간 직접 통신 불가 (bridge 모드에서도)

유용한 진단 명령

# 가상 디바이스 전체 목록
ip -d link show type bridge
ip -d link show type vlan
ip -d link show type bond
ip -d link show type veth
ip -d link show type vxlan
ip -d link show type macvlan

# 브리지 포트 및 상태
bridge link show
bridge -d link show

# FDB 테이블 (MAC 학습 현황)
bridge fdb show
bridge fdb show dev br0 | sort

# VLAN 필터 현황
bridge vlan show
bridge -json vlan show

# Bonding 상태 (LACP 포함)
cat /proc/net/bonding/bond0

# 네트워크 네임스페이스 목록
ip netns list

# 특정 네임스페이스에서 명령 실행
ip netns exec ns1 ip addr show
ip netns exec ns1 ping 10.0.0.1

# VXLAN FDB (원격 VTEP 매핑)
bridge fdb show dev vxlan100

# 네트워크 통계
ip -s link show dev bond0
ip -s link show dev vxlan100
nstat -a | grep -i vxlan

# ethtool로 오프로드 상태 확인
ethtool -k eth0 | grep -i vlan
ethtool -k eth0 | grep -i offload
디버깅 팁: ip monitor 명령으로 네트워크 이벤트를 실시간 모니터링할 수 있습니다. ip monitor link(링크 상태), ip monitor neigh(ARP/NDP), ip monitor route(라우팅)를 활용하면 가상 네트워크 디바이스의 동작을 추적하는 데 유용합니다.

관련 커널 설정

# Bridge
CONFIG_BRIDGE=m
CONFIG_BRIDGE_VLAN_FILTERING=y
CONFIG_BRIDGE_IGMP_SNOOPING=y

# VLAN
CONFIG_VLAN_8021Q=m
CONFIG_VLAN_8021Q_GVRP=y
CONFIG_VLAN_8021Q_MVRP=y

# Bonding / Team
CONFIG_BONDING=m
CONFIG_NET_TEAM=m
CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=m
CONFIG_NET_TEAM_MODE_LOADBALANCE=m
CONFIG_NET_TEAM_MODE_ROUNDROBIN=m
CONFIG_NET_TEAM_MODE_BROADCAST=m

# MACVLAN / IPVLAN
CONFIG_MACVLAN=m
CONFIG_MACVTAP=m
CONFIG_IPVLAN=m
CONFIG_IPVTAP=m

# veth
CONFIG_VETH=m

# TUN/TAP
CONFIG_TUN=m
CONFIG_TAP=m

# VXLAN
CONFIG_VXLAN=m

# switchdev
CONFIG_NET_SWITCHDEV=y