MAC 주소 (MAC Address)
MAC(Media Access Control) 주소는 데이터 링크 계층(L2)에서 네트워크 인터페이스를 고유하게 식별하는 하드웨어 주소입니다.
이 문서에서는 IEEE 표준 EUI-48/EUI-64 비트 구조부터 이더넷 프레임에서의 역할,
리눅스 커널의 net_device 주소 관리, ARP를 통한 IP→MAC 해석,
Bridge FDB 학습 메커니즘, 수신 필터링, 가상 디바이스(macvlan/macvtap) 할당,
SR-IOV VF MAC 관리까지 MAC 주소의 전 영역을 커널 내부 관점에서 다룹니다.
- 이더넷 (Ethernet) — 이더넷 프레임 구조와 L2 동작 원리
- 네트워크 스택 개요 — 리눅스 커널 네트워크 스택의 전체 계층 구조
핵심 요약
- EUI-48 (48비트, 6바이트) — OUI(3B, 제조사) + NIC 고유번호(3B)로 구성된 전 세계 고유 하드웨어 주소
- I/G 비트 (bit 0) — 0이면 유니캐스트, 1이면 멀티캐스트/브로드캐스트를 구분하는 첫 번째 비트
- U/L 비트 (bit 1) — 0이면 글로벌(IEEE 할당), 1이면 로컬(관리자 수동 설정)을 구분
- ARP/NDP — IPv4에서는 ARP, IPv6에서는 NDP(Neighbor Discovery)를 통해 IP → MAC 주소를 해석
- dev_addr — 리눅스 커널
net_device구조체의 현재 활성 MAC 주소 필드.perm_addr은 원래 하드웨어 MAC
단계별 이해
- MAC 주소 구조 파악 — EUI-48/64 비트 구조에서 OUI, I/G 비트, U/L 비트의 의미를 이해합니다
- 이더넷 프레임에서의 역할 — 이더넷 프레임 속 MAC에서 L2 헤더의 Src/Dst MAC이 어떻게 사용되는지 확인합니다
- 커널 내부 관리 — net_device 주소 관리에서
dev_addr,perm_addr,dev_set_mac_address()흐름을 살펴봅니다 - 주소 해석과 실전 — ARP와 Neighbor 테이블, Bridge FDB, 가상 디바이스 섹션에서 실제 네트워크 통신에서 MAC 주소가 어떻게 활용되는지 파악합니다
개요
MAC 주소는 OSI 모델 2계층(데이터 링크)에서 동작하며, 동일 브로드캐스트 도메인 내의 장치를 식별하는 유일한 수단입니다. IP 주소가 논리적 주소라면, MAC 주소는 NIC(Network Interface Card)에 물리적으로 할당된 하드웨어 주소입니다.
MAC 주소의 핵심 역할
| 역할 | 설명 | 관련 커널 경로 |
|---|---|---|
| 프레임 전달 | 이더넷 스위치가 목적지 MAC을 기반으로 포트를 결정 | br_handle_frame(), br_fdb_find_rcu() |
| IP→MAC 해석 | ARP/NDP가 IP 주소를 MAC 주소로 변환 | arp_process(), neigh_resolve_output() |
| 수신 필터링 | NIC가 자신의 MAC과 일치하는 프레임만 수신 | __dev_set_rx_mode(), ndo_set_rx_mode |
| VLAN 태깅 | MAC + VLAN ID 조합으로 트래픽 분리 | vlan_do_receive() |
| 가상화 격리 | VM/컨테이너마다 고유 MAC으로 트래픽 분리 | macvlan_handle_frame(), veth_xmit() |
OSI 7계층에서 MAC 주소의 위치
MAC 주소 vs IP 주소 비교
| 특성 | MAC 주소 | IP 주소 |
|---|---|---|
| 계층 | L2 (데이터 링크) | L3 (네트워크) |
| 크기 | 48비트 (6바이트) | IPv4: 32비트, IPv6: 128비트 |
| 할당 방식 | 제조사가 하드웨어에 영구 기록 (OUI 기반) | DHCP/수동 설정 (논리적 할당) |
| 유효 범위 | 동일 L2 도메인 (같은 서브넷/VLAN) | 전 세계 (라우팅 가능) |
| 라우터 통과 | 홉마다 변경됨 (다음 홉의 MAC으로 교체) | 끝까지 유지됨 (출발지/목적지 IP 불변) |
| 주소 형식 | 00:1A:2B:3C:4D:5E (16진수 콜론 구분) |
192.168.1.1 (IPv4) / fe80::1 (IPv6) |
| 커널 저장소 | net_device->dev_addr |
struct in_ifaddr / struct inet6_ifaddr |
| 해석 프로토콜 | ARP Request/Reply (IPv4), NDP (IPv6) | DNS (호스트명→IP) |
라우터 통과 시 MAC 변화 과정
MAC 주소가 라우터(L3 장비)를 통과할 때 변경되는 과정을 이해하는 것이 중요합니다. 출발지/목적지 IP는 종단 간 유지되지만, MAC은 매 홉(hop)마다 갈아 끼워집니다.
[호스트 A] ──→ [라우터 R1] ──→ [라우터 R2] ──→ [호스트 B]
구간 1 (A → R1):
SRC MAC: A의 MAC DST MAC: R1의 MAC (기본 게이트웨이)
SRC IP: A의 IP DST IP: B의 IP
구간 2 (R1 → R2):
SRC MAC: R1의 MAC DST MAC: R2의 MAC (다음 홉)
SRC IP: A의 IP DST IP: B의 IP ← IP 변경 없음!
구간 3 (R2 → B):
SRC MAC: R2의 MAC DST MAC: B의 MAC
SRC IP: A의 IP DST IP: B의 IP ← IP 변경 없음!
EUI-48/EUI-64 비트 구조
EUI-48 (48비트 MAC 주소)
가장 널리 사용되는 MAC 주소 형식입니다. 총 48비트(6바이트)로 구성되며, 상위 24비트는 IEEE가 제조사에 할당하는 OUI(Organizationally Unique Identifier), 하위 24비트는 제조사가 자체 관리하는 NIC 고유 번호입니다.
EUI-64 (64비트 확장 주소)
IPv6의 인터페이스 식별자(Interface Identifier)와 IEEE 802.15.4(IoT/센서 네트워크)에서
사용됩니다. EUI-48에서 변환할 때는 OUI와 NIC 고유 번호 사이에 0xFFFE를
삽입하고, U/L 비트를 반전시킵니다.
| 형식 | 비트 수 | 주소 공간 | 주요 용도 |
|---|---|---|---|
| EUI-48 | 48비트 (6바이트) | 248 ≈ 281조 | 이더넷, Wi-Fi, Bluetooth |
| EUI-64 | 64비트 (8바이트) | 264 ≈ 1.8 × 1019 | IPv6 SLAAC, IEEE 802.15.4, Zigbee |
| Modified EUI-64 | 64비트 | EUI-48에서 변환 | IPv6 인터페이스 ID (0xFFFE 삽입 + U/L 반전) |
EUI-48 → Modified EUI-64 변환 예시
원본 EUI-48: 00:1A:2B:3C:4D:5E
1) OUI 분리: 00:1A:2B | 3C:4D:5E
2) FFFE 삽입: 00:1A:2B:FF:FE:3C:4D:5E
3) U/L 비트 반전: 02:1A:2B:FF:FE:3C:4D:5E (바이트0: 0x00 → 0x02, bit1 반전)
IPv6 Interface ID: 021a:2bff:fe3c:4d5e
주소 유형과 비트 플래그
MAC 주소의 첫 번째 바이트에 있는 두 개의 특수 비트가 주소의 성격을 완전히 결정합니다. 리눅스 커널은 이 비트들을 검사하여 패킷 수신 경로를 분기합니다.
I/G 비트 (bit 0) — 유니캐스트 vs 멀티캐스트
| bit 0 값 | 유형 | 의미 | 커널 매크로 |
|---|---|---|---|
0 |
유니캐스트 (Individual) | 단일 인터페이스 대상 | is_unicast_ether_addr() |
1 |
멀티캐스트 (Group) | 그룹 대상 (브로드캐스트 포함) | is_multicast_ether_addr() |
U/L 비트 (bit 1) — 글로벌 vs 로컬
| bit 1 값 | 유형 | 의미 | 사용 사례 |
|---|---|---|---|
0 |
글로벌 (Universal) | IEEE가 OUI를 통해 할당한 전 세계 고유 주소 | 실물 NIC, 공장 출고 MAC |
1 |
로컬 (Local) | 관리자가 수동으로 설정한 주소 | 가상 NIC, 컨테이너, macvlan, VM |
특수 MAC 주소
| 주소 | 이름 | 용도 | 커널 검사 함수 |
|---|---|---|---|
FF:FF:FF:FF:FF:FF |
브로드캐스트 | 동일 L2 도메인의 모든 호스트에게 전달 | is_broadcast_ether_addr() |
00:00:00:00:00:00 |
제로 주소 | 미할당/초기화 전 상태 | is_zero_ether_addr() |
01:00:5E:xx:xx:xx |
IPv4 멀티캐스트 | IGMP 기반 그룹 통신 | I/G bit = 1 확인 |
33:33:xx:xx:xx:xx |
IPv6 멀티캐스트 | NDP, MLD 등 IPv6 그룹 통신 | I/G bit = 1 확인 |
01:80:C2:00:00:0x |
STP/LLDP/LACP | L2 제어 프레임 (Bridge에서 소비) | br_handle_frame() 특수 처리 |
include/linux/etherdevice.h에
is_valid_ether_addr(), is_local_ether_addr(),
eth_random_addr() 등 MAC 주소 판별/생성 인라인 함수가 정의되어 있습니다.
이 함수들은 단순 비트 연산으로 구현되어 성능 오버헤드가 없습니다.
IPv4 멀티캐스트 → MAC 매핑 규칙
IPv4 멀티캐스트 주소(224.0.0.0/4)는 특정 규칙에 따라 MAC 주소로 매핑됩니다. 이 매핑은 1:1이 아니라 32:1이므로, 서로 다른 32개의 멀티캐스트 그룹이 같은 MAC 주소로 매핑될 수 있습니다.
/* include/linux/etherdevice.h — 핵심 판별 함수 */
static inline bool is_multicast_ether_addr(const u8 *addr)
{
return 0x01 & addr[0]; /* bit 0 = 1이면 멀티캐스트 */
}
static inline bool is_local_ether_addr(const u8 *addr)
{
return 0x02 & addr[0]; /* bit 1 = 1이면 로컬 관리 주소 */
}
static inline bool is_broadcast_ether_addr(const u8 *addr)
{
return (*(const u16 *)(addr + 0) &
*(const u16 *)(addr + 2) &
*(const u16 *)(addr + 4)) == 0xffff;
}
static inline bool is_valid_ether_addr(const u8 *addr)
{
return !is_multicast_ether_addr(addr) && !is_zero_ether_addr(addr);
}
MAC 주소 표기법과 바이트 순서
MAC 주소는 동일한 48비트 값이지만 운영체제, 장비, 프로토콜에 따라 서로 다른 표기법으로 표현됩니다. 또한 이더넷의 비트 전송 순서(LSB first)로 인해 Canonical Form vs Non-Canonical Form 문제가 발생할 수 있습니다.
표기법 변환과 커널 파싱
| 표기법 | 형식 | 사용 환경 | 정규식 패턴 |
|---|---|---|---|
| 콜론 (Colon-Separated) | XX:XX:XX:XX:XX:XX |
Linux, macOS, BSD | ([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2} |
| 하이픈 (Hyphen-Separated) | XX-XX-XX-XX-XX-XX |
Windows, IEEE 공식 표기 | ([0-9a-fA-F]{2}-){5}[0-9a-fA-F]{2} |
| 도트 (Dot-Separated) | XXXX.XXXX.XXXX |
Cisco IOS/NX-OS | ([0-9a-fA-F]{4}\.){2}[0-9a-fA-F]{4} |
| 베어 (No Separator) | XXXXXXXXXXXX |
일부 API, 데이터베이스 | [0-9a-fA-F]{12} |
/* lib/net_utils.c — 커널 MAC 주소 파싱 */
bool mac_pton(const char *s, u8 *mac)
{
/* "XX:XX:XX:XX:XX:XX" 또는 "XX-XX-XX-XX-XX-XX" 파싱 */
if (strlen(s) < 17)
return false;
for (int i = 0; i < 6; i++) {
if (!isxdigit(s[i * 3]) || !isxdigit(s[i * 3 + 1]))
return false;
if (i < 5 && s[i * 3 + 2] != ':' && s[i * 3 + 2] != '-')
return false;
mac[i] = (hex_to_bin(s[i * 3]) << 4) |
hex_to_bin(s[i * 3 + 1]);
}
return true;
}
/* 사용자 공간에서의 MAC 비교 (ether_aton 대체) */
/* 커널에서는 ether_addr_equal()을 사용 — 2×u16 + 1×u16 비교로 최적화 */
static inline bool ether_addr_equal(const u8 *addr1, const u8 *addr2)
{
const u16 *a = (const u16 *)addr1;
const u16 *b = (const u16 *)addr2;
return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2])) == 0;
}
ip link show 출력은 소문자 (00:1a:2b:3c:4d:5e)를 사용합니다.
비교 시에는 대소문자를 무시(case-insensitive)해야 합니다.
sysfs(/sys/class/net/*/address)도 소문자 콜론 형식을 출력합니다.
이더넷 프레임과 MAC 주소
이더넷 프레임(IEEE 802.3)에서 MAC 주소는 가장 앞에 위치합니다. NIC 하드웨어는 프레임의 처음 6바이트(목적지 MAC)를 먼저 읽어 자신의 주소와 비교하고, 일치하지 않으면 프레임을 버립니다.
커널의 이더넷 헤더 구조체
/* include/uapi/linux/if_ether.h */
#define ETH_ALEN 6 /* 이더넷 주소(MAC) 길이 */
#define ETH_HLEN 14 /* 이더넷 헤더 전체 길이 */
#define ETH_DATA_LEN 1500 /* 최대 페이로드 길이 */
struct ethhdr {
unsigned char h_dest[ETH_ALEN]; /* 목적지 MAC (6바이트) */
unsigned char h_source[ETH_ALEN]; /* 출발지 MAC (6바이트) */
__be16 h_proto; /* EtherType (2바이트) */
} __attribute__((packed));
/* sk_buff에서 이더넷 헤더 접근 */
struct ethhdr *eth = eth_hdr(skb);
/* eth->h_dest: 목적지 MAC
eth->h_source: 출발지 MAC
eth->h_proto: EtherType (ntohs()로 호스트 바이트 오더 변환) */
커널 자료구조와 MAC 주소 저장
리눅스 커널에서 네트워크 인터페이스의 MAC 주소는 struct net_device의 여러 필드에
저장됩니다. 각 필드는 서로 다른 용도와 생명주기를 가집니다.
| 필드 | 크기 | 용도 | 변경 가능성 |
|---|---|---|---|
dev->dev_addr |
addr_len 바이트 |
현재 활성 MAC 주소 (프레임 송신 시 출발지 MAC) | 런타임 변경 가능 (ip link set dev eth0 address ...) |
dev->perm_addr |
MAX_ADDR_LEN |
NIC EEPROM/펌웨어에 저장된 공장 출고 MAC | 변경 불가 (읽기 전용) |
dev->addr_len |
unsigned char |
주소 길이 (이더넷 = 6, InfiniBand = 20) | 인터페이스 유형에 따라 고정 |
dev->broadcast |
MAX_ADDR_LEN |
브로드캐스트 주소 (이더넷: FF:FF:FF:FF:FF:FF) |
인터페이스 유형에 따라 고정 |
dev->uc |
struct netdev_hw_addr_list |
추가 유니캐스트 MAC 목록 (macvlan 등) | 동적 추가/제거 |
dev->mc |
struct netdev_hw_addr_list |
멀티캐스트 MAC 목록 (IGMP 가입 시 추가) | 동적 추가/제거 |
/* net/core/dev_addr_lists.c — 유니캐스트/멀티캐스트 리스트 관리 */
int dev_uc_add(struct net_device *dev, const unsigned char *addr)
{
/* dev->uc 리스트에 유니캐스트 MAC 추가 */
int err;
netif_addr_lock_bh(dev);
err = __hw_addr_add(&dev->uc, addr, dev->addr_len,
NETDEV_HW_ADDR_T_UNICAST);
if (!err)
__dev_set_rx_mode(dev); /* NIC에 필터 업데이트 요청 */
netif_addr_unlock_bh(dev);
return err;
}
int dev_mc_add(struct net_device *dev, const unsigned char *addr)
{
/* dev->mc 리스트에 멀티캐스트 MAC 추가 */
int err;
netif_addr_lock_bh(dev);
err = __hw_addr_add(&dev->mc, addr, dev->addr_len,
NETDEV_HW_ADDR_T_MULTICAST);
if (!err)
__dev_set_rx_mode(dev);
netif_addr_unlock_bh(dev);
return err;
}
dev_addr vs perm_addr:
perm_addr은 드라이버가 probe() 시점에 EEPROM에서 읽어 한 번만
설정하며 이후 변경되지 않습니다. dev_addr은 사용자가
ip link set address로 변경할 수 있으며, 원래 주소로 복원할 때
perm_addr을 참조합니다. ethtool -P eth0으로 확인 가능합니다.
드라이버의 EEPROM MAC 읽기 과정
NIC 드라이버는 probe() 함수에서 EEPROM/Flash를 읽어 MAC 주소를 가져옵니다.
EEPROM이 없거나 유효하지 않은 경우 랜덤 MAC을 생성합니다.
/* 드라이버 probe() 내부 — MAC 초기화 패턴 (e1000e 기반 예시) */
static int e1000_probe(struct pci_dev *pdev, ...)
{
struct net_device *netdev;
struct e1000_adapter *adapter;
/* ... PCI/DMA 설정 ... */
/* 1) NIC EEPROM에서 MAC 읽기 */
if (e1000e_read_mac_addr(&adapter->hw))
dev_err(&pdev->dev, "NIC EEPROM read error\n");
/* 2) 유효성 검사 */
if (!is_valid_ether_addr(adapter->hw.mac.addr)) {
/* EEPROM MAC이 유효하지 않으면 랜덤 생성 */
dev_warn(&pdev->dev, "Invalid MAC, using random\n");
eth_hw_addr_random(netdev);
} else {
/* 3) 정상: dev_addr과 perm_addr 모두 설정 */
eth_hw_addr_set(netdev, adapter->hw.mac.addr);
memcpy(netdev->perm_addr, adapter->hw.mac.addr, ETH_ALEN);
}
/* 4) HW RAR 슬롯 0에 MAC 기록 */
e1000e_rar_set(hw, adapter->hw.mac.addr, 0);
/* ... */
}
MAC 주소 변경 경로
사용자가 ip link set dev eth0 address XX:XX:XX:XX:XX:XX 명령으로
MAC 주소를 변경하면, 커널 내부에서 다음과 같은 호출 체인이 실행됩니다.
/* net/core/dev.c — dev_set_mac_address() 핵심 경로 */
int dev_set_mac_address(struct net_device *dev, struct sockaddr *sa,
struct netlink_ext_ack *extack)
{
const struct net_device_ops *ops = dev->netdev_ops;
int err;
if (!ops->ndo_set_mac_address)
return -EOPNOTSUPP;
if (dev->flags & IFF_UP) /* 인터페이스 활성 상태에서도 변경 허용 */
; /* (드라이버가 거부할 수 있음) */
/* 변경 전 노티파이어 — FDB/ARP 테이블 갱신 준비 */
err = call_netdevice_notifiers(NETDEV_PRE_CHANGEADDR, dev);
err = notifier_to_errno(err);
if (err)
return err;
/* 드라이버에게 실제 변경 위임 */
err = ops->ndo_set_mac_address(dev, sa);
if (err)
return err;
/* 변경 완료 알림 — Bridge FDB, ARP 캐시 등 갱신 트리거 */
call_netdevice_notifiers(NETDEV_CHANGEADDR, dev);
add_device_randomness(dev->dev_addr, dev->addr_len);
return 0;
}
eth_mac_addr()는 단순히
dev->dev_addr을 복사합니다. 반면 실제 NIC 드라이버(e1000e, ixgbe 등)는
HW 레지스터(RAR: Receive Address Register)에 새 MAC을 기록하여 하드웨어 수신 필터를
즉시 갱신합니다. 일부 NIC는 IFF_UP 상태에서 MAC 변경을 거부할 수도 있습니다.
MAC 주소 수신 필터링
NIC는 기본적으로 자신의 MAC 주소와 일치하는 프레임만 수신합니다.
커널은 ndo_set_rx_mode 콜백을 통해 NIC의 수신 필터를 동적으로 제어합니다.
수신 모드 비교
| 모드 | IFF 플래그 | 수신 대상 | 사용 사례 |
|---|---|---|---|
| 일반 모드 | (기본) | 자신의 MAC + 등록된 멀티캐스트 + 브로드캐스트 | 일반적인 네트워크 운영 |
| 프로미스큐어스 | IFF_PROMISC |
모든 프레임 (MAC 무관) | tcpdump, 패킷 캡처, Bridge 포트 |
| 올멀티캐스트 | IFF_ALLMULTI |
자신의 MAC + 모든 멀티캐스트 + 브로드캐스트 | 멀티캐스트 라우터, IGMP 스누핑 |
수신 필터 업데이트 경로
/* net/core/dev.c — __dev_set_rx_mode() */
void __dev_set_rx_mode(struct net_device *dev)
{
const struct net_device_ops *ops = dev->netdev_ops;
if (!(dev->flags & IFF_UP))
return;
if (!netif_device_present(dev))
return;
/* 드라이버에게 현재 유니캐스트/멀티캐스트 리스트와 플래그 전달 */
if (ops->ndo_set_rx_mode)
ops->ndo_set_rx_mode(dev);
}
/* 드라이버 구현 예시 (ixgbe) — 유니캐스트/멀티캐스트 필터 프로그래밍 */
/* 1. dev->uc 리스트의 MAC들을 RAR(Receive Address Register)에 기록
2. dev->mc 리스트의 MAC들을 MTA(Multicast Table Array) 해시에 기록
3. 리스트가 HW 용량 초과 시 IFF_PROMISC 또는 IFF_ALLMULTI로 폴백 */
ARP와 Neighbor 서브시스템
IP 패킷을 전송하려면 먼저 다음 홉(next-hop)의 MAC 주소를 알아야 합니다. IPv4에서는 ARP(Address Resolution Protocol), IPv6에서는 NDP(Neighbor Discovery Protocol)가 이 역할을 수행합니다. 커널은 이를 Neighbor 서브시스템으로 통합 추상화합니다.
Neighbor 서브시스템 주요 구조체
/* include/net/neighbour.h — struct neighbour */
struct neighbour {
struct neighbour __rcu *next; /* 해시 체인 */
struct neigh_table *tbl; /* arp_tbl 또는 nd_tbl */
struct net_device *dev; /* 연결된 인터페이스 */
unsigned long updated;/* 마지막 갱신 시각 */
u8 nud_state; /* NUD_REACHABLE, NUD_STALE 등 */
u8 ha[ALIGN(MAX_ADDR_LEN, sizeof(unsigned long))];
/* ↑ 해석된 하드웨어(MAC) 주소 */
struct sk_buff_head arp_queue; /* 해석 대기 중인 패킷 큐 */
const struct neigh_ops *ops; /* arp_generic_ops 등 */
/* ... */
};
/* ARP 테이블 전역 인스턴스 */
struct neigh_table arp_tbl = {
.family = AF_INET,
.key_len = 4, /* IPv4: 4바이트 키 */
.hash = arp_hash,
.id = "arp_cache",
.gc_interval = 30 * HZ, /* 가비지 컬렉션 주기: 30초 */
/* ... */
};
ARP 관련 sysctl 튜닝 파라미터
| sysctl 경로 | 기본값 | 설명 |
|---|---|---|
net.ipv4.neigh.default.gc_stale_time |
60 | NUD_STALE 상태 유지 시간 (초) |
net.ipv4.neigh.default.base_reachable_time_ms |
30000 | NUD_REACHABLE 기본 유지 시간 (밀리초) |
net.ipv4.neigh.default.gc_thresh1 |
128 | GC 시작 임계값 (이 미만이면 GC 미수행) |
net.ipv4.neigh.default.gc_thresh2 |
512 | 소프트 상한 (5초 이상 된 항목 GC 대상) |
net.ipv4.neigh.default.gc_thresh3 |
1024 | 하드 상한 (강제 GC, 초과 시 새 항목 거부) |
gc_thresh3에 도달하면 새로운 Neighbor 항목을 생성하지 못해
네트워크 연결이 실패할 수 있습니다. Kubernetes 등에서는
gc_thresh1=4096, gc_thresh2=8192, gc_thresh3=16384 수준으로 상향 조정이 필요합니다.
Neighbor 상태 머신 (NUD States)
커널의 Neighbor 서브시스템은 각 이웃 항목을 유한 상태 머신으로 관리합니다. 상태 전이에 따라 ARP 재확인, 에이징, 가비지 컬렉션이 자동으로 수행됩니다.
Gratuitous ARP와 Proxy ARP
ARP에는 일반적인 해석 외에 두 가지 특수 동작이 있습니다.
| 유형 | 동작 | 용도 | 커널 설정 |
|---|---|---|---|
| Gratuitous ARP | 자기 자신의 IP를 목적지로 하는 ARP Request를 브로드캐스트 | IP 충돌 감지, MAC 변경 알림, VRRP/HSRP 페일오버 | net.ipv4.conf.all.arp_accept |
| Proxy ARP | 다른 호스트를 대신하여 ARP Reply 응답 (라우터/게이트웨이가 수행) | 서브넷 간 통신 지원, 브릿지 없는 컨테이너 연결 | net.ipv4.conf.eth0.proxy_arp |
# Gratuitous ARP 전송 (MAC 변경 후 네트워크에 알림)
arping -U -I eth0 192.168.1.10 # ARP Request 형태
arping -A -I eth0 192.168.1.10 # ARP Reply 형태
# Proxy ARP 활성화 (특정 인터페이스)
sysctl -w net.ipv4.conf.eth0.proxy_arp=1
# Gratuitous ARP 수락 정책
sysctl -w net.ipv4.conf.all.arp_accept=1 # GARP로 새 이웃 항목 생성 허용
sysctl -w net.ipv4.conf.all.arp_accept=0 # 기존 항목 갱신만 허용 (기본값)
IPv6 Neighbor Discovery (NDP)
IPv6에서는 ARP 대신 NDP(Neighbor Discovery Protocol)가 MAC 주소 해석을 담당합니다. NDP는 ICMPv6 위에서 동작하며, 주소 해석뿐 아니라 라우터 발견, 중복 주소 감지(DAD), 자동 주소 구성(SLAAC) 등 IPv4 ARP보다 훨씬 넓은 범위의 기능을 수행합니다.
| 항목 | ARP (IPv4) | NDP (IPv6) |
|---|---|---|
| 프로토콜 | 독립 L2 프로토콜 (EtherType 0x0806) | ICMPv6 (IPv6 Next Header 58) |
| 주소 해석 | ARP Request/Reply (브로드캐스트) | NS/NA (Solicited-Node 멀티캐스트) |
| 라우터 발견 | DHCP 또는 수동 설정 | RS/RA (Router Solicitation/Advertisement) |
| 중복 감지 | Gratuitous ARP (선택적) | DAD (Duplicate Address Detection, 필수) |
| 멀티캐스트 사용 | 브로드캐스트 (ff:ff:ff:ff:ff:ff) | Solicited-Node 멀티캐스트 (ff02::1:ffXX:XXXX) |
| 커널 테이블 | arp_tbl (neigh_table) |
nd_tbl (neigh_table) |
| 보안 | ARP Spoofing에 취약 | SEND(Secure NDP) 지원, RA Guard 가능 |
| 헤더 내 MAC | sha/tha 필드 (각 6바이트) | Source/Target Link-Layer Address 옵션 |
ICMPv6 NS/NA 메시지와 커널 함수
NDP의 핵심은 NS(Neighbor Solicitation)와 NA(Neighbor Advertisement) 메시지입니다. NS는 "이 IPv6 주소의 MAC은 무엇인가?"를 묻고, NA가 응답합니다.
| 메시지 | ICMPv6 Type | 목적 | 커널 함수 |
|---|---|---|---|
| NS (Neighbor Solicitation) | 135 | 타겟 IPv6의 MAC 질의, DAD 수행 | ndisc_send_ns() |
| NA (Neighbor Advertisement) | 136 | NS에 대한 응답, MAC 주소 전달 | ndisc_recv_na() |
| RS (Router Solicitation) | 133 | 라우터 존재 질의 | ndisc_send_rs() |
| RA (Router Advertisement) | 134 | 네트워크 프리픽스, MTU, 기본 라우터 공지 | ndisc_router_discovery() |
| Redirect | 137 | 더 적합한 next-hop 라우터 알림 | ndisc_redirect_rcv() |
DAD (Duplicate Address Detection)
IPv6 인터페이스가 주소를 사용하기 전에, 해당 주소가 네트워크에 이미 존재하는지 확인하는 과정입니다. 커널은 타겟을 자신의 주소로 설정한 NS를 Solicited-Node 멀티캐스트 그룹으로 전송하고, 지정된 시간 내에 NA 응답이 없으면 주소를 확정합니다.
# DAD 관련 sysctl
# DAD 시도 횟수 (0=DAD 비활성화, 1=기본)
sysctl -w net.ipv6.conf.eth0.dad_transmits=1
# DAD 실패 시 주소를 tentative 상태로 유지하는 대신 제거
sysctl -w net.ipv6.conf.eth0.accept_dad=1
# DAD 진행 중인 주소 확인 (tentative 플래그)
ip -6 addr show dev eth0 tentative
# 강화된 DAD (RFC 7527): 루프백 감지
sysctl -w net.ipv6.conf.eth0.enhanced_dad=1
SLAAC + EUI-64 자동 구성
SLAAC(Stateless Address Autoconfiguration)은 라우터가 RA로 공지한 네트워크 프리픽스(64비트)에 호스트가 자체 생성한 인터페이스 ID(64비트)를 결합하여 전체 128비트 IPv6 주소를 만듭니다. 전통적인 EUI-64 방식은 MAC 주소를 기반으로 인터페이스 ID를 생성합니다.
EUI-64 변환 과정:
MAC: AA:BB:CC:DD:EE:FF (48비트)
↓ 중간에 FF:FE 삽입
EUI-64: AA:BB:CC:FF:FE:DD:EE:FF (64비트)
↓ 7번째 비트(U/L bit) 반전
IID: A8:BB:CC:FF:FE:DD:EE:FF
프리픽스(RA): 2001:db8:1::/64
최종 주소: 2001:db8:1::a8bb:ccff:fedd:eeff/64
use_tempaddr=2)가 도입되었으며,
최신 커널은 RFC 7217의 Stable Privacy Addresses(addr_gen_mode=2)를 권장합니다.
| 파라미터 | 기본값 | 설명 |
|---|---|---|
net.ipv6.conf.*.dad_transmits |
1 | DAD NS 전송 횟수. 0이면 DAD 비활성화 |
net.ipv6.conf.*.accept_dad |
1 | DAD 실패 시 동작: 0=무시, 1=주소 비활성화, 2=인터페이스 비활성화 |
net.ipv6.conf.*.use_tempaddr |
0 | Privacy Extensions: 0=비활성, 1=생성하되 선호X, 2=생성+선호 |
net.ipv6.conf.*.addr_gen_mode |
0 | 주소 생성 모드: 0=EUI-64, 1=none, 2=stable-privacy, 3=random |
net.ipv6.conf.*.accept_ra |
1 | RA 수락 여부: 0=거부, 1=forwarding 비활성 시 수락, 2=항상 수락 |
net.ipv6.conf.*.router_solicitations |
3 | 인터페이스 활성화 시 RS 전송 횟수 |
net.ipv6.neigh.*.base_reachable_time_ms |
30000 | Neighbor 엔트리 reachable 상태 유지 기본 시간(ms) |
net.ipv6.neigh.*.gc_stale_time |
60 | stale 상태 neighbor 엔트리 GC 대기 시간(초) |
ip -6 neigh show로 nd_tbl 캐시 상태를 확인할 수 있습니다.
상태 값은 INCOMPLETE → REACHABLE → STALE → DELAY → PROBE → FAILED 순으로 전이됩니다.
ip -6 neigh flush dev eth0으로 특정 인터페이스의 캐시를 초기화할 수 있습니다.
Bridge FDB와 MAC 학습
리눅스 브리지는 물리적 이더넷 스위치처럼 MAC 주소를 학습하여 포트 간 프레임을 효율적으로 전달합니다. 이 학습 결과를 저장하는 테이블이 FDB(Forwarding Database)입니다.
FDB 주요 구조체와 함수
/* net/bridge/br_fdb.c — FDB 엔트리 */
struct net_bridge_fdb_entry {
struct rhash_head rhnode; /* 해시 테이블 노드 */
struct net_bridge_port *dst; /* 학습된 포트 */
unsigned long updated; /* 마지막 갱신 jiffies */
unsigned long used; /* 마지막 사용 jiffies */
mac_addr addr; /* MAC 주소 */
__u16 vlan_id; /* VLAN ID (VLAN 필터링 시) */
unsigned char is_local:1, /* 브리지 자체 MAC */
is_static:1, /* 정적 FDB 항목 */
added_by_user:1;
/* ... */
};
/* FDB 에이징: 기본 300초 (5분) 후 동적 항목 만료 */
/* bridge fdb show / bridge fdb add 로 정적 항목 관리 */
FDB 운영 명령
# FDB 테이블 조회
bridge fdb show br br0
# 정적 FDB 항목 추가 (특정 MAC → 특정 포트 고정)
bridge fdb add AA:BB:CC:DD:EE:FF dev eth0 master static
# 정적 항목 삭제
bridge fdb del AA:BB:CC:DD:EE:FF dev eth0 master
# 에이징 시간 변경 (기본 300초)
ip link set br0 type bridge ageing_time 600
대규모 FDB 테이블 관리와 튜닝
클라우드 및 데이터센터 환경에서는 브리지 하나에 수천~수만 개의 MAC 항목이 학습될 수 있습니다. FDB 해시 테이블의 크기, 에이징 정책, VLAN 필터링 설정이 성능에 직접적인 영향을 미칩니다.
| 파라미터 | 기본값 | 설명 | 설정 방법 |
|---|---|---|---|
ageing_time |
300 (초) | 동적 FDB 항목의 만료 시간. 0으로 설정하면 에이징 비활성화 (정적 환경에 적합) | ip link set br0 type bridge ageing_time <초> |
hash_max |
512 | FDB 해시 테이블 버킷 수. 대규모 환경에서는 4096 이상으로 증가 | echo 4096 > /sys/class/net/br0/bridge/hash_max |
hash_elasticity |
4 | 버킷당 최대 체인 길이. 초과 시 해시 확장 시도 | echo 16 > /sys/class/net/br0/bridge/hash_elasticity |
multicast_snooping |
1 (on) | IGMP/MLD 스누핑으로 멀티캐스트 FDB 최적화 | ip link set br0 type bridge mcast_snooping 1 |
vlan_filtering |
0 (off) | VLAN별 FDB 분리. 대규모 멀티테넌트에서 필수 | ip link set br0 type bridge vlan_filtering 1 |
| FDB 항목 수 | 해시 조회 시간 | 메모리 사용 | 권장 hash_max | 비고 |
|---|---|---|---|---|
| ~1K | ~50ns | ~100KB | 512 (기본) | 일반 사무실/소규모 환경 |
| ~10K | ~100ns | ~1MB | 2048 | 중형 데이터센터, VLAN 필터링 권장 |
| ~100K | ~200ns+ | ~10MB | 8192+ | 대규모 클라우드. HW offload 또는 EVPN 고려 |
# 대규모 FDB 환경 튜닝 스크립트
BRIDGE=br0
# 해시 테이블 확대 (항목 수 대비 충분한 버킷)
echo 4096 > /sys/class/net/$BRIDGE/bridge/hash_max
echo 16 > /sys/class/net/$BRIDGE/bridge/hash_elasticity
# 에이징 시간 단축 (오래된 항목 빠르게 제거)
ip link set $BRIDGE type bridge ageing_time 120
# VLAN 필터링 활성화 (멀티테넌트 분리)
ip link set $BRIDGE type bridge vlan_filtering 1
# 멀티캐스트 스누핑 활성화
ip link set $BRIDGE type bridge mcast_snooping 1
# FDB 항목 수 모니터링
bridge fdb show br $BRIDGE | wc -l
# GC(Garbage Collection) 주기 관련 커널 파라미터
sysctl -w net.ipv4.neigh.default.gc_interval=30
sysctl -w net.ipv4.neigh.default.gc_stale_time=60
# 실시간 FDB 변경 모니터링
bridge monitor fdb
bridge fdb add ... offload 플래그가 표시되면 해당 항목은 하드웨어에서 직접 조회됩니다.
10만 이상의 MAC 항목이 필요한 환경에서는 HW offload 또는 EVPN+VXLAN 구조를 검토하세요.
VXLAN/EVPN과 MAC 학습
데이터센터와 클라우드 환경에서 VXLAN(Virtual Extensible LAN)은 L2 도메인을 L3 네트워크 위에 오버레이하여 물리적 위치에 관계없이 동일 L2 세그먼트를 제공합니다. VXLAN에서의 MAC 학습은 전통적인 Bridge FDB와 근본적으로 다른 메커니즘을 사용합니다.
VXLAN FDB 관리
# VXLAN 인터페이스 생성 (멀티캐스트 학습 모드)
ip link add vxlan100 type vxlan id 100 \
group 239.1.1.1 dev eth0 dstport 4789
ip link set vxlan100 up
# VXLAN FDB 조회 (원격 VTEP 매핑)
bridge fdb show dev vxlan100
# → AA:BB:CC:DD:EE:FF dev vxlan100 dst 10.0.0.2 self permanent
# → 00:00:00:00:00:00 dev vxlan100 dst 239.1.1.1 self permanent (BUM)
# 정적 VXLAN FDB 항목 추가 (EVPN 없이 수동 관리)
bridge fdb append AA:BB:CC:DD:EE:FF dev vxlan100 dst 10.0.0.2
# BUM 트래픽 대상 VTEP 추가 (unicast head-end replication)
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
# EVPN 모드 VXLAN (학습 비활성화, BGP가 FDB 관리)
ip link add vxlan100 type vxlan id 100 \
local 10.0.0.1 nolearning dstport 4789
# nolearning: 데이터 플레인 학습 비활성화 → BGP EVPN이 FDB 프로그래밍
EVPN Route Type과 MAC
| Type | 이름 | MAC 관련 내용 |
|---|---|---|
| Type-2 | MAC/IP Advertisement | MAC + IP + VNI + VTEP IP를 광고. VXLAN FDB와 ARP 캐시를 동시에 구성 |
| Type-3 | Inclusive Multicast | BUM 트래픽 복제 트리 구성. 멀티캐스트 대체(Ingress Replication) |
| Type-5 | IP Prefix | L3 VPN 경로. Symmetric IRB에서 MAC과 IP 경로를 분리 광고 |
bgpd가 담당합니다.
FRR은 BGP EVPN Type-2 경로를 수신하면 Netlink를 통해 커널의 VXLAN FDB에
정적 항목을 프로그래밍합니다. vtysh에서 show evpn mac vni 100으로
학습된 원격 MAC을 확인할 수 있습니다.
가상 디바이스와 MAC 할당
가상화와 컨테이너 환경에서는 하나의 물리 NIC 위에 여러 가상 인터페이스가 생성되며, 각각 고유한 MAC 주소가 필요합니다. 리눅스 커널은 다양한 가상 네트워크 디바이스를 제공합니다.
가상 디바이스별 MAC 할당 방식
| 디바이스 유형 | MAC 할당 방식 | 물리 NIC 관계 | 성능 특성 |
|---|---|---|---|
| macvlan | 사용자 지정 또는 랜덤 로컬 MAC | 하위 인터페이스의 유니캐스트 리스트에 등록 | 브리지 없이 직접 L2 분리, 높은 성능 |
| macvtap | macvlan과 동일 + TAP 인터페이스 제공 | 동일 (macvlan 기반) | VM 네트워킹에 최적화 |
| veth pair | 각 끝에 독립 랜덤 로컬 MAC | 가상 케이블, 물리 NIC 무관 | 컨테이너 네트워킹 표준 |
| bridge port | 물리 NIC MAC 또는 별도 할당 | 프로미스큐어스 모드 강제 | 범용 L2 스위칭 |
| bond/team | 첫 번째 슬레이브 MAC 또는 지정 MAC | 모든 슬레이브가 동일 MAC 공유 | 링크 집선/이중화 |
| dummy | 랜덤 로컬 MAC | 물리 NIC 없음 | 루프백 대체, 테스트용 |
macvlan 동작 모드와 MAC 처리
| 모드 | 설명 | MAC 격리 |
|---|---|---|
bridge |
macvlan 인터페이스 간 직접 통신 가능 | 각 macvlan이 고유 MAC, FDB 기반 전달 |
vepa |
모든 트래픽이 외부 스위치로 전달 (hairpin) | 외부 스위치가 MAC 학습/전달 담당 |
private |
macvlan 간 완전 격리 | 같은 하위 인터페이스의 macvlan끼리 통신 불가 |
passthru |
하위 인터페이스를 단일 macvlan이 독점 | 하위 인터페이스 MAC을 그대로 사용 |
# macvlan 생성 (로컬 관리 MAC 자동 할당)
ip link add macvlan0 link eth0 type macvlan mode bridge
# 특정 MAC 지정
ip link add macvlan1 link eth0 address 02:42:AC:11:00:01 type macvlan mode bridge
# veth pair 생성 (컨테이너 연결용)
ip link add veth-host type veth peer name veth-ns
# 양쪽 모두 자동으로 랜덤 로컬 MAC 할당됨 (U/L bit = 1)
eth_random_addr()는
랜덤 MAC을 생성한 뒤 addr[0] |= 0x02로 U/L 비트를 설정하고
addr[0] &= 0xFE로 I/G 비트를 클리어합니다.
이렇게 하면 IEEE 할당 주소와 절대 충돌하지 않는 로컬 유니캐스트 MAC이 됩니다.
macvlan 모드별 트래픽 경로
Bonding/Teaming MAC 동작
Linux bonding(bond0)과 teaming(team0)은 여러 물리 NIC를 하나의 논리 인터페이스로 묶습니다. 본딩 모드에 따라 슬레이브 NIC들의 MAC 주소 관리 방식이 완전히 다르므로, 모드별 동작을 정확히 이해해야 합니다.
| 모드 | 이름 | bond0 MAC 소스 | 슬레이브 MAC 변경 | 설명 |
|---|---|---|---|---|
| 0 | balance-rr | 첫 번째 슬레이브 | 모든 슬레이브 → bond0 MAC으로 통일 | 패킷을 라운드로빈으로 분배. 스위치 포트채널 필요 |
| 1 | active-backup | 활성 슬레이브 | 기본: 활성 슬레이브 MAC 유지 (fail_over_mac에 따라 다름) | 하나만 활성, 나머지 대기. 가장 안전한 기본 모드 |
| 2 | balance-xor | 첫 번째 슬레이브 | 모든 슬레이브 → bond0 MAC | src/dst MAC XOR 해시로 슬레이브 선택 |
| 3 | broadcast | 첫 번째 슬레이브 | 모든 슬레이브 → bond0 MAC | 모든 슬레이브에 동시 전송. 특수 용도 |
| 4 | 802.3ad (LACP) | 첫 번째 슬레이브 | 모든 슬레이브 → bond0 MAC | 스위치와 LACP 협상. 대역폭 집약 + 이중화 |
| 5 | balance-tlb | 첫 번째 슬레이브 | 각 슬레이브 고유 MAC 유지 | TX 부하 분산. 스위치 설정 불필요. 수신은 활성 슬레이브만 |
| 6 | balance-alb | 첫 번째 슬레이브 | 각 슬레이브 고유 MAC 유지 (ARP 협상으로 수신 분배) | TX+RX 부하 분산. ARP reply로 피어에게 다른 슬레이브 MAC 전달 |
fail_over_mac 정책 (active-backup 모드 전용)
| 정책 | 값 | 동작 | 사용 시나리오 |
|---|---|---|---|
none |
0 (기본) | bond0 MAC을 활성 슬레이브에 강제 설정. 페일오버 시 새 활성 슬레이브의 MAC이 bond0 MAC으로 변경됨 | 일반적인 환경. 스위치 FDB가 빠르게 갱신됨 |
active |
1 | bond0 MAC이 현재 활성 슬레이브의 원래 MAC을 따라감. 페일오버 시 bond0 MAC 자체가 변경됨 | 슬레이브 MAC을 변경할 수 없는 경우 (일부 하드웨어 제약) |
follow |
2 | bond0 MAC은 첫 번째 활성 슬레이브에 고정. 페일오버 시 새 활성 슬레이브의 MAC이 bond0 MAC으로 변경됨 | bond0 MAC 일관성 유지가 중요한 환경 |
# bonding 생성 (802.3ad LACP 모드)
ip link add bond0 type bond mode 802.3ad
# LACP 파라미터 설정
ip link set bond0 type bond \
lacp_rate fast \
xmit_hash_policy layer3+4 \
miimon 100
# 슬레이브 추가 (슬레이브 MAC → bond0 MAC으로 통일됨)
ip link set eth0 master bond0
ip link set eth1 master bond0
ip link set bond0 up
# MAC 확인 — 모든 슬레이브가 동일 MAC
ip link show bond0
ip link show eth0
ip link show eth1
# active-backup + fail_over_mac=active 설정
ip link add bond1 type bond mode active-backup
ip link set bond1 type bond fail_over_mac active
ip link set eth2 master bond1
ip link set eth3 master bond1
ip link set bond1 up
# teaming 드라이버 (대안)
ip link add team0 type team
teamdctl team0 config dump
SR-IOV와 VF MAC 관리
SR-IOV(Single Root I/O Virtualization)는 하나의 물리 NIC(PF)에서 여러 가상 기능(VF)을 생성하여 각각 독립적인 MAC 주소와 TX/RX 큐를 가지게 합니다. VM이나 컨테이너에 VF를 직접 할당(passthrough)하면 소프트웨어 브리지 오버헤드 없이 네이티브에 가까운 성능을 얻습니다.
PF/VF MAC 관리 체계
| 항목 | PF (Physical Function) | VF (Virtual Function) |
|---|---|---|
| MAC 소유권 | EEPROM/펌웨어 영구 MAC | PF 드라이버가 할당 (관리자 지정 또는 랜덤) |
| 변경 권한 | root만 가능 | PF 관리자가 허용한 경우에만 VF 자체 변경 가능 |
| HW 필터 | RAR 슬롯 0 (고정) | RAR 슬롯 1~N (VF별 할당) |
| 스푸핑 방지 | N/A | ip link set eth0 vf 0 spoofchk on |
# VF 생성 (예: ixgbe NIC에서 4개 VF)
echo 4 > /sys/class/net/eth0/device/sriov_numvfs
# VF에 MAC 주소 할당 (PF 관리자 권한)
ip link set eth0 vf 0 mac 02:00:00:00:00:01
ip link set eth0 vf 1 mac 02:00:00:00:00:02
# VF의 MAC 스푸핑 방지 활성화
ip link set eth0 vf 0 spoofchk on
# VF VLAN 태깅
ip link set eth0 vf 0 vlan 100
# VF 상태 확인
ip link show eth0
# → vf 0 MAC 02:00:00:00:00:01, spoof checking on, link-state auto, vlan 100
spoofchk on이 활성화되면
VF가 자신에게 할당된 MAC과 다른 출발지 MAC으로 프레임을 전송하려 할 때
NIC 하드웨어가 프레임을 드롭합니다. 이는 악의적인 VM이 다른 VM의 트래픽을
가로채는 것을 방지하는 핵심 보안 메커니즘입니다.
컨테이너/클라우드 환경 MAC 관리
Docker, Kubernetes, OpenStack 등 클라우드/컨테이너 플랫폼에서는 대규모의 가상 네트워크 인터페이스가 동적으로 생성·삭제됩니다. 각 인터페이스에 고유한 MAC 주소가 필요하며, MAC 충돌 방지, ARP/Neighbor 테이블 스케일링, CNI(Container Network Interface)별 할당 정책 등 고유한 과제가 존재합니다.
Docker MAC 할당 메커니즘
| 항목 | Docker 기본 동작 |
|---|---|
| MAC 접두사 | 02:42:AC — Docker의 기본 OUI 접두사 (U/L 비트 = 1, 로컬) |
| 하위 3바이트 | 컨테이너 IP 주소의 하위 3바이트 사용 (172.17.0.2 → AC:11:00:02) |
| 사용자 지정 | docker run --mac-address 02:42:AC:11:FF:01 ... |
| 네트워크 격리 | 각 컨테이너가 독립 네트워크 네임스페이스 → 독립 ARP 테이블 |
| Bridge FDB | docker0 브리지가 veth 포트의 MAC을 동적 학습 |
Kubernetes CNI별 MAC 할당 정책
| CNI | 네트워크 모델 | MAC 할당 | 특징 |
|---|---|---|---|
| Calico | L3 라우팅 (BGP) | veth pair에 랜덤 로컬 MAC. Proxy ARP로 게이트웨이 MAC 응답 | Bridge 불필요, ARP 테이블 최소화 |
| Flannel (VXLAN) | VXLAN 오버레이 | veth pair 랜덤 MAC + VXLAN FDB에 원격 MAC 등록 | 노드 간 MAC 학습은 Flannel 데몬이 관리 |
| Cilium | eBPF 기반 | veth pair 랜덤 MAC. eBPF로 L2/L3 처리 (Bridge 우회) | MAC 필터링을 eBPF 맵으로 고속 처리 |
| SR-IOV CNI | VF passthrough | PF 관리자가 VF에 MAC 할당 (ip link set vf mac) |
HW 필터로 최고 성능, spoofchk 활성화 권장 |
| Macvlan CNI | macvlan sub-if | 물리 NIC의 유니캐스트 리스트에 직접 등록 | HW 필터 용량 초과 시 프로미스큐어스 전환 주의 |
# Docker 컨테이너에 특정 MAC 할당
docker run --mac-address 02:42:AC:11:FF:01 --name myapp alpine
# Kubernetes Pod에서 MAC 확인
kubectl exec -it mypod -- ip link show eth0
# 컨테이너 환경 ARP 테이블 스케일링 (호스트에서 설정)
sysctl -w net.ipv4.neigh.default.gc_thresh1=4096
sysctl -w net.ipv4.neigh.default.gc_thresh2=8192
sysctl -w net.ipv4.neigh.default.gc_thresh3=16384
# 대규모 환경: Bridge FDB 해시 테이블 확대
echo 4096 > /sys/class/net/cni0/bridge/hash_max
# Calico Proxy ARP 확인
sysctl net.ipv4.conf.cali*.proxy_arp
# → net.ipv4.conf.cali1234abcd.proxy_arp = 1
# Pod veth pair의 MAC 확인 (호스트 측)
ip link show type veth | grep -A1 "cali\|veth"
net_device MAC 관리가 적용되지 않습니다.
rte_eth_dev_default_mac_addr_set() API로 직접 MAC을 설정하며,
rte_eth_dev_mac_addr_add()로 추가 유니캐스트 필터를 등록합니다.
MAC 보안 위협과 방어
MAC 주소는 L2 수준에서 신뢰를 기반으로 동작하기 때문에 다양한 공격에 취약합니다. 리눅스 커널과 네트워크 인프라에서 제공하는 방어 메커니즘을 이해하는 것이 중요합니다.
ARP Spoofing 공격 원리
ARP는 인증 메커니즘이 없어, 누구든 위조된 ARP Reply를 전송할 수 있습니다. 공격자가 "게이트웨이의 IP는 내 MAC이야"라는 위조 ARP Reply를 지속 전송하면, 피해자의 ARP 캐시가 오염되어 모든 외부 트래픽이 공격자를 경유합니다 (MITM).
# ARP Spoofing 탐지: 동일 IP에 대해 MAC이 계속 변경되는지 확인
ip neigh show | grep -i "192.168.1.1"
# STALE/REACHABLE 상태에서 MAC이 자주 바뀌면 의심
# arptables로 정적 ARP 바인딩 (방어)
arp -s 192.168.1.1 AA:BB:CC:DD:EE:FF
# nftables로 특정 포트의 ARP 응답 제한 (Linux Bridge 방어)
nft add rule bridge filter forward ether type arp arp operation reply \
arp saddr ip 192.168.1.1 arp saddr ether != AA:BB:CC:DD:EE:FF drop
MAC Flooding 공격과 방어
공격자가 임의의 MAC 주소를 대량으로 전송하면 스위치/브리지의 FDB 테이블이 포화되어, 학습되지 않은 프레임을 모든 포트로 플러딩합니다. 이를 통해 공격자는 다른 포트의 트래픽을 엿볼 수 있습니다.
# Linux Bridge: 포트당 FDB 학습 수 제한
bridge link set dev eth0 learning_limit 100
# Bridge FDB 크기 제한 (전체)
ip link set br0 type bridge fdb_max_learned 4096
# ebtables로 특정 포트의 출발지 MAC 화이트리스트
ebtables -A FORWARD -i eth0 -s ! 00:1A:2B:3C:4D:5E -j DROP
리눅스 커널의 MAC 보안 기능 요약
| 기능 | 계층 | 설명 | 설정 방법 |
|---|---|---|---|
| SR-IOV spoofchk | HW | VF의 출발지 MAC 검증, 불일치 시 HW 드롭 | ip link set eth0 vf 0 spoofchk on |
| Bridge Port Isolation | SW | 브리지 포트 간 직접 통신 차단 | bridge link set dev eth0 isolated on |
| ebtables MAC 필터 | SW | L2 프레임의 MAC 기반 필터링 | ebtables -A FORWARD -s MAC -j DROP |
| nftables bridge | SW | nftables의 bridge family로 L2 필터링 | nft add table bridge filter |
| eBPF TC/XDP | SW/HW | 프로그래머블 MAC 검증 및 정책 | BPF 프로그램 attach |
| 정적 ARP 항목 | SW | 핵심 호스트의 MAC을 수동 고정 | ip neigh add ... nud permanent |
| Bridge VLAN 필터 | SW | VLAN별 트래픽 분리로 공격 범위 축소 | bridge vlan add dev eth0 vid 100 |
실전 방어 규칙: ebtables / nftables / eBPF
L2 수준의 MAC 기반 보안은 커널의 브리지 넷필터(ebtables), nftables bridge 패밀리, eBPF tc-bpf 프로그램으로 구현합니다. 각각의 특성과 사용 시나리오가 다르므로 환경에 맞는 도구를 선택합니다.
ebtables: ARP Spoofing 방어
# ARP Spoofing 방어: 특정 포트에서 허용된 MAC만 ARP Reply 가능
ebtables -A FORWARD -p ARP --arp-op Reply \
--arp-mac-src ! AA:BB:CC:DD:EE:FF -i eth1 -j DROP
# 알려지지 않은 소스 MAC 차단 (화이트리스트 방식)
ebtables -N ALLOWED_MACS
ebtables -A ALLOWED_MACS -s 00:11:22:33:44:55 -j RETURN
ebtables -A ALLOWED_MACS -s 00:11:22:33:44:66 -j RETURN
ebtables -A ALLOWED_MACS -j DROP
# FORWARD/INPUT 체인에 적용
ebtables -A FORWARD -i eth1 -j ALLOWED_MACS
ebtables -A INPUT -i eth1 -j ALLOWED_MACS
# MAC-IP 바인딩 (ARP 정적 검증)
ebtables -A FORWARD -p ARP --arp-ip-src 192.168.1.100 \
--arp-mac-src ! 00:11:22:33:44:55 -j DROP
nftables: bridge 패밀리 MAC 필터링
# nftables bridge 패밀리로 L2 필터링 (nft 0.9+)
nft add table bridge filter
nft add chain bridge filter forward \
'{ type filter hook forward priority 0; policy accept; }'
# 특정 소스 MAC 차단
nft add rule bridge filter forward \
ether saddr AA:BB:CC:DD:EE:FF drop
# VLAN 100에서만 특정 MAC 허용
nft add rule bridge filter forward \
vlan id 100 ether saddr != { 00:11:22:33:44:55, 00:11:22:33:44:66 } drop
# ARP Reply에서 MAC-IP 바인딩 검증
nft add rule bridge filter forward \
arp operation reply \
arp saddr ether != 00:11:22:33:44:55 \
arp saddr ip 192.168.1.100 \
drop
# 규칙 목록 확인
nft list table bridge filter
eBPF tc-bpf: 프로그래밍 가능 MAC 필터
/* tc-bpf MAC 필터 예시 (tc ingress에서 허용 MAC만 통과) */
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
/* 허용 MAC 목록을 담는 해시 맵 */
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 256);
__type(key, __u8[ETH_ALEN]); /* 6바이트 MAC */
__type(value, __u32); /* 1=허용 */
} allowed_macs SEC(".maps");
SEC("tc")
int mac_filter(struct __sk_buff *skb)
{
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
/* 이더넷 헤더 바운드 체크 */
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return TC_ACT_SHOT;
/* 소스 MAC을 맵에서 조회 */
__u32 *val = bpf_map_lookup_elem(&allowed_macs, eth->h_source);
if (!val)
return TC_ACT_SHOT; /* 미등록 MAC → 드롭 */
return TC_ACT_OK; /* 허용된 MAC → 통과 */
}
char _license[] SEC("license") = "GPL";
# eBPF 프로그램 컴파일 및 적용
clang -O2 -target bpf -c mac_filter.c -o mac_filter.o
# tc ingress에 부착
tc qdisc add dev br0 clsact
tc filter add dev br0 ingress bpf da obj mac_filter.o sec tc
# 허용 MAC 등록 (bpftool 사용)
bpftool map update name allowed_macs \
key hex 00 11 22 33 44 55 value hex 01 00 00 00
# 필터 상태 확인
tc filter show dev br0 ingress
net.ipv4.conf.all.arp_accept sysctl로 Gratuitous ARP 수락 정책을 제어할 수 있습니다.
MACsec (IEEE 802.1AE)
MACsec(Media Access Control Security)은 IEEE 802.1AE 표준으로 정의된 L2 홉 바이 홉 암호화 프로토콜입니다. 이더넷 프레임 단위로 기밀성(AES-GCM 암호화)과 무결성(ICV 검증)을 제공하며, IPsec이 L3 이상에서 동작하는 것과 달리 MACsec은 L2에서 직접 보호합니다.
| 용어 | 전체 이름 | 설명 |
|---|---|---|
| SecY | Security Entity | MACsec 프로토콜의 논리적 인스턴스. 하나의 네트워크 포트에 하나의 SecY가 대응 |
| SC | Secure Channel | 단방향 통신 채널. 송신/수신 각각 별도의 SC를 가짐 |
| SA | Secure Association | SC 내의 암호화 세션. 키 교체 시 새 SA가 생성되며 AN(0~3)으로 구분 |
| SAK | Secure Association Key | AES-GCM 128/256bit 대칭키. MKA 프로토콜로 자동 분배 또는 수동 설정 |
| SCI | Secure Channel Identifier | MAC주소(6B) + Port ID(2B) = 8바이트. SecTAG에 포함되어 SC를 식별 |
| PN | Packet Number | 32/64bit 시퀀스 번호. 리플레이 공격 방지에 사용 |
| MKA | MACsec Key Agreement | IEEE 802.1X-2010 정의. CAK(Connectivity Association Key)로부터 SAK를 파생·분배 |
리눅스 MACsec 설정
# MACsec 인터페이스 생성 (AES-GCM-128 암호화)
ip link add link eth0 macsec0 type macsec encrypt on
# 수신 SC 및 SA 설정 (상대방의 SCI와 키)
ip macsec add macsec0 rx port 1 address 00:11:22:33:44:55
ip macsec add macsec0 rx port 1 address 00:11:22:33:44:55 sa 0 \
pn 1 on key 00 \
aaaabbbbccccddddeeeeffffaaaabbbb
# 송신 SA 설정 (자신의 키)
ip macsec add macsec0 tx sa 0 pn 1 on key 01 \
11112222333344445555666677778888
# 인터페이스 활성화
ip link set macsec0 up
ip addr add 192.168.100.1/24 dev macsec0
# MACsec 상태 확인
ip macsec show
# AES-GCM-256 사용 (더 강한 암호화)
ip link add link eth0 macsec1 type macsec \
encrypt on cipher gcm-aes-256
| 소스 파일 | 역할 |
|---|---|
drivers/net/macsec.c |
MACsec 가상 디바이스 드라이버, SecY/SC/SA 관리 |
include/net/macsec.h |
MACsec 데이터 구조체 (struct macsec_secy, struct macsec_rx_sc) |
include/uapi/linux/if_macsec.h |
Netlink 인터페이스 상수, 속성 정의 |
net/8021q/ |
802.1Q VLAN — MACsec과 함께 사용 시 스택 순서 참고 |
macsec_ops 콜백을 통해 오프로드하며,
ethtool -k eth0 | grep macsec로 지원 여부를 확인할 수 있습니다.
HW offload 시 CPU 오버헤드 없이 라인 레이트에서 L2 암호화가 가능합니다.
Random MAC과 프라이버시
MAC 주소는 디바이스를 추적하는 데 사용될 수 있어, 최신 운영체제와 디바이스들은 MAC 주소 랜덤화(MAC Address Randomization)를 적극 도입하고 있습니다.
MAC 랜덤화 유형
| 유형 | 시점 | 목적 | 구현 |
|---|---|---|---|
| Wi-Fi 프로브 랜덤화 | 네트워크 스캔 시 | AP가 스캔 요청으로 디바이스 추적 방지 | iOS 8+, Android 8+, Windows 10+ |
| 연결별 랜덤화 | 네트워크 연결 시 | 네트워크별 다른 MAC으로 교차 추적 방지 | iOS 14+, Android 10+ |
커널 addr_assign_type |
인터페이스 생성 시 | 가상 디바이스의 자동 MAC 할당 | NET_ADDR_RANDOM (3) |
/* include/linux/netdevice.h — addr_assign_type 값 */
#define NET_ADDR_PERM 0 /* EEPROM/NIC 펌웨어 영구 주소 */
#define NET_ADDR_RANDOM 1 /* 커널이 랜덤 생성 */
#define NET_ADDR_STOLEN 2 /* 다른 디바이스에서 복사 */
#define NET_ADDR_SET 3 /* 사용자가 수동 설정 */
/* eth_hw_addr_random() — 드라이버가 EEPROM MAC이 없을 때 호출 */
void eth_hw_addr_random(struct net_device *dev)
{
u8 addr[ETH_ALEN];
eth_random_addr(addr); /* 랜덤 + 로컬 유니캐스트 보장 */
__dev_addr_set(dev, addr, ETH_ALEN);
dev->addr_assign_type = NET_ADDR_RANDOM;
}
/sys/class/net/eth0/addr_assign_type으로
현재 MAC이 어떻게 할당되었는지 확인할 수 있습니다.
0이면 EEPROM 영구 주소, 1이면 커널 랜덤 생성, 3이면 사용자 설정입니다.
Wi-Fi/802.11 MAC 주소
이더넷과 달리 Wi-Fi(IEEE 802.11) 프레임은 최대 4개의 MAC 주소 필드를 가집니다. 이는 무선 환경에서 AP(Access Point), 클라이언트(STA), 중계(WDS) 등 다양한 역할을 구분해야 하기 때문입니다. 리눅스 커널의 mac80211 서브시스템이 이를 관리합니다.
802.11 프레임의 4개 MAC 주소 필드
| 항목 | 이더넷 (802.3) | Wi-Fi (802.11) |
|---|---|---|
| MAC 주소 필드 수 | 2개 (DST, SRC) | 3~4개 (Addr1~4) |
| BSSID | 없음 | AP MAC 주소 = BSSID (BSS 식별) |
| 중계 시 | 라우터가 MAC 교체 | WDS/Mesh: Addr4로 원래 SA 보존 |
| 프레임 크기 | 최대 1518B (VLAN: 1522B) | 최대 2346B (QoS: 7935B, A-MSDU) |
| 커널 서브시스템 | net/ethernet/, net/core/ | net/mac80211/, net/wireless/ |
| MAC 랜덤화 | 일반적이지 않음 | 프로브/연결별 랜덤화 표준 |
mac80211 서브시스템에서의 MAC 관리
/* net/mac80211/iface.c — 가상 인터페이스 MAC 설정 */
static int ieee80211_change_mac(struct net_device *dev, void *addr)
{
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
struct sockaddr *sa = addr;
int ret;
/* 인터페이스가 활성 상태이면 거부 (연결 끊어야 변경 가능) */
if (ieee80211_sdata_running(sdata))
return -EBUSY;
ret = eth_mac_addr(dev, sa);
if (ret == 0)
memcpy(sdata->vif.addr, sa->sa_data, ETH_ALEN);
return ret;
}
/* Wi-Fi 프레임을 이더넷으로 변환 — AP/Bridge 경로 */
/* net/wireless/util.c */
int ieee80211_data_to_8023(struct sk_buff *skb,
const u8 *addr, enum nl80211_iftype iftype)
{
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
/* ToDS/FromDS 비트에 따라 DA, SA 결정 */
/* 802.11 헤더 제거 → ethhdr(DA + SA + EtherType) 삽입 */
/* ... */
}
Wi-Fi MAC 랜덤화와 리눅스
# NetworkManager: Wi-Fi 연결별 MAC 랜덤화 설정
nmcli connection modify "MyWiFi" wifi.cloned-mac-address random
nmcli connection modify "MyWiFi" wifi.mac-address-randomization always
# iwd(Intel Wireless Daemon) MAC 랜덤화
# /etc/iwd/main.conf
# [General]
# AddressRandomization=network # 네트워크별 고정 랜덤 MAC
# AddressRandomizationRange=full # 전체 48비트 랜덤 (nic도 랜덤)
# wpa_supplicant MAC 랜덤화
# wpa_supplicant.conf:
# mac_addr=1 # 스캔 시 랜덤 MAC
# preassoc_mac_addr=1 # 연결 전 랜덤 MAC
# 현재 Wi-Fi 인터페이스의 MAC 확인
iw dev wlan0 info | grep addr
ip link show wlan0
Device Tree/ACPI MAC 프로비저닝
임베디드 시스템과 서버 환경에서 NIC의 MAC 주소는 EEPROM 외에도 Device Tree(DT), ACPI, 부트로더 명령줄 등 다양한 소스에서 제공될 수 있습니다. 커널은 이러한 소스를 우선순위에 따라 탐색합니다.
Device Tree MAC 프로퍼티
| DT 프로퍼티 | 설정 주체 | 용도 |
|---|---|---|
mac-address |
부트로더(U-Boot 등)가 런타임에 기록 | EEPROM/OTP에서 읽은 MAC을 DT에 패치. 가장 높은 우선순위 |
local-mac-address |
DT 소스 파일에 정적으로 기록 | 기본 MAC. 부트로더가 mac-address를 설정하지 않을 때 사용 |
nvmem-cells |
NVMEM 프레임워크 참조 | OTP/EEPROM의 MAC 셀을 참조하여 커널이 직접 읽기 |
/* drivers/of/net.c — DT에서 MAC 주소 읽기 */
int of_get_mac_address(struct device_node *np, u8 *addr)
{
int ret;
/* 1순위: "mac-address" 프로퍼티 (부트로더가 패치) */
ret = of_get_mac_addr(np, "mac-address", addr);
if (!ret)
return 0;
/* 2순위: "local-mac-address" 프로퍼티 (DT 소스 정적) */
ret = of_get_mac_addr(np, "local-mac-address", addr);
if (!ret)
return 0;
/* 3순위: NVMEM 셀 참조 */
return of_get_mac_addr_nvmem(np, addr);
}
/* 드라이버 사용 예시 (TI AM335x CPSW) */
static int cpsw_probe(struct platform_device *pdev)
{
u8 mac[ETH_ALEN];
int ret = of_get_mac_address(pdev->dev.of_node, mac);
if (ret) {
/* DT에 MAC 없음 → 랜덤 생성 */
eth_hw_addr_random(ndev);
} else {
eth_hw_addr_set(ndev, mac);
}
}
U-Boot에서 DT MAC 패치
# U-Boot 환경 변수에서 DT로 MAC 패치
# U-Boot 프롬프트:
setenv ethaddr 00:1A:2B:3C:4D:5E
# 부팅 시 U-Boot이 DT의 ethernet 노드에 mac-address 프로퍼티를 자동 패치
# 커널 명령줄로 MAC 전달 (일부 드라이버 지원)
# bootargs에 추가:
# macaddr=00:1A:2B:3C:4D:5E
# NVMEM 셀 참조 DT 예시
# ethernet@4a100000 {
# nvmem-cells = <&mac_addr>;
# nvmem-cell-names = "mac-address";
# };
# &otp {
# mac_addr: mac-address@1e {
# reg = <0x1e 0x6>;
# };
# };
device_get_mac_address()가
ACPI _DSD(Device Specific Data) 패키지에서 "mac-address" 프로퍼티를 찾습니다.
일부 서버 BMC는 SMBIOS Type 11(OEM Strings)에 MAC을 기록하며,
커널 드라이버가 이를 파싱합니다.
MAC 인증과 접근 제어 (802.1X MAB)
기업 네트워크에서 MAC 주소를 기반으로 디바이스 접근을 제어하는 방법은 크게 MAC Authentication Bypass(MAB), hostapd MAC ACL, Bridge Port MAC 제한이 있습니다.
hostapd MAC ACL (Access Control List)
| 설정 | macaddr_acl 값 | 동작 |
|---|---|---|
| 허용 목록 (Whitelist) | 1 | accept_mac_file에 있는 MAC만 허용 |
| 차단 목록 (Blacklist) | 0 | deny_mac_file에 있는 MAC을 거부, 나머지 허용 |
| RADIUS 기반 | 2 | RADIUS 서버에 MAC을 질의하여 인증 (MAB) |
# hostapd MAC ACL 설정 예시
# /etc/hostapd/hostapd.conf
macaddr_acl=1 # 허용 목록 모드
accept_mac_file=/etc/hostapd/accept # 허용 MAC 목록 파일
# /etc/hostapd/accept (한 줄에 MAC 하나)
# AA:BB:CC:DD:EE:FF
# 11:22:33:44:55:66
# FreeRADIUS MAB 사용자 파일 (/etc/freeradius/users)
# MAC을 소문자, 하이픈 구분으로 등록
# aa-bb-cc-dd-ee-ff Cleartext-Password := "aa-bb-cc-dd-ee-ff"
# Tunnel-Type = VLAN,
# Tunnel-Medium-Type = IEEE-802,
# Tunnel-Private-Group-Id = 100
# Linux Bridge에서 포트별 MAC 학습 제한
bridge link set dev eth1 learning off
bridge fdb add AA:BB:CC:DD:EE:FF dev eth1 master static
# learning off + 정적 FDB = 화이트리스트 효과
XDP/TC MAC 고속 처리
eBPF 기반의 XDP(eXpress Data Path)는 NIC 드라이버 수준에서 패킷을 처리하여 커널 네트워크 스택을 거치지 않고 10Gbps+ 라인 레이트에서 MAC 기반 필터링, 리다이렉트, 헤더 조작을 수행합니다. TC(Traffic Control) 계층의 eBPF도 유사한 MAC 처리를 제공하되 더 풍부한 기능(egress 훅, skb 접근)을 지원합니다.
XDP MAC 스왑 (L2 반사기)
/* XDP: MAC 주소 스왑 후 TX — L2 반사기(reflector) 또는 로드밸런서 기초 */
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <bpf/bpf_helpers.h>
SEC("xdp")
int xdp_mac_swap(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_DROP;
/* SRC MAC ↔ DST MAC 스왑 */
__u8 tmp[ETH_ALEN];
__builtin_memcpy(tmp, eth->h_source, ETH_ALEN);
__builtin_memcpy(eth->h_source, eth->h_dest, ETH_ALEN);
__builtin_memcpy(eth->h_dest, tmp, ETH_ALEN);
return XDP_TX; /* 동일 NIC로 즉시 반송 */
}
char _license[] SEC("license") = "GPL";
XDP MAC 기반 L2 리다이렉트 (스위칭)
/* XDP: MAC 주소 기반으로 다른 인터페이스로 리다이렉트 — L2 소프트웨어 스위치 */
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <bpf/bpf_helpers.h>
/* MAC → ifindex 매핑 맵 (FDB 역할) */
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 4096);
__type(key, __u8[ETH_ALEN]);
__type(value, __u32); /* target ifindex */
} fdb_map SEC(".maps");
/* devmap: 리다이렉트 대상 인터페이스 맵 */
struct {
__uint(type, BPF_MAP_TYPE_DEVMAP);
__uint(max_entries, 256);
__type(key, __u32); /* ifindex */
__type(value, __u32); /* ifindex */
} tx_port SEC(".maps");
SEC("xdp")
int xdp_l2_switch(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_DROP;
/* 1. 소스 MAC 학습: {src_mac → ingress_ifindex} */
__u32 ingress = ctx->ingress_ifindex;
bpf_map_update_elem(&fdb_map, eth->h_source, &ingress, BPF_ANY);
/* 2. 목적지 MAC 조회 */
__u32 *dst_ifindex = bpf_map_lookup_elem(&fdb_map, eth->h_dest);
if (!dst_ifindex)
return XDP_PASS; /* FDB miss → 커널 스택으로 (플러딩) */
/* 3. 목적지 인터페이스로 리다이렉트 */
return bpf_redirect_map(&tx_port, *dst_ifindex, 0);
}
char _license[] SEC("license") = "GPL";
# XDP 프로그램 컴파일 및 적용
clang -O2 -g -target bpf -c xdp_l2_switch.c -o xdp_l2_switch.o
# eth0에 XDP 프로그램 부착 (native 모드)
ip link set dev eth0 xdpdrv obj xdp_l2_switch.o sec xdp
# XDP 프로그램 상태 확인
ip link show eth0
# → ... xdp/id:42 ...
# devmap에 리다이렉트 대상 추가 (bpftool)
bpftool map update name tx_port key hex 03 00 00 00 value hex 04 00 00 00
# XDP 통계 확인
bpftool prog show id 42
ethtool -S eth0 | grep xdp
# XDP 제거
ip link set dev eth0 xdp off
xdpoffload 모드).
MAC 기반 필터링/리다이렉트를 CPU 개입 없이 라인 레이트로 처리합니다.
ip link set dev eth0 xdpoffload obj prog.o sec xdp로 적용합니다.
IEEE 802.1CB FRER와 TSN
IEEE 802.1CB FRER(Frame Replication and Elimination for Reliability)은 TSN(Time-Sensitive Networking) 프로파일의 핵심 구성 요소로, 동일 프레임을 다중 경로로 복제 전송한 뒤 수신 측에서 중복을 제거하여 제로 패킷 손실을 보장합니다. 자동차, 산업 자동화, 항공 네트워크에서 사용됩니다.
| 표준 | 이름 | MAC 관련 역할 |
|---|---|---|
| 802.1CB | FRER | R-TAG를 이더넷 MAC 헤더 뒤에 삽입하여 프레임 복제/제거 |
| 802.1Qbv | Time-Aware Shaper | 시간 기반 게이트로 특정 MAC 트래픽에 보장된 전송 슬롯 할당 |
| 802.1Qci | Per-Stream Filtering | MAC+VLAN+프레임 크기 기반 스트림 필터링 및 폴리싱 |
| 802.1AS | gPTP | PTP 멀티캐스트 MAC(01:80:C2:00:00:0E)으로 시간 동기화 |
# 리눅스 TSN 지원 확인 (커널 5.10+)
# tc qdisc로 802.1Qbv TAS (Time-Aware Shaper) 설정
tc qdisc replace dev eth0 parent root handle 100 taprio \
num_tc 3 \
map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 \
queues 1@0 1@1 2@2 \
base-time 1000000000 \
sched-entry S 01 300000 \
sched-entry S 02 300000 \
sched-entry S 04 400000 \
flags 0x2
# FRER은 커널 6.x+에서 tc flower + ct 조합으로 부분 구현
# 전체 802.1CB는 스마트NIC/스위치 ASIC에서 주로 HW 구현
# Intel i225/i226 NIC: TSN 지원 (igc 드라이버)
ethtool -T eth0 # PTP 타임스탬프 지원 확인
실전 디버깅과 도구
MAC 주소 관련 주요 명령
| 명령 | 용도 | 출력 예시 |
|---|---|---|
ip link show eth0 |
현재 MAC, 상태, MTU 조회 | link/ether 00:1a:2b:3c:4d:5e brd ff:ff:ff:ff:ff:ff |
ethtool -P eth0 |
영구(EEPROM) MAC 조회 | Permanent address: 00:1a:2b:3c:4d:5e |
ip neigh show |
ARP/Neighbor 캐시 조회 | 192.168.1.1 dev eth0 lladdr aa:bb:cc:dd:ee:ff REACHABLE |
bridge fdb show |
Bridge FDB 테이블 조회 | aa:bb:cc:dd:ee:ff dev eth0 master br0 |
ip maddr show |
멀티캐스트 MAC 그룹 조회 | link 01:00:5e:00:00:01 |
cat /sys/class/net/eth0/address |
sysfs를 통한 MAC 조회 | 00:1a:2b:3c:4d:5e |
tcpdump로 MAC 수준 분석
# 특정 MAC에서 오는 프레임만 캡처
tcpdump -i eth0 ether src 00:1a:2b:3c:4d:5e -nn
# 특정 MAC으로 가는 프레임만 캡처
tcpdump -i eth0 ether dst ff:ff:ff:ff:ff:ff -nn
# ARP 패킷만 캡처 (MAC 해석 과정 관찰)
tcpdump -i eth0 arp -nn -e
# -e 옵션: 이더넷 헤더(MAC 주소) 출력
# VLAN 태그 포함 프레임 캡처
tcpdump -i eth0 vlan -nn -e
MAC 관련 트러블슈팅 체크리스트
| 증상 | 가능한 원인 | 진단 명령 |
|---|---|---|
| 통신 불가 (ARP 실패) | MAC 충돌, 잘못된 VLAN, 스위치 포트 보안 | arping -I eth0 192.168.1.1, tcpdump -i eth0 arp -e |
| 간헐적 연결 끊김 | MAC 주소 중복 (Duplicate MAC) | arping -D -I eth0 -c 3 192.168.1.10 (중복 감지) |
| Bridge에서 플러딩 과다 | FDB 학습 실패, 에이징 시간 과소 | bridge fdb show, bridge -s fdb show |
| VM 네트워크 불통 | SR-IOV spoofchk, MAC 미할당 | ip link show eth0 (VF 상태), dmesg | grep -i spoof |
| macvlan 성능 저하 | HW 유니캐스트 필터 초과 → 프로미스큐어스 | ethtool -S eth0 | grep -i promisc |
| Neighbor 테이블 오버플로 | gc_thresh3 초과 (컨테이너 대규모 환경) | dmesg | grep "neighbour table overflow" |
커널 tracepoint를 이용한 MAC 이벤트 추적
# Neighbor 이벤트 추적 (ARP 학습/만료)
echo 1 > /sys/kernel/debug/tracing/events/neigh/neigh_update/enable
cat /sys/kernel/debug/tracing/trace_pipe
# Bridge FDB 이벤트 추적
echo 1 > /sys/kernel/debug/tracing/events/bridge/fdb_delete/enable
echo 1 > /sys/kernel/debug/tracing/events/bridge/br_fdb_update/enable
cat /sys/kernel/debug/tracing/trace_pipe
# NET_DEVICE 이벤트 (MAC 변경 포함) perf 추적
perf trace -e 'net:*' -- sleep 5
OUI 레지스트리와 주요 벤더
IEEE는 OUI(Organizationally Unique Identifier)를 제조사에 할당하고 공개 레지스트리를 유지합니다. MAC 주소의 상위 3바이트를 보면 디바이스 제조사를 식별할 수 있습니다.
주요 벤더 OUI 예시
| OUI | 벤더 | 비고 |
|---|---|---|
00:50:56 | VMware | VMware VM NIC |
52:54:00 | QEMU/KVM | QEMU 가상 NIC 기본값 |
02:42:AC | Docker | Docker 컨테이너 기본 MAC 접두사 |
00:15:5D | Microsoft | Hyper-V 가상 NIC |
08:00:27 | Oracle | VirtualBox 가상 NIC |
00:1A:11 | GCE 가상 NIC | |
00:0C:29 | VMware | VMware 자동 생성 MAC |
/usr/share/misc/oui.txt 또는 macchanger -l로
로컬에서 확인할 수도 있습니다. 네트워크 포렌식이나 디바이스 식별에 유용합니다.
커널 소스 주요 경로
| 파일/디렉터리 | 내용 |
|---|---|
include/linux/etherdevice.h |
MAC 주소 판별/생성 인라인 함수 (is_valid_ether_addr, eth_random_addr 등) |
include/uapi/linux/if_ether.h |
struct ethhdr, ETH_ALEN, EtherType 상수 |
include/net/neighbour.h |
struct neighbour, struct neigh_table, NUD 상태 상수 |
net/core/dev.c |
dev_set_mac_address(), __dev_set_rx_mode() |
net/core/dev_addr_lists.c |
dev_uc_add(), dev_mc_add(), 유니캐스트/멀티캐스트 리스트 관리 |
net/ipv4/arp.c |
arp_process(), arp_send_dst(), ARP 프로토콜 처리 |
net/core/neighbour.c |
Neighbor 서브시스템 코어 (neigh_update(), GC, 상태 머신) |
net/bridge/br_fdb.c |
Bridge FDB (br_fdb_update(), br_fdb_find_rcu()) |
net/bridge/br_input.c |
br_handle_frame(), 브리지 수신 경로 |
drivers/net/macvlan.c |
macvlan/macvtap 드라이버 |
drivers/net/ethernet/intel/ixgbe/ |
Intel 10G NIC — VF MAC 관리, RAR 프로그래밍 예시 |
관련 문서
- sk_buff 자료구조 — 패킷 버퍼의 MAC 헤더 포인터,
eth_hdr(skb)접근 경로 - 네트워크 스택 — 전체 네트워크 스택 구조와 계층별 처리 흐름
- Network Device 드라이버 —
ndo_set_mac_address,ndo_set_rx_mode구현
- Bridge/VLAN/Bonding — 브리지 FDB, VLAN 필터링, 본딩 MAC 정책
- 네트워크 스택 고급 — RSS, RPS 등 멀티코어 패킷 분산과 MAC 해시
- IP (IPv4/IPv6) — ARP/NDP와 연계된 IP 계층 처리
- 네트워크 네임스페이스 — 컨테이너별 독립 MAC/ARP 테이블
- VFIO & mdev — SR-IOV VF 패스스루와 MAC 관리
- TUN/TAP — 가상 인터페이스 MAC 할당 메커니즘