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 주소
이더넷 프레임(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 구조를 검토하세요.
가상 디바이스와 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 보안 위협과 방어
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이면 사용자 설정입니다.
실전 디버깅과 도구
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 할당 메커니즘