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까지 실무 관점으로 다룹니다.
Bonding 개요
Linux Bonding은 여러 물리 NIC를 하나의 논리 인터페이스로 묶어 이중화(redundancy)와 부하 분산(load balancing)을 제공합니다. bonding 커널 모듈(drivers/net/bonding/)로 구현되며, 7가지 모드를 지원합니다. 커널 2.0부터 존재해온 가장 오래된 NIC 집계 기술로, 현재까지 가장 널리 사용됩니다.
Bonding 모드
| 모드 | 이름 | 설명 | 스위치 설정 | 최대 대역폭 |
|---|---|---|---|---|
| 0 | balance-rr | 라운드 로빈: 패킷을 순서대로 분산 | 필요 (정적 EtherChannel) | N × link speed (TX+RX) |
| 1 | active-backup | Active-Standby: 활성 슬레이브 하나만 사용 | 불필요 | 1 × link speed |
| 2 | balance-xor | XOR 해시: xmit_hash_policy 기반 분산 | 필요 (정적 EtherChannel) | N × link speed (TX+RX) |
| 3 | broadcast | 모든 슬레이브로 동일 패킷 전송 (fault tolerance용) | 필요 | 1 × link speed (TX), N × (RX) |
| 4 | 802.3ad | LACP: IEEE 802.3ad 동적 링크 집계 | 필요 (LACP 지원) | N × link speed (TX+RX) |
| 5 | balance-tlb | 송신 부하 분산 (Adaptive TLB) | 불필요 | N × link speed (TX), 1 × (RX) |
| 6 | balance-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
802.3ad 모드에서는 lacp_rate fast(1초 간격 LACPDU)와 xmit_hash_policy layer3+4를 권장합니다. 스위치 측에서도 LACP를 활성화하고 동일한 해시 알고리즘을 설정해야 합니다.
Bonding 모드별 심화
각 모드는 bond_xmit_*() 함수와 별도의 수신 처리 로직을 가집니다. 커널 소스에서 모드 선택은 bond_set_mode_ops()가 담당합니다.
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-xor나 802.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 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;
};
802.3ad LACP 프로토콜 심화
LACP(Link Aggregation Control Protocol)는 양쪽 장비가 주기적으로 LACPDU(LACP Data Unit)를 교환하여 링크 집계 그룹(LAG)을 동적으로 형성하고 유지합니다. 커널 구현은 drivers/net/bonding/bond_3ad.c에 있습니다.
LACP 상태 머신
Linux bonding의 LACP 구현은 IEEE 802.3ad 부속서 43에 정의된 3개의 상태 머신을 구현합니다:
| 상태 머신 | 역할 | 커널 함수 |
|---|---|---|
| Receive Machine | LACPDU 수신 처리, 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_rate fast: LACPDU를 매 1초 전송, 타임아웃 3초. 장애 감지가 빠르지만 CPU/대역폭 소비 약간 증가lacp_rate slow: LACPDU를 매 30초 전송, 타임아웃 90초. 리소스 절약되지만 장애 감지 느림- 프로덕션 환경에서는
fast를 권장 — 스위치 장애 시 3초 이내 감지 가능
장애 감지와 페일오버
Bonding은 두 가지 링크 모니터링 방식을 제공합니다: MII 모니터링(L1/L2 수준)과 ARP 모니터링(L3 수준). 두 방식은 동시 사용이 불가합니다.
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 | ARP |
|---|---|---|
| 감지 계층 | 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)에서 패킷을 어떤 슬레이브로 보낼지 결정하는 해시 함수를 선택합니다. 해시 결과를 슬레이브 수로 모듈러 연산하여 대상 슬레이브를 결정합니다.
/* 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로 슬레이브 선택 */
}
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의 권장 대안으로 소개되었습니다.
| 항목 | Bonding | Team |
|---|---|---|
| 설정 인터페이스 | sysfs / module params | Netlink / 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 모드
| 모드 | MACVLAN 간 통신 | 외부 통신 | 설명 |
|---|---|---|---|
bridge | O (직접) | O | MACVLAN 간 내부 브리징 지원 |
vepa | O (외부 스위치 경유) | O | 모든 트래픽이 외부 스위치를 통과 |
private | X | O | MACVLAN 간 완전 격리 |
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: 각 컨테이너에 고유 MAC이 필요한 경우, 802.1Q VLAN과 결합할 때
- IPVLAN: MAC 주소 수 제한이 있는 환경 (일부 클라우드), Netfilter 규칙 공유 시 (L3S 모드)
veth pair (가상 이더넷 쌍)
veth는 항상 쌍(pair)으로 생성되는 가상 이더넷 디바이스입니다. 한쪽에 전송된 패킷은 반대쪽에서 수신됩니다. Docker, Kubernetes, LXC 등 컨테이너 네트워킹의 기본 빌딩 블록이며, 네트워크 네임스페이스 간 통신의 핵심입니다.
# 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_rcv() 경로를 통해 peer로부터 수신된 패킷에 XDP 프로그램을 적용할 수 있어, 컨테이너 네트워킹에서 고성능 패킷 처리가 가능합니다. 또한 GRO(Generic Receive Offload)도 지원합니다.
TUN/TAP
TUN은 L3(IP) 수준, TAP은 L2(이더넷) 수준에서 유저스페이스와 커널 간 패킷 I/O를 제공합니다. VPN(OpenVPN, WireGuard), 가상화(QEMU/KVM), 네트워크 시뮬레이션에 사용됩니다.
| 항목 | TUN | TAP |
|---|---|---|
| 계층 | L3 (IP 패킷) | L2 (이더넷 프레임) |
| 용도 | 라우팅 기반 VPN | 브리징, VM NIC |
| 디바이스 파일 | /dev/net/tun | |
| 생성 플래그 | IFF_TUN | IFF_TAP |
TUN/TAP 아키텍처
TUN/TAP 디바이스는 drivers/net/tun.c에서 구현됩니다. 유저스페이스 프로세스가 /dev/net/tun 캐릭터 디바이스를 열고 TUNSETIFF ioctl로 가상 인터페이스를 생성하면, 커널은 tun_struct와 연결된 net_device를 등록합니다. 이후 유저스페이스는 파일 디스크립터의 read()/write()를 통해 커널 네트워크 스택과 직접 패킷을 교환합니다.
커널 자료 구조
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_file과 sk_receive_queue를 가지므로 락 경합 없이 동시 I/O가 가능합니다.
주요 플래그 및 ioctl
/dev/net/tun을 열고 TUNSETIFF ioctl을 호출할 때 ifreq.ifr_flags에 설정하는 플래그로 디바이스 동작을 제어합니다.
| 플래그 | 값 | 설명 |
|---|---|---|
IFF_TUN | 0x0001 | L3 (IP) TUN 디바이스 생성 |
IFF_TAP | 0x0002 | L2 (Ethernet) TAP 디바이스 생성 |
IFF_NO_PI | 0x1000 | PI(Packet Info) 헤더 생략 — 순수 패킷만 전달 |
IFF_VNET_HDR | 0x4000 | virtio-net 헤더 포함 (GSO/checksum offload) |
IFF_MULTI_QUEUE | 0x0100 | 멀티큐 모드 활성화 |
IFF_PERSIST | 0x0800 | fd 닫아도 디바이스 유지 |
IFF_NOFILTER | 0x1000 | 패킷 필터 비활성화 |
IFF_NO_PI를 설정하지 않으면 각 패킷 앞에 4바이트 struct tun_pi가 붙습니다 — flags(2바이트, 예: TUN_PKT_STRIP)와 proto(2바이트, ETH_P_IP 등). 대부분의 애플리케이션은 IFF_NO_PI를 설정하여 순수 패킷만 교환합니다.
디바이스 생성 후 추가 설정을 위한 ioctl 명령입니다.
| ioctl 명령 | 인자 | 설명 |
|---|---|---|
TUNSETIFF | struct ifreq * | 디바이스 생성/연결 (이름 + 플래그 설정) |
TUNSETOWNER | uid_t | 디바이스 소유자 UID 설정 |
TUNSETGROUP | gid_t | 디바이스 그룹 GID 설정 |
TUNSETPERSIST | int | 0: 비영구, 1: fd 닫아도 디바이스 유지 |
TUNSETOFFLOAD | unsigned long | offload 기능 설정 (TUN_F_CSUM, TUN_F_TSO4 등) |
TUNSETVNETHDRSZ | int | virtio-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;
}
poll()/epoll()/select()를 완벽히 지원합니다. POLLIN은 sk_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
/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 주소를 공유합니다.
| 항목 | L2 | L3 | L3S |
|---|---|---|---|
| 패킷 처리 계층 | 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 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
- 일반 경로: ~3~5 Mpps (NAPI 미사용, softirq 기반)
- GRO 활성화: ~8~12 Mpps (대용량 패킷 병합)
- XDP: ~15~24 Mpps (커널 스택 우회, 제로카피)
- XDP_REDIRECT: 컨테이너 간 직접 전달로 브리지 오버헤드 제거
- Cilium, Calico 등 CNI 플러그인이 veth+XDP 조합을 활용
가상 NIC 성능 비교
각 가상 네트워크 인터페이스의 성능과 오버헤드를 비교합니다. 실제 성능은 하드웨어, 커널 버전, 워크로드에 따라 달라지지만, 상대적 순위와 특성은 일관됩니다.
| 인터페이스 | TX 오버헤드 | RX 오버헤드 | Throughput (상대) | Latency | XDP 지원 |
|---|---|---|---|---|---|
| MACVLAN | 매우 낮음 | MAC hash lookup | 95~98% | 최소 | O (passthru) |
| IPVLAN L3 | 낮음 | IP lookup | 93~97% | 최소 | O |
| veth + bridge | 중간 | bridge FDB lookup | 80~90% | 낮음 | O |
| veth + XDP | 낮음 | XDP native | 90~95% | 최소 | O (native) |
| TAP + vhost-net | 중간 | vhost worker | 70~85% | 중간 | X |
| TAP (userspace) | 높음 | 유저 read() | 40~60% | 높음 | X |
| TUN | 높음 | 유저 read() | 40~60% | 높음 | X |
| 모드 | 단일 플로우 대역폭 | 다중 플로우 대역폭 | CPU 오버헤드 | 페일오버 속도 |
|---|---|---|---|---|
| active-backup | 1×link | 1×link | 최소 | 100ms~300ms |
| balance-rr | N×link (재정렬 있음) | N×link | 낮음 | 100ms~300ms |
| balance-xor | 1×link | N×link | 낮음 (해시) | 100ms~300ms |
| 802.3ad | 1×link | N×link | 중간 (LACP) | 1s~3s (LACP) |
| balance-tlb | 1×link | N×link (TX) | 중간 (리밸런싱) | 100ms~300ms |
| balance-alb | 1×link | N×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 주소 | 네임스페이스 이동 | 주요 사용처 |
|---|---|---|---|---|---|
| Bonding | NIC 이중화/집계 | L2 | 단일 (bond) | 가능 | 서버 HA, 대역폭 집계 |
| Team | NIC 이중화/집계 | 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/O | L3 | 없음 | 가능 | VPN (OpenVPN, WireGuard) |
| TAP | 유저스페이스 L2 I/O | L2 | 고유 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 + 네트워크 네임스페이스
관련 문서
이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.