DSA Tagging 심화

DSA(Distributed Switch Architecture)는 내장 스위치 칩을 Linux 네트워크 스택에 자연스럽게 통합하는 프레임워크입니다. 이 문서는 DSA tagging 메커니즘의 태그 프로토콜 헤더 바이트 분석, TX 인젝션/RX 추출 경로, MTU 헤드룸 관리, 멀티칩 패브릭 아키텍처, FDB/MDB 학습, tc 오프로딩, Device Tree 모델링, 드라이버 디버깅까지 DSA 데이터면의 모든 것을 심층적으로 정리합니다.

전제 조건: Bridge/VLAN/Bonding 문서를 먼저 읽으세요. 브리지 FDB, VLAN 필터링, CPU 포트 개념을 알고 있어야 DSA 태그 경로를 이해하기 쉽습니다.
일상 비유: 이 개념은 물류 센터 내부 라벨과 비슷합니다. 외부 상자(이더넷 프레임)는 같아도 내부 라벨(DSA 태그)로 어느 포트에서 왔고 어디로 보낼지 분류합니다.

핵심 요약

  • CPU 포트 -- 스위치와 호스트 CPU를 연결하는 내부 업링크
  • 사용자 포트 -- 실제 물리 포트(netdev로 노출)
  • tag driver -- 제조사별 DSA 태그 삽입/제거 로직
  • dsa_slave -- 사용자 포트용 net_device 구현
  • switchdev -- 브리지/VLAN/FDB를 하드웨어로 오프로드

단계별 이해

  1. 포트 초기화
    DSA 트리가 구성되고 사용자 포트 netdev가 생성됩니다.
  2. 송신 시 태깅
    CPU 포트로 나가기 직전에 목적 포트 정보를 태그로 삽입합니다.
  3. 수신 시 디태깅
    CPU가 받은 프레임에서 소스 포트 정보를 해석해 올바른 netdev로 전달합니다.
  4. 브리지/ACL 오프로드
    switchdev 연계로 데이터면 룰을 스위치 칩에 내려 보냅니다.

DSA 데이터 경로

DSA 프레임워크는 내장 이더넷 스위치의 각 포트를 독립적인 net_device로 노출하여, 표준 Linux 네트워크 도구(ip, bridge, tc 등)로 관리할 수 있게 합니다. 핵심은 CPU 포트를 통해 호스트와 스위치 간에 프레임을 교환할 때 태그로 포트 정보를 인코딩하는 것입니다.

DSA 데이터 경로 전체 구조 사용자 공간: ip / bridge / tc / tcpdump 등 lan0 netdev dsa_slave_dev lan1 netdev dsa_slave_dev lan2 netdev dsa_slave_dev DSA Tag Driver xmit() / rcv() 태그 삽입/제거 CPU 포트 (master) eth0 (SoC MAC) 내장 스위치 ASIC FDB 학습, VLAN 필터링, ACL/TC 오프로드, STP, IGMP 스누핑 port0 | port1 | port2 | port3 | port4 | cpu_port
DSA 포트 유형: DSA는 세 가지 포트 유형을 정의합니다. 사용자 포트(user port)는 외부 디바이스가 연결되는 물리 포트이고, CPU 포트는 호스트 SoC MAC에 연결되며, DSA 포트는 멀티칩 구성에서 스위치 간 연결(cascade/trunk)에 사용됩니다.
포트 유형역할netdev 노출태그 처리
User port외부 디바이스 연결lan0, lan1 등TX: 태그 삽입, RX: 태그 제거
CPU port호스트 SoC MAC 연결eth0 (master)태그가 붙은 프레임 전달
DSA port스위치 간 cascade 연결없음 (내부)다중 칩 태그 전달
Unused port미사용 포트없음비활성화

DSA 트리 구조

DSA는 하나 이상의 스위치 칩을 트리(tree)로 조직합니다. 각 스위치는 트리 내에서 고유한 인덱스를 가지며, dsa,member = <tree-index switch-index> DTS 속성으로 식별됩니다.

/* DSA 트리 핵심 자료구조 */
struct dsa_switch_tree {
    struct list_head    list;         /* 전역 트리 목록 */
    unsigned int        index;        /* 트리 인덱스 */
    struct kref         refcount;
    struct list_head    ports;        /* 모든 포트 목록 */
    struct dsa_platform_data *pd;
    bool                setup;
    const struct dsa_device_ops *tag_ops; /* 태그 프로토콜 */
};

struct dsa_switch {
    struct dsa_switch_tree *dst;     /* 소속 트리 */
    unsigned int        index;        /* 트리 내 스위치 인덱스 */
    unsigned int        num_ports;
    struct dsa_port     *ports;
    const struct dsa_switch_ops *ops; /* 드라이버 콜백 */
};

주요 태그 프로토콜

DSA 태그 프로토콜은 제조사마다 다르며, 커널 소스의 net/dsa/tag_*.c 파일에 각각 구현되어 있습니다. 태그 위치(header/tail/EtherType 재사용), 크기, 인코딩 방식이 모두 다릅니다.

프로토콜커널 파일위치크기 (바이트)대표 칩셋
tag_dsatag_dsa.c헤더 (Src MAC 뒤)4Marvell 88E6xxx (legacy)
tag_edsatag_edsa.c헤더 (EtherType 재정의)8Marvell 88E6xxx (확장)
tag_ocelottag_ocelot.c헤더 (프레임 앞)16Microchip Ocelot/Felix
tag_ocelot_8021qtag_ocelot_8021q.c802.1Q 재사용4Microchip Ocelot (NPI-less)
tag_ksztag_ksz.c테일1~3Microchip KSZ 시리즈
tag_rtl4_atag_rtl4_a.c헤더 (EtherType)4Realtek RTL8365MB
tag_rtl8_4tag_rtl8_4.c헤더 (EtherType)8Realtek RTL8366RB/RTL8365
tag_mtktag_mtk.c헤더 (특수 EtherType)4MediaTek MT7530/MT7531
tag_brcmtag_brcm.c헤더 (Broadcom 전용)4Broadcom SF2/BCM53xx
tag_brcm_prependtag_brcm.c헤더 (프레임 앞)4Broadcom (prepend 변형)
tag_sja1105tag_sja1105.cVLAN 기반 + 메타프레임가변NXP SJA1105
tag_8021qtag_8021q.c802.1Q VLAN 재사용4공통 소프트웨어 경로
tag_hellcreektag_hellcreek.c테일2Hirschmann Hellcreek
tag_lan9303tag_lan9303.c헤더 (특수)4SMSC/Microchip LAN9303
tag_xrs700xtag_xrs700x.c테일1Arrow XRS700x
태그 프로토콜 선택: 스위치 칩이 지원하는 태그 프로토콜은 하드웨어에 의해 결정됩니다. 같은 칩셋이 여러 태그 모드를 지원하기도 합니다(예: Marvell의 DSA vs EDSA, Ocelot의 NPI vs 8021q). 커널 6.x에서는 tag_proto devlink 파라미터로 런타임에 태그 프로토콜을 변경할 수 있는 칩도 있습니다.
# 현재 사용 중인 태그 프로토콜 확인
cat /sys/class/net/lan0/dsa/tagging

# devlink로 태그 프로토콜 변경 (지원하는 드라이버만)
devlink dev param set pci/0000:00:00.0 name tag_protocol value DSA_TAG_PROTO_OCELOT_8021Q cmode runtime

태그 포맷 심화

DSA 태그는 단순히 포트 번호만 담는 필드가 아니라, 디바이스 인덱스, 트랩 이유, VLAN/TC 우선순위, 타임스탬프 플래그 등 다양한 메타데이터를 포함할 수 있습니다. 아래에서 주요 태그 프로토콜의 바이트 레벨 구조를 분석합니다.

Marvell DSA 태그 (4바이트)

Marvell 88E6xxx 시리즈의 기본 DSA 태그는 Src MAC과 EtherType 사이에 4바이트를 삽입합니다.

Marvell DSA 태그 4바이트 구조 이더넷 프레임 (DSA 태그 포함): Dst MAC (6B) Src MAC (6B) DSA Tag (4B) EtherType (2B) Payload FCS (4B) DSA 태그 4바이트 비트 필드: 바이트 0 Mode[1:0] | TagCmd[1:0] | SrcDev[4:3] 바이트 1 SrcDev[2:0] | SrcPort[4:0] 바이트 2 TypeOrLen | PRI[2:0] | CFI | VID[11:8] 바이트 3 VID[7:0] Mode[1:0]: 00=FROM_CPU, 01=TO_CPU, 10=TO_SNIFFER, 11=FORWARD TagCmd[1:0]: 태그 명령 (무태그/단일태그/이중태그 구분) SrcDev[4:0]: 소스 디바이스 인덱스 (멀티칩 패브릭에서 칩 구분) SrcPort[4:0]: 소스 포트 번호 (0~31) PRI[2:0]/CFI/VID[11:0]: 802.1Q 우선순위, CFI, VLAN ID 필드
/* net/dsa/tag_dsa.c - Marvell DSA 태그 xmit 핵심 로직 */
static struct sk_buff *dsa_xmit(struct sk_buff *skb, struct net_device *dev)
{
    struct dsa_port *dp = dsa_slave_to_port(dev);
    u8 *dsa_header;

    /* MAC 헤더 뒤에 4바이트 공간 확보 */
    if (skb_cow_head(skb, DSA_HLEN) < 0)
        return NULL;

    skb_push(skb, DSA_HLEN);               /* 4바이트 삽입 공간 확보 */
    dsa_alloc_etype_header(skb, DSA_HLEN);  /* MAC 헤더 앞쪽으로 이동 */

    dsa_header = dsa_etype_header_pos_tx(skb);

    /* FROM_CPU 모드, 목적 포트 지정 */
    dsa_header[0] = (DSA_CMD_FROM_CPU << 6) |
                    (dp->ds->index & 0x1f);
    dsa_header[1] = dp->index << 3;
    dsa_header[2] = 0;   /* 우선순위, VLAN 등 */
    dsa_header[3] = 0;

    return skb;
}

Marvell EDSA 태그 (8바이트)

EDSA(Extended DSA)는 DSA 태그를 8바이트로 확장하여 더 많은 메타데이터를 포함합니다. EtherType 0xDADA로 식별되며, 추가 4바이트에 트렁크 정보, PTP 타임스탬프 힌트, 정책 미러링 등의 필드가 포함됩니다.

오프셋필드비트설명
0~1EtherType160xDADA (EDSA 식별자)
2Mode/TagCmd/SrcDev 상위8DSA와 동일한 필드 배치
3SrcDev 하위/SrcPort8소스 디바이스/포트
4PRI/CFI/VID 상위8802.1Q 우선순위 정보
5VID 하위8VLAN ID 하위 8비트
6Trunk/Timestamp 힌트8트렁크 그룹, PTP 플래그
7확장 필드8FPri, 정책 미러, 코드

Microchip KSZ 테일 태그 (1~3바이트)

KSZ 시리즈는 프레임 에 태그를 부착합니다. 모델에 따라 1바이트(KSZ8795), 2바이트(KSZ9477), 3바이트 변형이 있습니다.

/* net/dsa/tag_ksz.c - KSZ9477 테일 태그 (2바이트) */
/* TX: 마지막 2바이트에 목적 포트 비트맵 */
/*   [바이트0] 오버라이드(1) | 포트 비트맵 상위 */
/*   [바이트1] 포트 비트맵 하위 (bit N = port N) */

/* RX: 소스 포트 + 타임스탬프 */
/*   [바이트0] 타임스탬프(1) | 소스 포트[2:0] */
/*   [바이트1] 타임스탬프 데이터 */

static struct sk_buff *ksz9477_xmit(struct sk_buff *skb,
                                      struct net_device *dev)
{
    struct dsa_port *dp = dsa_slave_to_port(dev);
    u8 *tag;
    u16 val;

    /* 프레임 끝에 2바이트 추가 */
    if (skb_put_padto(skb, ETH_ZLEN + KSZ9477_INGRESS_TAG_LEN))
        return NULL;

    tag = skb_put(skb, KSZ9477_INGRESS_TAG_LEN);
    val = BIT(dp->index);  /* 목적 포트 비트맵 */
    val |= KSZ9477_TAIL_TAG_OVERRIDE;
    put_unaligned_be16(val, tag);

    return skb;
}

MediaTek MT7530 태그 (4바이트)

MediaTek MT7530/MT7531은 EtherType 0x8100과 유사한 위치에 4바이트 태그를 삽입합니다. 상위 2바이트가 0x8100이 아닌 0x05xx 형태로 구분됩니다.

TX 오프셋비트필드설명
바이트 0~1[15:0]특수 태그 헤더DPID(목적 포트 비트맵)
바이트 2~3[15:0]VLAN 정보VID, 우선순위 힌트
RX 오프셋비트필드설명
바이트 0~1[15:0]특수 태그 헤더소스 포트, ARL 위반 플래그
바이트 2~3[15:0]VLAN/이유VID, 트랩 이유 코드

Microchip Ocelot 태그 (16바이트)

Ocelot/Felix 칩은 가장 정보량이 많은 태그를 사용합니다. 16바이트의 IFH(Injection Frame Header) 또는 EFH(Extraction Frame Header)를 프레임 앞에 삽입합니다.

/* Ocelot IFH (TX) 주요 필드 */
struct ocelot_ifh {
    u64 bypass:    1;   /* 분류 엔진 우회 */
    u64 dest:     12;   /* 목적 포트 마스크 */
    u64 src_port: 4;    /* 소스 포트 */
    u64 qos_class:3;    /* QoS 클래스 */
    u64 dp:       2;    /* 드롭 우선순위 */
    u64 tag_type: 3;    /* VLAN 태그 타입 */
    u64 vlan_vid: 12;   /* VLAN ID */
    u64 rew_op:   10;   /* 재작성 명령 (PTP 등) */
    /* ... 총 128비트 (16바이트) */
};

tag_8021q 소프트웨어 태그

tag_8021q는 하드웨어 전용 태그가 아닌 표준 802.1Q VLAN 태그를 DSA 메타데이터로 재활용하는 소프트웨어 방식입니다. 하드웨어 NPI(Network Processor Interface) 포트가 없는 구성이나 tag_proto 교체 시 폴백으로 사용됩니다.

DSA 태그 삽입 위치 비교 Header Tag 방식 (Marvell DSA/EDSA, Broadcom, MTK) Dst MAC Src MAC DSA Header Tag EtherType Payload + FCS Tail Tag 방식 (KSZ, Hellcreek, XRS700x) Dst MAC Src MAC EtherType Payload Tail Tag tag_8021q 방식 (소프트웨어 VLAN 재해석) Dst MAC Src MAC 802.1Q 재해석 태그 Inner EtherType Payload + FCS tag_8021q VID 인코딩: VID = (switch_id << 8) | (port_id) | 0x1000 (방향 구분) RX/TX용 VID가 분리되며, 스위치 하드웨어 VLAN 테이블에 사전 프로비저닝 필요
/* net/dsa/tag_8021q.c - VID 인코딩 방식 */
#define DSA_8021Q_DIR_SHIFT      11
#define DSA_8021Q_DIR_RX         (1 << DSA_8021Q_DIR_SHIFT)
#define DSA_8021Q_DIR_TX         (2 << DSA_8021Q_DIR_SHIFT)
#define DSA_8021Q_SWITCH_ID_SHIFT 8
#define DSA_8021Q_PORT_SHIFT     0

/* TX VID: direction(2) | switch_id(3) | port(8) */
static u16 dsa_tag_8021q_tx_vid(struct dsa_port *dp)
{
    return DSA_8021Q_DIR_TX |
           (dp->ds->index << DSA_8021Q_SWITCH_ID_SHIFT) |
           dp->index;
}

커널 구현 포인트

DSA 코어 코드는 net/dsa/ 디렉토리에 위치하며, 태그 드라이버는 net/dsa/tag_*.c, 스위치 드라이버는 drivers/net/dsa/에 위치합니다. 핵심 등록 흐름을 살펴봅니다.

/* DSA 드라이버 등록 흐름 */

/* 1. 태그 드라이버 등록 (net/dsa/tag_*.c) */
static const struct dsa_device_ops my_tag_ops = {
    .name    = "my_tag",
    .proto   = DSA_TAG_PROTO_MY,
    .xmit    = my_tag_xmit,     /* TX: 태그 삽입 */
    .rcv     = my_tag_rcv,       /* RX: 태그 제거 + 소스 포트 복원 */
    .needed_headroom = MY_TAG_LEN,
    .needed_tailroom = 0,
    .flow_dissect    = my_tag_flow_dissect,
    .overhead        = MY_TAG_LEN,
};
DSA_TAG_DRIVER(my_tag_ops);
MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_MY);

/* 2. 스위치 드라이버 등록 (drivers/net/dsa/*.c) */
static const struct dsa_switch_ops my_switch_ops = {
    .get_tag_protocol = my_get_tag_protocol,
    .setup            = my_setup,
    .port_enable      = my_port_enable,
    .port_disable     = my_port_disable,
    .port_fdb_add     = my_port_fdb_add,
    .port_fdb_del     = my_port_fdb_del,
    .port_fdb_dump    = my_port_fdb_dump,
    .port_mdb_add     = my_port_mdb_add,
    .port_mdb_del     = my_port_mdb_del,
    .port_bridge_join = my_port_bridge_join,
    .port_bridge_leave= my_port_bridge_leave,
    .port_vlan_add    = my_port_vlan_add,
    .port_vlan_del    = my_port_vlan_del,
    .port_vlan_filtering = my_port_vlan_filtering,
    .cls_flower_add   = my_cls_flower_add,
    .cls_flower_del   = my_cls_flower_del,
    /* ... */
};
dsa_switch_ops 콜백 수: 커널 6.x 기준으로 dsa_switch_ops에는 80개 이상의 콜백 함수 포인터가 정의되어 있습니다. 대부분은 선택적(optional)이며, 필수 콜백은 get_tag_protocolsetup뿐입니다.
/* DSA 송신 경로 핵심 (net/dsa/slave.c) */
static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb,
                                   struct net_device *dev)
{
    struct dsa_slave_priv *p = netdev_priv(dev);
    struct sk_buff *nskb;

    dev_sw_netstats_tx_add(dev, 1, skb->len);

    memset(skb->cb, 0, sizeof(skb->cb));

    /* 태그 드라이버의 xmit() 호출 */
    nskb = p->xmit(skb, dev);
    if (!nskb) {
        kfree_skb(skb);
        return NETDEV_TX_OK;
    }

    /* master netdev로 전달 */
    skb->dev = dsa_slave_to_master(dev);
    dev_queue_xmit(skb);

    return NETDEV_TX_OK;
}

송수신 파이프라인 상세

TX는 사용자 포트 netdev에서 시작해 CPU 포트 직전에 태그가 삽입되고, RX는 CPU 포트에서 들어온 프레임을 태그 해석 후 해당 사용자 포트 netdev로 fan-out 합니다. 이 경로에서 GRO/LRO, checksum offload, timestamp 처리 순서가 깨지면 성능/정합성 문제가 발생합니다.

DSA TX/RX 파이프라인 상세 TX 경로 (사용자 포트 -> 스위치) 1. 사용자 공간 send() / sendmsg() 2. dsa_slave_xmit() - slave netdev TX 3. tag_ops->xmit() - 태그 삽입 4. skb->dev = master (CPU 포트) 5. dev_queue_xmit() -> master NIC TX RX 경로 (스위치 -> 사용자 포트) 1. master NIC RX -> NAPI poll 2. dsa_switch_rcv() 호출 (rx_handler) 3. tag_ops->rcv() - 태그 제거 + 소스 포트 4. skb->dev = dsa_slave[src_port] 5. netif_receive_skb() -> 상위 스택 스위치 ASIC (하드웨어 포워딩 엔진) CPU port TX CPU port RX 성능 체크 포인트 1. CPU 포트 속도: 스위치 ASIC과 SoC MAC 간 링크 속도가 병목이 됨 2. MTU 정합: 태그 오버헤드 포함 시 CPU 포트 MTU가 충분해야 함 3. GRO/GSO 경로: master NIC에서 GRO 처리 후 태그 디코딩 순서 주의 4. Checksum offload: 태그 삽입 시 L4 checksum 재계산 필요 여부 5. skb clone/linearize: RX fan-out 시 skb 공유 상태에서 태그 strip 비용 6. RPS/RFS: CPU 포트 단일 큐 -> 멀티코어 분배 설정 필요
/* DSA RX 경로 핵심 (net/dsa/slave.c) */
static rx_handler_result_t dsa_switch_rcv(struct sk_buff **pskb)
{
    struct sk_buff *skb = *pskb;
    struct dsa_port *cpu_dp;
    struct sk_buff *nskb;

    cpu_dp = skb->dev->dsa_ptr;
    if (unlikely(!cpu_dp))
        return RX_HANDLER_PASS;

    /* 태그 드라이버의 rcv() 호출 */
    nskb = cpu_dp->tag_ops->rcv(skb, skb->dev);
    if (!nskb) {
        kfree_skb(skb);
        return RX_HANDLER_CONSUMED;
    }

    skb = nskb;
    skb_push(skb, ETH_HLEN);
    skb->pkt_type = PACKET_HOST;
    skb->protocol = eth_type_trans(skb, skb->dev);

    /* slave netdev의 통계 업데이트 */
    dev_sw_netstats_rx_add(skb->dev, skb->len);

    netif_receive_skb(skb);
    return RX_HANDLER_CONSUMED;
}
RX 핸들러 등록: DSA는 master netdev에 netdev_rx_handler_register()dsa_switch_rcv를 등록합니다. 이는 브리지의 br_handle_frame과 동일한 메커니즘이므로, master netdev를 브리지에 직접 추가하면 충돌이 발생합니다. 브리지에 추가하는 것은 slave netdev여야 합니다.

브리지/VLAN 연동

DSA 포트는 Linux bridge에 일반 netdev처럼 붙지만, 실제 데이터면 처리(FDB 학습, VLAN 필터링)는 스위치 칩이 수행합니다. 이 "오프로드" 경로와 소프트웨어 폴백 경로의 차이를 이해해야 합니다.

# DSA 슬레이브를 브리지에 추가
ip link add name br0 type bridge
ip link set br0 up

ip link set lan0 master br0
ip link set lan1 master br0
ip link set lan2 master br0

# VLAN 필터링 활성화
ip link set br0 type bridge vlan_filtering 1

# VLAN 추가 (pvid: 기본 VLAN, untagged: 태그 제거)
bridge vlan add dev lan0 vid 100 pvid untagged
bridge vlan add dev lan1 vid 100 pvid untagged
bridge vlan add dev lan2 vid 200 pvid untagged
bridge vlan add dev br0 vid 100 self
bridge vlan add dev br0 vid 200 self

# 상태 확인
bridge vlan show
bridge -d link show
동작오프로드 (하드웨어)소프트웨어 폴백
FDB 학습스위치 ATU가 자동 학습CPU가 소프트웨어 학습
VLAN 필터링스위치 VTU가 필터링bridge VLAN 필터
STP 상태포트별 하드웨어 STP 상태소프트웨어 STP 데몬
IGMP 스누핑MDB 기반 멀티캐스트 필터링CPU 기반 스누핑
유니캐스트 포워딩라인 레이트 하드웨어 포워딩CPU 경유 (느림)
오프로드 확인법: bridge -d link show에서 offload 플래그가 표시되면 하드웨어 오프로드가 활성화된 것입니다. FDB 엔트리에서도 offloaded 플래그를 확인하세요.
# 오프로드 상태 확인 예시
$ bridge -d link show
3: lan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding
    hairpin off guard off root_block off fastleave off
    learning on flood on mcast_flood on bcast_flood on mcast_router 1
    mcast_to_unicast off neigh_suppress off vlan_tunnel off
    isolated off locked off mab off
    backup_port eth0 backup_nhid 0

$ bridge -d fdb show
aa:bb:cc:dd:ee:01 dev lan0 master br0 offloaded
aa:bb:cc:dd:ee:02 dev lan1 master br0 offloaded

FDB/MDB 학습과 오프로드

FDB(Forwarding Database)와 MDB(Multicast Database)는 스위치의 핵심 데이터 플레인 테이블입니다. DSA 환경에서는 하드웨어 학습과 소프트웨어 학습이 동시에 존재하며, 이 둘의 동기화가 중요합니다.

FDB/MDB 학습 및 동기화 흐름 하드웨어 학습 (ATU) 1. 프레임이 스위치 포트에 도착 2. ATU가 Src MAC + VLAN + Port 학습 3. ATU miss/violation 시 CPU로 트랩 4. 에이징: 일정 시간 무트래픽 시 삭제 5. switchdev FDB notify -> 소프트웨어 동기화 소프트웨어 학습 (bridge FDB) 1. bridge fdb add 명령 실행 2. switchdev SWITCHDEV_FDB_ADD notify 3. DSA 드라이버 port_fdb_add() 호출 4. 하드웨어 ATU에 정적 엔트리 추가 5. 성공 시 offloaded 플래그 설정 동기화 MDB (Multicast Database) 관리 IGMP/MLD 스누핑 연동: 1. 호스트가 IGMP Join(224.x.x.x 그룹) 전송 2. 스위치 CPU 포트로 IGMP 프레임 트랩 3. 브리지 IGMP 스누핑 로직이 MDB 엔트리 생성 4. switchdev 통해 port_mdb_add() -> 스위치 하드웨어 멀티캐스트 필터 업데이트 5. 결과: 해당 그룹 트래픽이 가입한 포트에만 전달 (불필요한 flooding 방지)
/* switchdev FDB 이벤트 처리 (개념 코드) */
static int my_port_fdb_add(struct dsa_switch *ds, int port,
                           const unsigned char *addr, u16 vid,
                           struct dsa_db db)
{
    /* 하드웨어 ATU에 정적 MAC 엔트리 추가 */
    struct atu_entry entry = {
        .mac    = addr,
        .vid    = vid,
        .port   = port,
        .state  = ATU_STATE_STATIC,
    };

    return hw_atu_write(ds, &entry);
}

static int my_port_mdb_add(struct dsa_switch *ds, int port,
                           const struct switchdev_obj_port_mdb *mdb,
                           struct dsa_db db)
{
    /* 멀티캐스트 그룹 MAC을 포트 비트맵에 추가 */
    return hw_mdb_add_port(ds, mdb->addr, mdb->vid, port);
}
# FDB/MDB 관리 명령어

# 정적 FDB 엔트리 추가
bridge fdb add aa:bb:cc:dd:ee:01 dev lan0 master static

# FDB 전체 출력 (offloaded 플래그 확인)
bridge fdb show

# MDB 확인
bridge mdb show

# 정적 MDB 엔트리 추가 (멀티캐스트 그룹)
bridge mdb add dev br0 port lan0 grp 239.1.1.1 permanent

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

switchdev 이벤트 연계

DSA는 switchdev notifier를 통해 브리지/VLAN/FDB 상태를 드라이버에 동기화합니다. 사용자 공간에서 보이는 bridge 명령은 제어면이고, 실제 데이터면은 switch chip 룰 업데이트로 이어집니다.

switchdev 이벤트트리거DSA 드라이버 콜백하드웨어 동작
SWITCHDEV_FDB_ADD_TO_DEVICEbridge fdb addport_fdb_add()ATU 정적 엔트리 추가
SWITCHDEV_FDB_DEL_TO_DEVICEbridge fdb delport_fdb_del()ATU 엔트리 삭제
SWITCHDEV_PORT_OBJ_ADD (VLAN)bridge vlan addport_vlan_add()VTU/VLAN 테이블 업데이트
SWITCHDEV_PORT_OBJ_ADD (MDB)bridge mdb add / IGMP joinport_mdb_add()멀티캐스트 필터 업데이트
SWITCHDEV_PORT_ATTR_SET (STP)STP 상태 변경port_stp_state_set()포트 STP 상태 변경
SWITCHDEV_PORT_ATTR_SET (learning)bridge link set learningport_bridge_flags()ATU 학습 활성화/비활성화
/* switchdev notifier 체인 (개념적 흐름) */

/* 1. 사용자: bridge vlan add dev lan0 vid 100 */
/* 2. bridge 코드 -> switchdev notifier 발생 */
/* 3. DSA 코어가 수신 -> 해당 스위치 드라이버 콜백 호출 */

static int my_port_vlan_add(struct dsa_switch *ds, int port,
                            const struct switchdev_obj_port_vlan *vlan,
                            struct netlink_ext_ack *extack)
{
    u16 vid = vlan->vid;
    bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
    bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;

    /* 하드웨어 VLAN 테이블에 포트+VID 추가 */
    hw_vtu_add_member(ds, vid, port, untagged);

    if (pvid)
        hw_set_default_vid(ds, port, vid);

    return 0;
}

tc 오프로딩

DSA 드라이버는 tc(Traffic Control)의 flower classifier를 하드웨어 ACL/TCAM에 오프로드할 수 있습니다. 이를 통해 라인 레이트로 패킷 필터링, 미러링, 폴리싱, 리디렉션 등을 수행할 수 있습니다.

tc 동작하드웨어 대응지원 드라이버 예
tc flower matchTCAM/ACL 룰 프로그래밍Ocelot, SJA1105, MT7530
action drop하드웨어 드롭 액션대부분
action mirred포트 미러링Ocelot, Marvell
action police하드웨어 폴리서Ocelot
action redirect포트 리디렉션Ocelot, SJA1105
action vlan push/popVLAN 태그 조작Ocelot
mqprioQoS 큐 매핑Ocelot, taprio
taprio시간 기반 스케줄링 (TSN)Ocelot, SJA1105, Felix
# tc flower 오프로드 예제

# 특정 MAC 소스에서 오는 프레임 드롭
tc qdisc add dev lan0 clsact
tc filter add dev lan0 ingress protocol all \
    flower src_mac aa:bb:cc:dd:ee:ff \
    action drop

# lan0 -> lan1 미러링 (하드웨어)
tc filter add dev lan0 ingress protocol all \
    flower \
    action mirred egress mirror dev lan1

# VLAN 100 트래픽에 대한 폴리싱 (1Gbps 제한)
tc filter add dev lan0 ingress protocol 802.1q \
    flower vlan_id 100 \
    action police rate 1gbit burst 256k conform-exceed drop/ok

# taprio (TSN) 설정 예제
tc qdisc replace dev lan0 parent root taprio \
    num_tc 4 \
    map 0 0 1 1 2 2 3 3 \
    queues 1@0 1@1 1@2 1@3 \
    base-time 1000000000 \
    sched-entry S 0x1 250000 \
    sched-entry S 0x2 250000 \
    sched-entry S 0x4 250000 \
    sched-entry S 0x8 250000 \
    flags 0x2

# 오프로드 확인: hw_tc 카운터 증가 확인
tc -s filter show dev lan0 ingress
/* DSA tc flower 오프로드 콜백 */
static int my_cls_flower_add(struct dsa_switch *ds, int port,
                             struct flow_cls_offload *cls, bool ingress)
{
    struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
    struct flow_dissector_key_eth_addrs *key_eth;
    struct flow_action_entry *act;

    /* 매치 키 파싱 */
    if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
        key_eth = skb_flow_dissector_target(rule->match.dissector,
                                             FLOW_DISSECTOR_KEY_ETH_ADDRS,
                                             rule->match.key);
        /* key_eth->src, key_eth->dst를 TCAM에 프로그래밍 */
    }

    /* 액션 처리 */
    flow_action_for_each(i, act, &rule->action) {
        switch (act->id) {
        case FLOW_ACTION_DROP:
            hw_acl_set_action_drop(ds, port);
            break;
        case FLOW_ACTION_MIRRED:
            hw_acl_set_action_mirror(ds, port, act->dev);
            break;
        }
    }
    return 0;
}
tc 오프로드 제한: 하드웨어 TCAM/ACL 리소스는 유한합니다. 대부분의 스위치 칩은 수십~수백 개의 룰만 지원합니다. 오프로드 실패 시 tc는 소프트웨어 폴백으로 동작하며, tc -s filter show에서 in_hw/not_in_hw 플래그로 확인할 수 있습니다.

MTU/헤드룸/트레일러 이슈

DSA 태그는 프레임 길이를 늘립니다. 따라서 CPU 포트와 master netdev에서 충분한 headroom/tailroom 및 MTU를 보장하지 않으면, GSO 분할 실패나 silent drop이 발생합니다.

태그 타입오버헤드 (바이트)필요 영역영향
Marvell DSA4headroommaster MTU += 4
Marvell EDSA8headroommaster MTU += 8
Ocelot IFH16headroommaster MTU += 16
KSZ (2B)2tailroommaster MTU += 2
tag_8021q4headroommaster MTU += 4
MTK4headroommaster MTU += 4
Broadcom4headroommaster MTU += 4
/* DSA MTU 관리 핵심 코드 (net/dsa/slave.c) */
static int dsa_slave_change_mtu(struct net_device *dev, int new_mtu)
{
    struct dsa_port *dp = dsa_slave_to_port(dev);
    struct dsa_port *cpu_dp = dp->cpu_dp;
    struct net_device *master = cpu_dp->master;
    int overhead = cpu_dp->tag_ops->overhead;

    /* slave MTU 변경 시 master MTU도 조정 필요 */
    if (new_mtu + overhead > master->max_mtu)
        return -ERANGE;

    /* master MTU >= slave MTU + tag overhead */
    if (new_mtu + overhead > master->mtu) {
        int err = dev_set_mtu(master, new_mtu + overhead);
        if (err)
            return err;
    }

    dev->mtu = new_mtu;
    return 0;
}
# MTU 정합 확인 및 설정

# master와 slave MTU 비교
ip link show dev eth0 | grep mtu    # master: 1504 이상이어야
ip link show dev lan0 | grep mtu    # slave:  1500
ip link show dev lan1 | grep mtu    # slave:  1500

# CPU 포트 측 MTU 상향 (드라이버/하드웨어 허용 범위 내)
ip link set dev eth0 mtu 2000

# slave의 점보 프레임 활성화
ip link set dev lan0 mtu 9000

# headroom/tailroom 확인 (ethtool 또는 디버그 로그)
ethtool -k lan0 | grep -i offload
실무 팁: tail-tag 계열은 tailroom 부족 문제, header-tag 계열은 headroom 부족 문제가 자주 납니다. 드라이버에서 needed_headroom/needed_tailroom를 정확히 설정해야 합니다. 커널 6.x에서는 DSA 코어가 dev->needed_headroom을 자동으로 조정하지만, 일부 구형 드라이버는 수동 설정이 필요할 수 있습니다.

Device Tree 모델링 포인트

DSA bring-up 실패의 대부분은 DTS 구성 불일치에서 시작됩니다. CPU 포트의 ethernet phandle, phy-mode, 포트 인덱스, 멀티칩 링크(dsa,member)가 일치해야 태깅 경로가 정상 동작합니다.

DTS에서 DSA 트리 연결 관계 SoC MAC (master) ethernet = <&gmac0> phy-mode = "rgmii-id" Switch 0 (root) dsa,member = <0 0> cpu@5, lan@0..4 Switch 1 (cascade) dsa,member = <0 1> dsa@5, lan@0..4 CPU DSA 자주 깨지는 포인트 1. phy-mode 불일치: RGMII vs RGMII-ID vs RGMII-TXID (지연 보상 위치) 2. CPU 포트 인덱스 오류: DTS reg 값과 실제 하드웨어 포트 번호 불일치 3. fixed-link 누락: CPU-스위치 간 직결 시 PHY가 없으므로 fixed-link 필수 4. ethernet phandle 누락: CPU 포트가 어느 MAC에 연결되는지 지정 필수 5. 멀티칩에서 inter-switch 링크 포트가 user 포트로 잘못 노출 6. MDIO 버스 설정 오류: 스위치가 PHY로 인식되지 않음 결과: 패킷 loop/drop, offload 미동작, tag decode 실패, 링크 미감지
/* 완전한 DSA DTS 예시 (싱글 칩 + PHY) */
&gmac0 {
    phy-mode = "rgmii-id";
    status = "okay";

    fixed-link {
        speed = <1000>;
        full-duplex;
    };
};

&mdio {
    switch0: ethernet-switch@0 {
        compatible = "vendor,switch0";
        reg = <0>;
        dsa,member = <0 0>;

        ports {
            #address-cells = <1>;
            #size-cells = <0>;

            port@0 {
                reg = <0>;
                label = "lan0";
                phy-handle = <&sw_phy0>;
            };

            port@1 {
                reg = <1>;
                label = "lan1";
                phy-handle = <&sw_phy1>;
            };

            port@2 {
                reg = <2>;
                label = "lan2";
                phy-handle = <&sw_phy2>;
            };

            port@3 {
                reg = <3>;
                label = "lan3";
                phy-handle = <&sw_phy3>;
            };

            port@5 {
                reg = <5>;
                label = "cpu";
                ethernet = <&gmac0>;
                phy-mode = "rgmii-id";
                fixed-link {
                    speed = <1000>;
                    full-duplex;
                };
            };
        };

        mdio {
            #address-cells = <1>;
            #size-cells = <0>;

            sw_phy0: ethernet-phy@0 { reg = <0>; };
            sw_phy1: ethernet-phy@1 { reg = <1>; };
            sw_phy2: ethernet-phy@2 { reg = <2>; };
            sw_phy3: ethernet-phy@3 { reg = <3>; };
        };
    };
};
/* 멀티칩 DSA DTS 예시 (2칩 cascade) */
switch0: ethernet-switch@10 {
    compatible = "marvell,mv88e6085";
    reg = <0x10>;
    dsa,member = <0 0>;   /* tree 0, switch 0 */

    ports {
        port@5 {
            reg = <5>;
            label = "cpu";
            ethernet = <&gmac0>;
            phy-mode = "rgmii-id";
            fixed-link { speed = <1000>; full-duplex; };
        };
        port@6 {
            reg = <6>;
            label = "dsa";    /* 스위치 간 연결 포트 */
            link = <&switch1_port6>;
            phy-mode = "rgmii-id";
            fixed-link { speed = <1000>; full-duplex; };
        };
    };
};

switch1: ethernet-switch@11 {
    compatible = "marvell,mv88e6085";
    reg = <0x11>;
    dsa,member = <0 1>;   /* tree 0, switch 1 */

    ports {
        switch1_port6: port@6 {
            reg = <6>;
            label = "dsa";
            link = <&switch0>;
            phy-mode = "rgmii-id";
            fixed-link { speed = <1000>; full-duplex; };
        };
        /* 사용자 포트들 ... */
    };
};

멀티칩 DSA Fabric에서 태그 경계 처리

스위치 칩이 여러 개 연결된 DSA fabric에서는 태그가 단일 칩 내부 의미를 넘어 hop 정보를 반영할 수 있습니다. CPU 포트 도착 시점의 포트 decode, VLAN 도메인, STP 상태가 일치하지 않으면 loop/drop이 발생하기 쉽습니다.

멀티칩 DSA Fabric 토폴로지 Host CPU (master eth0) SoC MAC (RGMII) Switch A (root, index=0) port0: lan0 port1: lan1 port5: CPU port6: DSA (cascade) ATU/VTU 독립 관리 Switch B (cascade, index=1) port0: lan2 port1: lan3 port6: DSA (cascade) ATU/VTU 독립 관리 CPU link DSA cascade link 멀티칩 패브릭 체크리스트 1. 각 칩 간 dsa,member 속성과 포트 인덱스 일관성 확인 2. cascade 포트의 VLAN/STP 상태가 root와 동일 정책인지 확인 3. tag decode 실패 로그와 bridge fdb/vlan 상태를 함께 비교 4. MTU/headroom을 모든 경로(master/slave/cascade)에 동일하게 반영
토폴로지최대 스위치 수태그 인코딩대표 예
Single chip1switch_id = 0 고정일반적인 5포트 스위치
Daisy chain칩 의존 (보통 2~8)switch_id + port_idMarvell cascade
Cross-chip bridge칩 의존fabric-wide FDBMarvell 멀티칩 브리지
# 멀티칩 토폴로지 확인
ip -d link show | grep -E 'master|dsa|link'

# 크로스-칩 브리지 상태
bridge -d link show
bridge -d fdb show  # offloaded 플래그 확인
bridge vlan show

# 디버그 로그 확인
dmesg | grep -Ei 'dsa|tag|decode|drop|cascade'

태그 드라이버 연산 경로 (xmit/rcv)

DSA 태그 처리의 핵심은 dsa_device_ops 구현입니다. TX에서 CPU 포트로 내보내기 전 태그를 삽입하고, RX에서 하드웨어가 붙인 태그를 해석해 source port/metadata를 복원합니다.

dsa_device_ops 전체 콜백 인터페이스 struct dsa_device_ops TX 콜백 xmit(skb, dev) - 목적 포트 정보를 태그로 인코딩 - skb_push/skb_put으로 공간 확보 - NULL 반환 시 프레임 드롭 RX 콜백 rcv(skb, dev) - 태그에서 소스 포트/스위치 ID 추출 - 태그 바이트 제거 (skb_pull/trim) - dsa_master_find_slave()로 dev 매핑 보조 콜백 및 속성 flow_dissect(skb, ...) -- 태그 존재 시 flow dissector 오프셋 보정 needed_headroom / needed_tailroom -- 태그 삽입에 필요한 skb 여유 공간 overhead -- 태그 총 크기 (MTU 계산에 사용) name / proto -- 태그 프로토콜 이름과 열거형 ID
/* 완전한 태그 드라이버 구현 예시 (간소화) */
#define MY_TAG_LEN 4

static struct sk_buff *my_tag_xmit(struct sk_buff *skb,
                                    struct net_device *dev)
{
    struct dsa_port *dp = dsa_slave_to_port(dev);
    u8 *tag;

    /* headroom 부족 시 재할당 */
    if (skb_cow_head(skb, MY_TAG_LEN) < 0)
        return NULL;

    /* MAC 헤더 뒤에 태그 삽입 */
    skb_push(skb, MY_TAG_LEN);
    dsa_alloc_etype_header(skb, MY_TAG_LEN);
    tag = dsa_etype_header_pos_tx(skb);

    /* FROM_CPU 모드, 목적 포트 지정 */
    tag[0] = 0x40 | (dp->ds->index & 0x1f);
    tag[1] = dp->index << 3;
    tag[2] = 0x00;
    tag[3] = 0x00;

    return skb;
}

static struct sk_buff *my_tag_rcv(struct sk_buff *skb,
                                   struct net_device *dev)
{
    u8 *tag = dsa_etype_header_pos_rx(skb);
    u8 switch_id = tag[0] & 0x1f;
    u8 src_port = (tag[1] >> 3) & 0x1f;
    u8 mode = (tag[0] >> 6) & 0x3;

    /* TO_CPU 모드 확인 */
    if (mode != 0x01)
        return NULL;

    /* 태그 제거 */
    skb_pull_rcsum(skb, MY_TAG_LEN);
    dsa_strip_etype_header(skb, MY_TAG_LEN);

    skb->dev = dsa_master_find_slave(dev, switch_id, src_port);
    if (!skb->dev)
        return NULL;

    return skb;
}

오프로딩 검증: bridge/FDB/VLAN 일관성

DSA 환경에서는 소프트웨어 경로와 하드웨어 오프로딩 경로가 동시에 존재합니다. 문제 재현 시에는 "기능 동작"만 보지 말고 오프로딩 여부를 함께 확인해야 합니다.

DSA 오프로딩 검증 순서도 1. bridge -d offload 플래그 확인 2. FDB/VLAN 학습/전파 offloaded 확인 3. ethtool -S 드롭/에러 카운터 비교 4. tcpdump CPU 포트 태그 관찰 5. dmesg tag decode 오류 확인 판정 기준 오프로딩 활성: CPU 포트 트래픽 감소 + FDB/VLAN offloaded 플래그 존재 + 라인레이트 포워딩 소프트웨어 폴백: bridge -d에서 offload 플래그 누락 + CPU 사용률 높음 + 트래픽이 CPU 포트 통과 부분 오프로드: FDB는 offloaded이나 VLAN은 미오프로드 등 기능별 차이 가능 태그 오류: dmesg에 "invalid src_port" 또는 "tag decode failure" 메시지 + 패킷 드롭 증가 MTU 문제: ethtool -S에서 rx_oversized 또는 tx_dropped 카운터 증가
# 오프로딩 종합 검증 스크립트
#!/bin/bash

echo "=== 1. Bridge 오프로드 상태 ==="
bridge -d link show

echo "=== 2. FDB 오프로드 확인 ==="
bridge -d fdb show | grep -i offload

echo "=== 3. VLAN 상태 ==="
bridge vlan show

echo "=== 4. 포트 통계 ==="
for dev in eth0 lan0 lan1 lan2; do
    echo "--- $dev ---"
    ethtool -S $dev 2>/dev/null | grep -iE 'drop|error|overflow'
done

echo "=== 5. DSA 관련 커널 메시지 ==="
dmesg | grep -Ei 'dsa|tag|decode|drop|switchdev' | tail -20

커널 설정

옵션설명권장의존성
CONFIG_NET_DSADSA 코어 프레임워크y/mCONFIG_NET
CONFIG_NET_DSA_TAG_DSAMarvell DSA 태그 (4B)칩셋 의존CONFIG_NET_DSA
CONFIG_NET_DSA_TAG_EDSAMarvell EDSA 태그 (8B)칩셋 의존CONFIG_NET_DSA
CONFIG_NET_DSA_TAG_OCELOTMicrochip Ocelot IFH 태그칩셋 의존CONFIG_NET_DSA
CONFIG_NET_DSA_TAG_OCELOT_8021QOcelot 802.1Q 기반 태그칩셋 의존CONFIG_NET_DSA
CONFIG_NET_DSA_TAG_KSZMicrochip KSZ 테일 태그칩셋 의존CONFIG_NET_DSA
CONFIG_NET_DSA_TAG_MTKMediaTek MT7530 태그칩셋 의존CONFIG_NET_DSA
CONFIG_NET_DSA_TAG_BRCMBroadcom 태그칩셋 의존CONFIG_NET_DSA
CONFIG_NET_DSA_TAG_RTL4_ARealtek 4B 태그칩셋 의존CONFIG_NET_DSA
CONFIG_NET_DSA_TAG_RTL8_4Realtek 8B 태그칩셋 의존CONFIG_NET_DSA
CONFIG_NET_DSA_TAG_SJA1105NXP SJA1105 태그칩셋 의존CONFIG_NET_DSA
CONFIG_NET_DSA_TAG_8021Q공통 802.1Q 기반 소프트웨어 태그y/mCONFIG_NET_DSA
CONFIG_NET_SWITCHDEVswitchdev 오프로드 프레임워크yCONFIG_NET
CONFIG_BRIDGELinux 브리지 (필수)y/mCONFIG_NET
CONFIG_VLAN_8021Q802.1Q VLAN 지원y/mCONFIG_NET
# 커널 config 확인
zcat /proc/config.gz | grep -i net_dsa
# 또는
grep -i net_dsa /boot/config-$(uname -r)

PTP/타임스탬프 연동

TSN(Time-Sensitive Networking) 환경에서는 정밀한 시간 동기화가 필수입니다. DSA 드라이버는 PTP(Precision Time Protocol) 타임스탬프를 태그 메타데이터에 실어 보내거나, 별도의 레지스터에서 타임스탬프를 읽어 skb에 첨부합니다.

기능DSA 콜백설명지원 예
TX 타임스탬프port_txtstamp()송신 시 하드웨어 타임스탬프 첨부Ocelot, SJA1105
RX 타임스탬프port_rxtstamp()수신 태그에서 타임스탬프 추출Ocelot, SJA1105, KSZ
PTP 클럭 등록ptp_clock_register()스위치 하드웨어 클럭 노출대부분
taprio 스케줄링port_setup_tc()시간 기반 게이트 스케줄링Ocelot, SJA1105
/* PTP RX 타임스탬프 추출 예시 */
static bool my_port_rxtstamp(struct dsa_switch *ds, int port,
                            struct sk_buff *skb, unsigned int type)
{
    struct skb_shared_hwtstamps *shhwtstamps;
    u64 ns;

    /* 태그에서 타임스탬프 값 추출 */
    ns = extract_hw_timestamp(skb);

    shhwtstamps = skb_hwtstamps(skb);
    memset(shhwtstamps, 0, sizeof(*shhwtstamps));
    shhwtstamps->hwtstamp = ns_to_ktime(ns);

    return false; /* false = 즉시 전달 */
}
# PTP 클럭 확인
ls /dev/ptp*
ethtool -T lan0

# PTP 동기화 테스트
ptp4l -i lan0 -m -2 -s

DSA 드라이버는 devlink 인프라를 통해 스위치 리소스, 파라미터, 진단 정보를 노출합니다. devlink는 DSA 환경에서 태그 프로토콜 변경, 리소스 모니터링, 포트 스플리팅 등을 지원합니다.

# devlink 디바이스 확인
devlink dev show

# 파라미터 확인/변경
devlink dev param show
devlink dev param set pci/0000:00:00.0 name tag_protocol \
    value DSA_TAG_PROTO_OCELOT_8021Q cmode runtime

# 리소스 확인 (TCAM, FDB 테이블 등)
devlink resource show
devlink resource set pci/0000:00:00.0 path /fdb size 2048

# 포트 정보
devlink port show

# 스위치 트래픽 트래핑 (CPU로 올라오는 트래픽 제어)
devlink trap show
devlink trap set pci/0000:00:00.0 trap source_mac_is_multicast action trap
devlink trap group show
devlink 기능DSA 연관사용 예
devlink dev param태그 프로토콜 런타임 변경NPI -> 8021q 전환
devlink resource하드웨어 리소스 모니터링FDB/TCAM 사용량 확인
devlink trap트래픽 트래핑 제어특정 패킷 CPU 전달 활성화
devlink port포트 정보/스플리팅포트 상태 확인
devlink health드라이버 건강 상태FW 오류 진단

디버깅 체크리스트

# 1) DSA 트리와 포트 확인
ip -d link show | grep -A2 -E "dsa|master"

# 2) 태그 프로토콜 확인
cat /sys/class/net/lan0/dsa/tagging

# 3) 브리지/VLAN 확인
bridge link show
bridge vlan show
bridge fdb show

# 4) 패킷 경로 관찰 (CPU 포트)
tcpdump -i eth0 -e -nn -vv -c 100

# 5) ethtool 통계와 드롭 카운터
ethtool -S eth0 | grep -iE 'drop|error|overflow'
ethtool -S lan0 | grep -iE 'drop|error|overflow'

# 6) 동적 디버그 활성화
echo 'file net/dsa/* +p' > /sys/kernel/debug/dynamic_debug/control
echo 'file drivers/net/dsa/* +p' > /sys/kernel/debug/dynamic_debug/control
dmesg -w

# 7) switchdev 이벤트 모니터링
bridge monitor all

# 8) MTU 정합 확인
ip link show | grep mtu

# 9) devlink 상태 확인
devlink dev show
devlink trap show
devlink resource show
치명적 실수: master netdev(eth0)를 브리지에 직접 추가하면 DSA RX 핸들러와 충돌합니다. 반드시 slave netdev(lan0, lan1 등)만 브리지에 추가하세요. ip link set eth0 master br0 -- 절대 금지!
주의: CPU 포트 MTU가 낮으면 태그 오버헤드로 예기치 않은 드롭이 발생할 수 있습니다.

일반적인 장애 패턴과 해결

증상원인해결
lan0 netdev 미생성DTS 포트 정의 오류, 드라이버 미로드dmesg 확인, DTS 포트 reg/label 검증
패킷이 CPU에서 보이지 않음CPU 포트 링크 다운, phy-mode 불일치ethtool eth0, DTS phy-mode 확인
태그 decode 실패 로그잘못된 태그 프로토콜 또는 칩 불일치cat /sys/class/net/lan0/dsa/tagging
오프로드 미동작switchdev 드라이버 미구현bridge -d link show로 offload 확인
멀티칩 패킷 루프cascade 포트 STP 미설정bridge STP 활성화, VLAN 정합 확인
MTU 초과 드롭master MTU < slave MTU + tag overheadip link set eth0 mtu 상향
PTP 타임스탬프 누락태그에 타임스탬프 필드 미포함ethtool -T 확인, PTP 하드웨어 지원 검증
tc 오프로드 실패TCAM 리소스 부족devlink resource show, 룰 정리
/* 장애 패턴 예시: 잘못된 source port decode */
if (unlikely(src_port >= ds->num_ports)) {
    netdev_warn(master, "invalid DSA src_port=%u (max=%u)\n",
                src_port, ds->num_ports - 1);
    return NULL; /* drop */
}

/* 해당 포트에 slave가 없는 경우 */
if (unlikely(!dsa_is_user_port(ds, src_port))) {
    netdev_warn(master, "DSA src_port=%u is not user port\n",
                src_port);
    return NULL; /* drop */
}

실전 디버깅 플레이북

DSA 환경의 디버깅은 네트워크 스택, 스위치 하드웨어, DTS 설정의 세 층을 모두 점검해야 합니다. 아래는 실전에서 자주 사용하는 체계적인 디버깅 절차입니다.

######################################
# DSA 실전 디버깅 플레이북
######################################

# === Phase 1: 기본 토폴로지 확인 ===

# DSA 포트 관계 확인
ip -d link show | sed -n '/dsa\|master/p'

# 태그 프로토콜과 DSA 트리 정보
for dev in /sys/class/net/lan*; do
    echo "$(basename $dev): $(cat $dev/dsa/tagging 2>/dev/null)"
done

# === Phase 2: 오프로드 상태 점검 ===

# 스위치 오프로드 상태 확인
bridge -d link show
bridge -d fdb show | head -30
bridge vlan show

# === Phase 3: CPU 포트 패킷 분석 ===

# CPU 포트 캡처 (태그 유무 확인)
tcpdump -i eth0 -e -nn -vv -c 50

# === Phase 4: 카운터 분석 ===

# ethtool 통계와 드롭 카운터
ethtool -S eth0 2>/dev/null | grep -iE 'drop|error|overflow|rx_|tx_'
ethtool -S lan0 2>/dev/null | grep -iE 'drop|error|overflow|rx_|tx_'

# === Phase 5: 동적 디버그 활성화 ===

echo 'file net/dsa/* +p' > /sys/kernel/debug/dynamic_debug/control
echo 'file drivers/net/dsa/* +p' > /sys/kernel/debug/dynamic_debug/control
dmesg -w  # 별도 터미널에서 실시간 관찰

# === Phase 6: 트래픽 테스트 ===

# 포트 간 연결 테스트
ping -I lan0 192.168.1.1 -c 5
arping -I lan0 192.168.1.1 -c 5

# iperf3로 처리량 측정 (오프로드 효과 확인)
iperf3 -c 192.168.1.1 -B 192.168.1.100 -t 10
빠른 진단 순서: 1. ip -d link show로 DSA 트리 확인 2. bridge -d link show로 offload 플래그 확인 3. tcpdump -i eth0 -e로 CPU 포트 태그 관찰 4. dmesg | grep dsa로 오류 메시지 확인 5. ethtool -S로 드롭 카운터 비교

태그 프로토콜 헤더 바이트 심층 분석

각 태그 프로토콜은 고유한 바이트 레이아웃을 가지며, 패킷 캡처 시 원시 바이트를 해석하는 능력이 실전 디버깅의 핵심입니다. 이 섹션에서는 주요 5개 태그 프로토콜의 비트 레벨 구조를 상세히 분석합니다.

Marvell DSA 4바이트 비트 레벨

Marvell DSA 4바이트 비트 레벨 분석 비트: 31 30 29 28 27 26-24 23-19 18 17-15 14 13-12 11-0 Mode TagCmd SrcDev[4:0] SrcPort[4:0] T PRI C VID[11:0] Mode 필드 상세 (비트 31:30) 00 = FROM_CPU: 호스트에서 특정 포트로 직접 전송 01 = TO_CPU: 스위치에서 호스트로 트랩/포워드 10 = TO_SNIFFER: 미러링/스니퍼 포트로 전달 11 = FORWARD: 스위치 포워딩 엔진에 의한 일반 전달 (사용자 포트 간) TagCmd 필드 상세 (비트 29:28) 00 = None: VLAN 태그 없음 (untagged) 01 = Single: 단일 VLAN 태그 존재, 11 = Double: 이중 VLAN 태그 (QinQ) TO_CPU 모드 Reason Code (바이트 1 하위) SrcPort 위치에 트랩 이유 코드가 인코딩됨: MGMT_TRAP, POLICY_TRAP, ARL_MISS 등 Marvell 칩별 코드 매핑은 datasheet의 CPU Code Table 참조
/* Marvell DSA 태그 바이트 해석 유틸리티 (디버깅용) */
static void dsa_dump_tag(const u8 *tag, int len)
{
    u8 mode    = (tag[0] >> 6) & 0x3;
    u8 tag_cmd = (tag[0] >> 4) & 0x3;
    u8 src_dev = ((tag[0] & 0x1) << 4) | ((tag[1] >> 3) & 0xf);
    u8 src_port= tag[1] & 0x1f;
    u8 pri     = (tag[2] >> 5) & 0x7;
    u8 cfi     = (tag[2] >> 4) & 0x1;
    u16 vid    = ((tag[2] & 0xf) << 8) | tag[3];

    pr_debug("DSA: mode=%u cmd=%u dev=%u port=%u pri=%u cfi=%u vid=%u\n",
             mode, tag_cmd, src_dev, src_port, pri, cfi, vid);
}

/* EDSA 8바이트 확장 필드 해석 */
static void edsa_dump_tag(const u8 *tag)
{
    u16 etype = (u16)(tag[0] << 8) | tag[1];
    u8 trunk_id   = (tag[6] >> 4) & 0xf;
    u8 ptp_action = tag[6] & 0x3;
    u8 fpri       = (tag[7] >> 5) & 0x7;
    u8 mirror_code= tag[7] & 0x1f;

    pr_debug("EDSA: etype=0x%04x trunk=%u ptp=%u fpri=%u mirror=%u\n",
             etype, trunk_id, ptp_action, fpri, mirror_code);
}

Realtek RTL8 태그 바이트 분석 (8바이트)

Realtek RTL8365/RTL8366RB 시리즈는 EtherType 0x8899를 사용하는 8바이트 헤더 태그를 삽입합니다. TX/RX에서 필드 해석이 다릅니다.

오프셋TX (FROM_CPU)RX (TO_CPU)비트
0~10x8899 (EtherType)16
2프로토콜 버전 (0x04)프로토콜 버전8
3REASON (cpu_tag 타입)REASON (트랩 이유)8
4목적 포트 마스크 상위소스 포트 번호8
5목적 포트 마스크 하위소스 포트 번호 확장8
6QoS 우선순위, Learning 금지QoS 우선순위8
7VLAN 관련 플래그VLAN 관련 플래그8
/* net/dsa/tag_rtl8_4.c - RTL8 태그 핵심 구조 */
#define RTL8_4_TAG_LEN       8
#define RTL8_4_ETHERTYPE     0x8899
#define RTL8_4_PROTOCOL      0x04

static struct sk_buff *rtl8_4_tag_xmit(struct sk_buff *skb,
                                        struct net_device *dev)
{
    struct dsa_port *dp = dsa_slave_to_port(dev);
    u8 *tag;

    if (skb_cow_head(skb, RTL8_4_TAG_LEN) < 0)
        return NULL;

    skb_push(skb, RTL8_4_TAG_LEN);
    dsa_alloc_etype_header(skb, RTL8_4_TAG_LEN);
    tag = dsa_etype_header_pos_tx(skb);

    tag[0] = (RTL8_4_ETHERTYPE >> 8) & 0xff;
    tag[1] = RTL8_4_ETHERTYPE & 0xff;
    tag[2] = RTL8_4_PROTOCOL;
    tag[3] = 0x00;
    tag[4] = 0x00;
    tag[5] = BIT(dp->index);  /* 목적 포트 비트맵 */
    tag[6] = 0x00;
    tag[7] = 0x00;

    return skb;
}

Broadcom BRCM 태그 바이트 분석 (4바이트)

Broadcom SF2/BCM53xx 시리즈는 두 가지 변형을 사용합니다: tag_brcm(MAC 뒤 삽입)과 tag_brcm_prepend(프레임 앞 삽입).

비트 위치필드설명
[31:24]OpCode0x20=Ingress, 0x10=Egress
[23:21]TC (Traffic Class)QoS 우선순위 (0~7)
[20]TE (Tag Enforce)VLAN 태그 강제 플래그
[19:16]TS (Timestamp)타임스탬프 인덱스
[15:8]소스/목적 포트TX: 목적 포트 비트맵, RX: 소스 포트
[7:0]Reason/이유 코드TO_CPU 트랩 이유 코드
/* net/dsa/tag_brcm.c - Broadcom 태그 구조체 */
#define BRCM_TAG_LEN             4
#define BRCM_TAG_TYPE_INGRESS    0x20
#define BRCM_TAG_TYPE_EGRESS     0x10

/* TX 방향: FROM_CPU -> Ingress OpCode */
static struct sk_buff *brcm_tag_xmit(struct sk_buff *skb,
                                      struct net_device *dev)
{
    struct dsa_port *dp = dsa_slave_to_port(dev);
    u8 *brcm_tag;

    skb_push(skb, BRCM_TAG_LEN);
    dsa_alloc_etype_header(skb, BRCM_TAG_LEN);
    brcm_tag = dsa_etype_header_pos_tx(skb);

    brcm_tag[0] = BRCM_TAG_TYPE_INGRESS;
    brcm_tag[1] = 0x00;
    brcm_tag[2] = 0x00;
    brcm_tag[3] = (1 << dp->index); /* 목적 포트 비트맵 */

    return skb;
}

NXP SJA1105 VLAN 기반 태그

SJA1105는 고유한 접근법을 사용합니다. 전용 태그 헤더 대신 VLAN 태그를 메타데이터 캐리어로 재활용하며, 관리 프레임에는 별도의 메타프레임 메커니즘을 사용합니다.

경로인코딩 방식VID 사용비고
TX (데이터)VLAN 태그 VID에 목적 포트 인코딩VID 맵핑 테이블 사용스위치 VLAN 테이블에 사전 프로비저닝 필요
RX (데이터)소스 포트별 고유 VID 할당포트 ID -> VID 역매핑VLAN 필터링과 공존 시 충돌 주의
TX (관리)메타프레임 (Follow-up 프레임)별도 control 채널PTP, SPI 레지스터 접근
RX (관리)메타프레임 + link-local 트랩스위치 내부 큐STP BPDU, LLDP 등
/* SJA1105 VID 매핑 개념 */
/* TX: 사용자 포트 0 -> VID 1xx, 포트 1 -> VID 2xx, ... */
/* RX: VID에서 소스 포트 역산 */

static u16 sja1105_tx_vid(struct dsa_port *dp)
{
    /* 스위치 VLAN 테이블에 미리 설정된 매핑 */
    return SJA1105_DEFAULT_VLAN(dp->ds->index, dp->index);
}

/* 메타프레임 구조 (64바이트 고정 길이) */
struct sja1105_meta_frame {
    u8  dst_mac[6]; /* 01:80:C2:00:00:01 (link-local) */
    u8  src_mac[6]; /* 스위치 포트 MAC */
    u16 ethertype;   /* 0x0008 (SJA1105 전용) */
    u64 tstamp;      /* PTP 타임스탬프 */
    u8  src_port;    /* 소스 포트 */
    u8  switch_id;   /* 스위치 인덱스 */
};
tcpdump에서 DSA 태그 확인: CPU 포트(master) netdev에서 캡처하면 DSA 태그가 포함된 원시 프레임을 볼 수 있습니다. tcpdump -i eth0 -e -xx -c 10으로 헥스 덤프를 확인하고, 위의 비트 레이아웃에 따라 수동 해석이 가능합니다. slave netdev(lan0 등)에서 캡처하면 태그가 이미 제거된 프레임이 보입니다.

TX 인젝션 상세 메커니즘

TX 인젝션은 호스트 CPU에서 특정 스위치 포트로 프레임을 직접 전송하는 과정입니다. dsa_slave_xmit()에서 태그 드라이버의 xmit()까지의 전체 경로와 각 단계에서의 skb 조작을 상세히 분석합니다.

TX 인젝션 skb 변환 과정 1. 원본 프레임 (사용자 공간에서 전달) Dst MAC Src MAC EtherType Payload 2. skb_cow_head(skb, TAG_LEN) -- headroom 확보 headroom Dst MAC Src MAC EtherType Payload 3. skb_push + dsa_alloc_etype_header -- 태그 공간 삽입 Dst MAC Src MAC DSA Tag EtherType Payload 4. 태그 필드 작성 (FROM_CPU 모드, 목적 포트 인코딩) tag[0] = (FROM_CPU << 6) | dev_index; tag[1] = port_index << 3; tag[2] = pri << 5 | cfi << 4 | vid_hi; tag[3] = vid_lo; 5. skb->dev = master; dev_queue_xmit(skb) 태그가 삽입된 skb를 master netdev(eth0)의 TX 큐에 전달 master NIC 드라이버가 실제 하드웨어 전송 수행 -> CPU 포트 -> 스위치 ASIC TX 인젝션 시 주의사항 headroom 부족 시 skb_cow_head()가 skb를 재할당 -> 성능 저하 (needed_headroom 정확히 설정 필수)
/* TX 인젝션 전체 흐름 추적 (커널 내부) */

/* 1단계: 소켓에서 slave netdev로 */
/* sys_sendto() -> sock_sendmsg() -> ... -> dev_hard_start_xmit() */

/* 2단계: dsa_slave_xmit() 진입 */
static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb,
                                   struct net_device *dev)
{
    struct dsa_slave_priv *p = netdev_priv(dev);

    /* 통계 업데이트 (slave의 TX 카운터) */
    dev_sw_netstats_tx_add(dev, 1, skb->len);

    /* skb->cb 초기화 (DSA 내부 메타데이터용) */
    memset(skb->cb, 0, sizeof(skb->cb));

    /* 3단계: 태그 드라이버 xmit() 호출 */
    skb = p->xmit(skb, dev);
    if (!skb) {
        /* xmit() 실패: headroom 부족 또는 skb 조작 실패 */
        return NETDEV_TX_OK;
    }

    /* 4단계: master netdev로 전환 */
    skb->dev = dsa_slave_to_master(dev);

    /* 5단계: master의 TX 큐에 삽입 */
    dev_queue_xmit(skb);

    return NETDEV_TX_OK;
}

RX 추출 상세 메커니즘

RX 추출은 스위치 ASIC에서 CPU 포트로 올라온 태그 프레임을 해석하여, 올바른 slave netdev로 전달하는 과정입니다. master netdev의 rx_handler로 등록된 dsa_switch_rcv()가 NAPI poll 이후에 호출됩니다.

/* RX 추출 상세 코드 흐름 */

/* 1. master NIC NAPI poll -> netif_receive_skb() */
/* 2. RX handler 체인 -> dsa_switch_rcv() 호출 */

static rx_handler_result_t dsa_switch_rcv(struct sk_buff **pskb)
{
    struct sk_buff *skb = *pskb;
    struct dsa_port *cpu_dp = skb->dev->dsa_ptr;

    /* CPU 포트 DSA 포인터 유효성 검사 */
    if (unlikely(!cpu_dp))
        return RX_HANDLER_PASS;  /* DSA가 아닌 프레임: 통과 */

    /* 3. 태그 드라이버 rcv() 호출 */
    skb = cpu_dp->tag_ops->rcv(skb, skb->dev);
    if (!skb) {
        /* 태그 해석 실패: 잘못된 포트 번호, 손상된 태그 등 */
        return RX_HANDLER_CONSUMED;
    }

    /* 4. skb->dev가 올바른 slave로 설정되었는지 확인 */
    /* rcv()가 dsa_master_find_slave()로 매핑 */

    /* 5. L2 헤더 재설정 */
    skb_push(skb, ETH_HLEN);
    skb->pkt_type = PACKET_HOST;
    skb->protocol = eth_type_trans(skb, skb->dev);

    /* 6. slave의 RX 통계 업데이트 */
    dev_sw_netstats_rx_add(skb->dev, skb->len);

    /* 7. 상위 프로토콜 스택으로 전달 */
    netif_receive_skb(skb);

    return RX_HANDLER_CONSUMED;
}

/* 태그 드라이버 rcv()에서 수행하는 핵심 작업 */
/* a. 태그 바이트 위치 확인 (header/tail) */
/* b. 소스 포트/스위치 ID 추출 */
/* c. 트랩 이유 코드 확인 (관리 프레임 등) */
/* d. 태그 바이트 제거 (skb_pull_rcsum / skb_trim) */
/* e. skb->dev = dsa_master_find_slave(dev, sw_id, port) */
GRO와 RX 추출 순서: master NIC에서 GRO가 활성화된 경우, 여러 프레임이 하나의 skb로 병합될 수 있습니다. DSA 태그가 각 프레임 내부에 있으므로 GRO 후 태그 해석이 불가능해질 수 있습니다. 이를 방지하기 위해 일부 DSA 태그 드라이버는 flow_dissect() 콜백으로 태그 오프셋을 GRO에 알려줍니다. master에서 ethtool -K eth0 gro off로 GRO를 비활성화하면 디버깅이 쉬워집니다.

MTU 헤드룸 정밀 계산

DSA 환경에서 MTU 관련 문제는 가장 흔한 장애 원인 중 하나입니다. 태그 오버헤드, VLAN 태그, QinQ 등이 복합되면 프레임 크기가 예상을 초과할 수 있습니다.

DSA 환경 프레임 크기 계산 기본 이더넷 (MTU 1500) 6B dst 6B src 2B Payload (46~1500 바이트) 4B FCS = 1518B DSA 헤더 태그 (Marvell 4B) 6B 6B 4B tag 2B Payload (1500B) 4B = 1522B DSA + 802.1Q VLAN 6B 6B 4B tag 4B VLAN 2B Payload (1500B) 4B = 1526B MTU 계산 공식 master_MTU >= slave_MTU + tag_overhead master_MTU >= slave_MTU + tag_overhead + VLAN_HLEN (VLAN 사용 시) master_max_mtu >= 최대 slave_MTU + tag_overhead (하드웨어 제한 확인) 실용 예시: Ocelot (16B tag) + VLAN: master_MTU >= 1500 + 16 + 4 = 1520 EDSA (8B tag) + QinQ: master_MTU >= 1500 + 8 + 8 = 1516
# MTU 정합 진단 스크립트
#!/bin/bash

echo "=== MTU 정합 진단 ==="

# master MTU
MASTER_MTU=$(ip -o link show eth0 | grep -oP 'mtu \K\d+')
echo "Master (eth0) MTU: $MASTER_MTU"

# 태그 오버헤드 확인
TAG_PROTO=$(cat /sys/class/net/lan0/dsa/tagging 2>/dev/null)
echo "Tag protocol: $TAG_PROTO"

# 각 slave MTU
for dev in /sys/class/net/lan*; do
    NAME=$(basename $dev)
    MTU=$(cat $dev/mtu)
    echo "Slave $NAME MTU: $MTU"
done

# 경고: 일반적인 태그 오버헤드
echo ""
echo "일반적인 태그 오버헤드:"
echo "  DSA (Marvell): 4B  -> master >= slave + 4"
echo "  EDSA:          8B  -> master >= slave + 8"
echo "  Ocelot IFH:    16B -> master >= slave + 16"
echo "  KSZ (tail):    2B  -> master >= slave + 2"
echo "  tag_8021q:     4B  -> master >= slave + 4"

# 자동 MTU 조정
# ip link set eth0 mtu $((SLAVE_MTU + TAG_OVERHEAD))
점보 프레임 함정: slave에서 MTU 9000을 설정하면 master도 자동으로 조정되어야 합니다. 그러나 master NIC 드라이버가 max_mtu를 제한하면 slave MTU 변경이 -ERANGE로 실패합니다. ip link set eth0 mtu 9016이 먼저 성공하는지 확인한 후 slave MTU를 조정하세요.

멀티칩 패브릭 심화 (Cascading)

DSA는 하나의 트리에 최대 4개(칩 의존)의 스위치 칩을 데이지 체인 방식으로 연결할 수 있습니다. 이 구성에서 태그의 switch_id 필드가 핵심이며, 크로스-칩 FDB/VLAN 동기화가 필수적입니다.

멀티칩 DSA Fabric 상세 토폴로지 Host CPU (eth0 master) RGMII/SGMII Switch 0 (root) dsa,member = <0 0> port0: lan0 (user) port1: lan1 (user) port2: lan2 (user) port5: CPU port port6: DSA cascade Switch 1 dsa,member = <0 1> port0: lan3 (user) port1: lan4 (user) port2: lan5 (user) port5: DSA cascade (to SW0) port6: DSA cascade (to SW2) Switch 2 dsa,member = <0 2> port0: lan6 (user) port1: lan7 (user) port2: lan8 (user) port5: DSA cascade (to SW1) CPU link 크로스-칩 패킷 흐름 예시 lan0(SW0:port0) -> lan6(SW2:port0) 전송: 1. lan0에서 TX: dsa_slave_xmit() -> tag_ops->xmit() 2. 태그: FROM_CPU, dst_dev=2, dst_port=0 인코딩 3. master(eth0) -> CPU port -> SW0 ASIC 4. SW0: 태그의 dst_dev=2이므로 cascade port(port6)로 전달 5. SW1: dst_dev=2이므로 다시 cascade port(port6)로 전달 6. SW2: dst_dev=2(자신)이므로 dst_port=0(lan6)으로 최종 전달 핵심: 태그의 dev/port 필드가 전체 fabric에서 유일해야 함
/* 크로스-칩 브리지 FDB 동기화 */

/* lan0(SW0)과 lan6(SW2)을 같은 bridge에 추가하면: */
/* 1. 두 스위치 모두에 FDB 엔트리가 설치되어야 함 */
/* 2. cascade 포트의 VLAN 멤버십이 일치해야 함 */
/* 3. STP 상태가 모든 경로에서 Forwarding이어야 함 */

static int dsa_port_fdb_add(struct dsa_port *dp,
                           const unsigned char *addr,
                           u16 vid)
{
    struct dsa_notifier_fdb_info info = {
        .dp   = dp,
        .addr = addr,
        .vid  = vid,
    };

    /* DSA 코어가 트리 내 모든 스위치에 통지 */
    return dsa_port_notify(dp, DSA_NOTIFIER_FDB_ADD, &info);
}

/* 각 스위치의 port_fdb_add()가 호출됨 */
/* - 로컬 스위치: 해당 포트에 FDB 추가 */
/* - 원격 스위치: cascade 포트에 FDB 추가 */
cascade 포트 디버깅: cascade 포트 간 패킷 루프가 발생하면, bridge link set lan0 learning off로 소프트웨어 학습을 비활성화하고 bridge link set lan0 flood off로 unknown unicast flooding을 비활성화한 후 tcpdump -i eth0 -e -xx로 CPU 포트에서 원인 프레임을 분석하세요.

Bridge Offload 심화

DSA 환경에서 bridge offload는 단순히 FDB/VLAN만이 아닙니다. STP 상태, flooding 제어, 멀티캐스트 라우터 포트, LAG 오프로드 등 브리지의 거의 모든 기능이 하드웨어로 오프로드될 수 있습니다.

기능switchdev 속성/객체DSA 콜백하드웨어 동작
STP 상태SWITCHDEV_ATTR_ID_PORT_STP_STATEport_stp_state_set()포트 STP 상태 변경 (Disabled/Blocking/Listening/Learning/Forwarding)
LearningSWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGSport_bridge_flags()ATU 자동 학습 활성화/비활성화
FloodingSWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGSport_bridge_flags()Unknown unicast/multicast/broadcast flooding 제어
VLAN 필터링SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERINGport_vlan_filtering()하드웨어 VLAN 필터 모드 활성화
Ageing timeSWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIMEport_bridge_join()ATU 에이징 타이머 설정
MDB (멀티캐스트)SWITCHDEV_OBJ_ID_PORT_MDBport_mdb_add()멀티캐스트 필터 업데이트
VLANSWITCHDEV_OBJ_ID_PORT_VLANport_vlan_add()VLAN 테이블 업데이트
MRP (Media Redundancy)SWITCHDEV_OBJ_ID_MRPport_mrp_add()산업용 링 프로토콜 오프로드
MAB (MAC Auth Bypass)port locked/mab 플래그port_bridge_flags()포트 잠금 + MAC 기반 인증
Host FDB호스트 MAC 엔트리port_fdb_add()CPU 포트 FDB 엔트리 관리
# Bridge Offload 전체 검증

# 브리지 생성 및 포트 추가
ip link add name br0 type bridge vlan_filtering 1 stp_state 1
ip link set br0 up
ip link set lan0 master br0
ip link set lan1 master br0
ip link set lan2 master br0

# 오프로드 상태 확인 (상세 모드)
bridge -d -j link show | python3 -m json.tool

# 포트별 플래그 확인
bridge link show dev lan0
# hairpin off guard off root_block off fastleave off
# learning on flood on mcast_flood on bcast_flood on

# flooding 제어 (unknown unicast 차단)
bridge link set dev lan2 flood off

# 멀티캐스트 라우터 포트 지정
bridge link set dev lan0 mcast_router 2

# STP 상태 확인
bridge link show dev lan0 | grep state
# state forwarding

# Host FDB 엔트리 (CPU MAC)
bridge fdb add 00:11:22:33:44:55 dev br0 self local

# 포트 격리 (isolated 모드)
bridge link set dev lan1 isolated on
# isolated 포트 간 직접 통신 차단 (Private VLAN 유사)

# MAB (MAC Authentication Bypass)
bridge link set dev lan2 locked on mab on
# 미인증 MAC은 CPU로 트랩 -> 인증 서버 연동
/* Bridge Offload 콜백 구현 예시 */

static int my_port_bridge_flags(struct dsa_switch *ds, int port,
                               struct switchdev_brport_flags flags,
                               struct netlink_ext_ack *extack)
{
    if (flags.mask & BR_LEARNING) {
        bool learning = !!(flags.val & BR_LEARNING);
        hw_set_atu_learning(ds, port, learning);
    }
    if (flags.mask & BR_FLOOD) {
        bool flood = !!(flags.val & BR_FLOOD);
        hw_set_unicast_flood(ds, port, flood);
    }
    if (flags.mask & BR_MCAST_FLOOD) {
        bool mcast_flood = !!(flags.val & BR_MCAST_FLOOD);
        hw_set_multicast_flood(ds, port, mcast_flood);
    }
    if (flags.mask & BR_BCAST_FLOOD) {
        bool bcast_flood = !!(flags.val & BR_BCAST_FLOOD);
        hw_set_broadcast_flood(ds, port, bcast_flood);
    }
    if (flags.mask & BR_PORT_LOCKED) {
        bool locked = !!(flags.val & BR_PORT_LOCKED);
        hw_set_port_locked(ds, port, locked);
    }
    return 0;
}

/* STP 상태 설정 */
static int my_port_stp_state_set(struct dsa_switch *ds, int port,
                                u8 state)
{
    u8 hw_state;

    switch (state) {
    case BR_STATE_DISABLED:
        hw_state = PORT_STATE_DISABLED;
        break;
    case BR_STATE_BLOCKING:
    case BR_STATE_LISTENING:
        hw_state = PORT_STATE_BLOCKING;
        break;
    case BR_STATE_LEARNING:
        hw_state = PORT_STATE_LEARNING;
        break;
    case BR_STATE_FORWARDING:
        hw_state = PORT_STATE_FORWARDING;
        break;
    }

    return hw_set_port_state(ds, port, hw_state);
}

tc 오프로딩 심화: TCAM/ACL 프로그래밍

tc flower 오프로드의 실제 구현은 TCAM(Ternary Content-Addressable Memory) 프로그래밍과 ACL(Access Control List) 룰 설치로 이루어집니다. 하드웨어 리소스 한계와 매치 키 제약을 이해해야 합니다.

매치 키flow_dissector 키하드웨어 TCAM 필드지원 범위
Src/Dst MACFLOW_DISSECTOR_KEY_ETH_ADDRSSA/DA 48비트대부분 지원
EtherTypeFLOW_DISSECTOR_KEY_BASICEtherType 16비트대부분 지원
VLAN ID/PRIFLOW_DISSECTOR_KEY_VLANVID 12비트 + PCP 3비트대부분 지원
Src/Dst IPFLOW_DISSECTOR_KEY_IPV4_ADDRSL3 필드일부 (Ocelot)
L4 포트FLOW_DISSECTOR_KEY_PORTSTCP/UDP 포트일부 (Ocelot)
IP 프로토콜FLOW_DISSECTOR_KEY_BASICIP Protocol 8비트일부
DSCP/TCFLOW_DISSECTOR_KEY_IPTOS/TC 필드일부 (Ocelot)
# tc 오프로드 고급 예제

# 1. L2 필터링: 특정 MAC에서 특정 VLAN으로만 허용
tc qdisc add dev lan0 clsact
tc filter add dev lan0 ingress protocol 802.1q \
    flower src_mac aa:bb:cc:00:00:01 vlan_id 100 \
    action pass
tc filter add dev lan0 ingress protocol 802.1q \
    flower src_mac aa:bb:cc:00:00:01 \
    action drop

# 2. 포트 미러링 (ingress + egress)
tc filter add dev lan0 ingress protocol all \
    flower \
    action mirred egress mirror dev lan3
tc filter add dev lan0 egress protocol all \
    flower \
    action mirred egress mirror dev lan3

# 3. 대역폭 폴리싱 (per-port ingress)
tc filter add dev lan1 ingress protocol all \
    flower \
    action police rate 100mbit burst 64k conform-exceed drop/ok

# 4. VLAN 태그 조작 (push/pop)
tc filter add dev lan0 ingress protocol all \
    flower \
    action vlan push id 200 priority 3 protocol 802.1q

# 5. 패킷 리디렉션 (포트 간)
tc filter add dev lan0 ingress protocol arp \
    flower \
    action mirred egress redirect dev lan2

# 6. 오프로드 상태 상세 확인
tc -s -d filter show dev lan0 ingress
# in_hw/not_in_hw 플래그, hw_tc 카운터, used_hw_stats 확인

# 7. TCAM 리소스 사용량
devlink resource show pci/0000:00:00.0
# TCAM: 128/256 used, FDB: 1024/4096 used
TCAM 리소스 관리: 대부분의 스위치 칩은 수십~수백 개의 TCAM 엔트리만 지원합니다. 과도한 tc filter 추가 시 ENOSPC 에러가 발생하며, 이후 추가되는 필터는 소프트웨어 폴백으로 동작합니다. devlink resource show로 사용량을 모니터링하고, 불필요한 룰을 정리하세요.

FDB/MDB 고급 관리

실전 환경에서 FDB/MDB 관련 문제는 MAC 이동(flapping), 에이징 불일치, 크로스-칩 동기화 실패 등 다양합니다. 이 섹션에서는 고급 FDB/MDB 관리 기법과 트러블슈팅 방법을 다룹니다.

/* FDB 동기화 메커니즘 상세 */

/* 하드웨어 -> 소프트웨어 학습 통지 */
static void my_atu_event_handler(struct dsa_switch *ds,
                                  struct atu_event *event)
{
    switch (event->type) {
    case ATU_LEARNED:
        /* 하드웨어가 새 MAC 학습 */
        dsa_port_learned_fdb(ds, event->port,
                             event->mac, event->vid);
        break;
    case ATU_AGED:
        /* 하드웨어 에이징으로 MAC 삭제 */
        dsa_port_forgotten_fdb(ds, event->port,
                               event->mac, event->vid);
        break;
    case ATU_VIOLATION:
        /* MAC 이동 감지 (다른 포트에서 학습된 MAC) */
        dsa_port_fdb_violation(ds, event->port,
                               event->mac, event->vid);
        break;
    }
}
# FDB/MDB 고급 관리 명령어

# 정적 FDB 엔트리 추가 (특정 포트에 고정)
bridge fdb add aa:bb:cc:dd:ee:01 dev lan0 master static

# 정적 MDB 엔트리 추가 (멀티캐스트 그룹)
bridge mdb add dev br0 port lan0 grp 239.1.1.1 permanent vid 100
bridge mdb add dev br0 port lan1 grp ff02::1 permanent  # IPv6

# FDB 덤프 (offloaded 플래그 확인)
bridge -d fdb show | column -t

# FDB 에이징 타임 조정 (초 단위, 0=비활성화)
ip link set br0 type bridge ageing_time 30000  # 300초

# MAC 학습 비활성화 (보안 강화)
bridge link set dev lan2 learning off

# Flooding 제어 (포트별)
bridge link set dev lan2 flood off         # unknown unicast
bridge link set dev lan2 mcast_flood off   # unknown multicast

# FDB 변경 모니터링 (실시간)
bridge monitor fdb

# MDB IGMP 스누핑 상태
bridge -d mdb show
ip maddr show dev lan0

# FDB 엔트리 수 확인
bridge fdb show | wc -l
bridge fdb show | grep offloaded | wc -l

# MAC flapping 감지
bridge monitor fdb 2>&1 | grep -E 'add|del' | head -50
FDB 동기화 문제 진단: 하드웨어 FDB와 소프트웨어 FDB가 불일치하면, 특정 MAC 주소로의 트래픽이 올바른 포트로 전달되지 않습니다. bridge fdb show에서 offloaded 플래그가 있는 엔트리가 하드웨어에 실제로 존재하는지 스위치 칩의 ATU 덤프 레지스터로 확인하세요.

VLAN-aware vs VLAN-unaware 브리지 모드

DSA 환경에서 브리지의 VLAN 필터링 모드는 데이터면 동작에 근본적인 차이를 만듭니다. VLAN-aware 모드에서는 스위치 하드웨어의 VTU(VLAN Table Unit)가 프레임 필터링을 수행하고, VLAN-unaware 모드에서는 VLAN 태그를 무시하고 포트 기반으로만 포워딩합니다.

VLAN-aware vs VLAN-unaware 브리지 동작 비교 VLAN-aware 모드 (vlan_filtering 1) 1. VTU(VLAN 테이블) 기반 필터링 활성화 2. 포트별 PVID(기본 VLAN) 할당 3. Untagged 프레임 -> PVID 자동 부여 4. VID 불일치 프레임 -> 하드웨어 드롭 5. trunk 포트에서 tagged 프레임 전달 6. CPU 포트 VLAN 멤버십 필수 설정 장점: L2 보안, 네트워크 분리, QoS 매핑 VLAN-unaware 모드 (vlan_filtering 0) 1. VTU 필터링 비활성화 2. VLAN 태그를 투명하게 통과 3. 포트 기반 포워딩만 수행 4. 모든 VLAN 트래픽 혼재 허용 5. tag_8021q 사용 시 VLAN 충돌 가능 6. 단순 스위칭 용도에 적합 장점: 설정 간소, 호환성 우수 모드 전환 시 주의사항 1. vlan_filtering 전환 시 기존 FDB 엔트리가 flush될 수 있음 (하드웨어 의존) 2. tag_8021q를 사용하는 드라이버는 VLAN-aware 모드에서 내부 VLAN과 사용자 VLAN 충돌 주의 3. 멀티칩 fabric에서 모든 스위치의 VLAN 모드가 일치해야 함 (불일치 시 프레임 드롭/루프) 4. VLAN-aware 모드에서 CPU 포트를 VLAN 멤버로 추가하지 않으면 관리 트래픽이 차단됨 5. SJA1105 등 tag_8021q 기반 드라이버는 모드 전환 시 내부 VLAN 테이블 전체 재프로비저닝 필요 6. Ocelot의 NPI vs 8021q 모드 전환은 devlink param으로만 가능 (bridge 설정과 별개) 7. 실시간 전환 시 수 밀리초의 트래픽 중단 발생 가능 (프로덕션 환경에서는 사전 계획 필수)
# VLAN-aware 브리지 설정 (완전한 예제)

# 브리지 생성 (VLAN 필터링 활성화)
ip link add name br0 type bridge vlan_filtering 1
ip link set br0 up

# 포트 추가
ip link set lan0 master br0
ip link set lan1 master br0
ip link set lan2 master br0

# VLAN 할당 (access 포트 + trunk 포트)
# lan0: access 포트 (VLAN 100, untagged)
bridge vlan add dev lan0 vid 100 pvid untagged
bridge vlan del dev lan0 vid 1  # 기본 VLAN 제거

# lan1: access 포트 (VLAN 200, untagged)
bridge vlan add dev lan1 vid 200 pvid untagged
bridge vlan del dev lan1 vid 1

# lan2: trunk 포트 (VLAN 100 + 200, tagged)
bridge vlan add dev lan2 vid 100
bridge vlan add dev lan2 vid 200
bridge vlan del dev lan2 vid 1

# CPU 포트(br0 self)에도 VLAN 멤버십 필요
bridge vlan add dev br0 vid 100 self
bridge vlan add dev br0 vid 200 self

# 확인
bridge vlan show

# 출력 예시:
# port     vlan-id
# lan0     100 PVID Egress Untagged
# lan1     200 PVID Egress Untagged
# lan2     100
#          200
# br0      100
#          200
# VLAN-unaware 브리지 설정 (기본 모드)

# 브리지 생성 (VLAN 필터링 비활성화 - 기본값)
ip link add name br0 type bridge
ip link set br0 up

ip link set lan0 master br0
ip link set lan1 master br0
ip link set lan2 master br0

# 이 모드에서는 VLAN 태그가 투명하게 통과
# 외부 VLAN 스위치가 태깅을 관리하는 경우에 적합

# 주의: tag_8021q 드라이버는 내부적으로 VLAN을 사용하므로
# VLAN-unaware 모드에서도 특정 VID가 예약됨
# 예약 VID 범위 확인:
bridge vlan show
/* VLAN 필터링 모드 전환 콜백 */
static int my_port_vlan_filtering(struct dsa_switch *ds, int port,
                                  bool vlan_filtering,
                                  struct netlink_ext_ack *extack)
{
    if (vlan_filtering) {
        /* VLAN-aware 모드 활성화 */
        /* 1. VTU 필터링 활성화 */
        hw_enable_vlan_filtering(ds, port);
        /* 2. 기본 PVID 설정 */
        hw_set_default_pvid(ds, port, 1);
        /* 3. CPU 포트 VLAN 멤버십 확인 */
        hw_ensure_cpu_vlan_member(ds);
    } else {
        /* VLAN-unaware 모드: 모든 프레임 통과 */
        hw_disable_vlan_filtering(ds, port);
    }
    return 0;
}

/* tag_8021q와 VLAN-aware 모드 공존 처리 */
/* SJA1105, Ocelot 등에서 복잡한 VLAN 테이블 관리가 필요 */
static int my_tag_8021q_vlan_add(struct dsa_switch *ds, int port,
                                  u16 vid, u16 flags)
{
    /* 내부 DSA VID와 사용자 VID 분리 */
    if (dsa_tag_8021q_is_vid(vid)) {
        /* DSA 내부 VID: CPU 포트와 해당 사용자 포트만 멤버 */
        return hw_vtu_add_internal(ds, vid, port);
    }
    /* 사용자 VID: 일반 VLAN 처리 */
    return hw_vtu_add_user(ds, vid, port, flags);
}
tag_8021q와 VLAN-aware 모드 충돌: tag_8021q 기반 드라이버(SJA1105, Ocelot 8021q 모드)는 내부적으로 VLAN ID를 DSA 메타데이터로 사용합니다. VLAN-aware 브리지 모드를 활성화하면 사용자 VLAN과 내부 DSA VLAN이 동일 VTU 테이블을 공유하므로, VID 범위 충돌이 발생할 수 있습니다. 커널 6.x에서는 이를 해결하기 위해 dsa_tag_8021q_bridge_join()에서 VLAN 테이블을 동적으로 재구성합니다.

MRP (Media Redundancy Protocol) 오프로딩

MRP(IEC 62439-2)는 산업용 이더넷 환경에서 링 토폴로지의 고속 장애 복구를 제공하는 프로토콜입니다. DSA 드라이버는 MRP 프레임 처리를 스위치 하드웨어에 오프로드하여 수 밀리초 이내의 절환 시간을 달성할 수 있습니다.

MRP 역할설명DSA 콜백하드웨어 동작
MRM (Manager)링 관리자: 테스트 프레임 생성, 장애 감지port_mrp_add()하드웨어 테스트 프레임 생성기 활성화
MRC (Client)링 클라이언트: MRP 프레임 포워딩port_mrp_add()MRP EtherType 프레임 하드웨어 포워딩
MRA (Auto-manager)자동 관리자: 매니저 부재 시 자동 승격port_mrp_add()매니저 감지 + 자동 역할 전환
# MRP 설정 예시 (iproute2 + bridge)

# 브리지 생성 (MRP 필수 설정)
ip link add name br0 type bridge stp_state 0
ip link set br0 up
ip link set lan0 master br0
ip link set lan1 master br0

# MRP 인스턴스 추가 (링 포트 2개 지정)
bridge mrp add dev br0 p_port lan0 s_port lan1 ring_id 1

# MRP 역할 설정 (MRM = Manager)
bridge mrp set ring dev br0 ring_id 1 ring_role mrm

# MRP 상태 확인
bridge mrp show dev br0
# ring_id 1: p_port lan0, s_port lan1, ring_role mrm
#            ring_state open, ring_prio 0x8000

# MRP 테스트 프레임 간격 설정 (밀리초)
bridge mrp set ring dev br0 ring_id 1 ring_test_interval 3500

# 오프로드 확인 (in_hw 플래그)
bridge -d mrp show dev br0
/* DSA MRP 오프로드 콜백 구현 */
static int my_port_mrp_add(struct dsa_switch *ds, int port,
                           const struct switchdev_obj_mrp *mrp)
{
    /* 링 포트에 MRP 프레임 포워딩 활성화 */
    hw_mrp_enable_ring(ds, mrp->p_port, mrp->s_port, mrp->ring_id);

    /* MRP EtherType (0x88E3) 프레임을 링 포트 간 하드웨어 포워딩 */
    hw_mrp_set_forwarding(ds, mrp->ring_id, true);

    return 0;
}

static int my_port_mrp_add_ring_role(struct dsa_switch *ds, int port,
                                    const struct switchdev_obj_ring_role_mrp *role)
{
    if (role->ring_role == BR_MRP_RING_ROLE_MRM) {
        /* 매니저 역할: 하드웨어 테스트 프레임 생성기 활성화 */
        hw_mrp_start_test_gen(ds, role->ring_id);
    } else {
        /* 클라이언트 역할: 테스트 프레임 포워딩만 */
        hw_mrp_stop_test_gen(ds, role->ring_id);
    }
    return 0;
}
MRP 지원 드라이버: 현재 커널에서 MRP 하드웨어 오프로드를 지원하는 DSA 드라이버는 ocelot(Microchip/Felix), hellcreek(Hirschmann) 등 산업용 스위치 칩이 대표적입니다. 대부분의 가정용/기업용 스위치 칩(Marvell, Realtek, MediaTek)은 MRP 오프로드를 지원하지 않으며, 이 경우 소프트웨어 폴백으로 동작합니다 (절환 시간이 수십~수백 밀리초로 증가).

포트 격리 및 Private VLAN

DSA 환경에서 포트 격리(Port Isolation)는 같은 브리지에 속한 포트 간 직접 통신을 차단하면서 CPU(업링크) 포트와만 통신을 허용하는 기능입니다. 호텔 네트워크, 공유 Wi-Fi, 사설 클라우드 테넌트 분리 등에 사용됩니다.

포트 격리 동작 다이어그램 업링크 / 라우터 CPU 포트 (eth0/br0) lan0 (isolated) 테넌트 A lan1 (isolated) 테넌트 B lan2 (isolated) 테넌트 C lan3 (promiscuous) 공유 서버 X X 포트 격리 구현 방식 1. bridge link set dev lanX isolated on : 격리 포트 간 직접 포워딩 차단 2. 격리 포트 -> 비격리 포트(promiscuous): 허용 3. 하드웨어 오프로드: 스위치 포트 격리 매트릭스(Port VLAN Map) 프로그래밍 4. 소프트웨어 폴백: CPU에서 격리 검사 (성능 저하)
# 포트 격리 설정 (호텔 네트워크 시나리오)

# 브리지 생성
ip link add name br0 type bridge
ip link set br0 up

# 모든 포트를 브리지에 추가
ip link set lan0 master br0
ip link set lan1 master br0
ip link set lan2 master br0
ip link set lan3 master br0

# 객실 포트를 격리 모드로 설정
bridge link set dev lan0 isolated on
bridge link set dev lan1 isolated on
bridge link set dev lan2 isolated on

# lan3는 공유 서버 (격리하지 않음 = promiscuous)
# 격리 포트 -> lan3: 허용
# 격리 포트 -> 격리 포트: 차단

# 확인
bridge -d link show
# lan0: isolated on
# lan1: isolated on
# lan2: isolated on
# lan3: isolated off (기본값)

# 테스트: lan0에서 lan1으로 ping -> 실패 (격리)
# 테스트: lan0에서 lan3으로 ping -> 성공 (promiscuous)
/* 포트 격리 하드웨어 오프로드 구현 */
static int my_port_bridge_flags(struct dsa_switch *ds, int port,
                               struct switchdev_brport_flags flags,
                               struct netlink_ext_ack *extack)
{
    if (flags.mask & BR_ISOLATED) {
        bool isolated = !!(flags.val & BR_ISOLATED);

        if (isolated) {
            /* 포트 VLAN 맵에서 다른 격리 포트 제거 */
            hw_update_pvlan_map(ds, port, true);
            /* CPU 포트와 비격리 포트만 허용 */
        } else {
            /* 포트 VLAN 맵 복원: 모든 브리지 포트 허용 */
            hw_update_pvlan_map(ds, port, false);
        }
    }
    return 0;
}

/* Marvell mv88e6xxx 포트 격리 구현 */
/* Port VLAN Map (PVT) 레지스터를 사용하여 */
/* 각 포트가 통신할 수 있는 포트 비트맵을 설정 */
static void mv88e6xxx_update_pvlan(struct dsa_switch *ds,
                                    int port, bool isolated)
{
    u16 output_ports = 0;
    int i;

    for (i = 0; i < ds->num_ports; i++) {
        if (i == port)
            continue;
        if (isolated && dsa_port_is_isolated(ds, i))
            continue;  /* 다른 격리 포트 제외 */
        output_ports |= BIT(i);
    }

    /* Port VLAN Map 레지스터 업데이트 */
    mv88e6xxx_port_set_vlan_map(ds, port, output_ports);
}
MAB (MAC Authentication Bypass)와 포트 격리 조합: bridge link set dev lan0 locked on mab on isolated on으로 설정하면, 미인증 MAC 주소의 트래픽을 CPU로 트랩하여 RADIUS 서버 등에서 인증 후 동적으로 FDB 엔트리를 추가하는 NAC(Network Access Control) 구성이 가능합니다. 인증된 MAC만 포워딩되고, 격리 모드로 다른 포트와의 직접 통신도 차단됩니다.

실전 스위치 드라이버 비교 (mv88e6xxx, sja1105, mt7530)

DSA 프레임워크를 구현하는 주요 스위치 드라이버 세 가지를 비교합니다. 각 드라이버는 태그 프로토콜, 오프로드 범위, 하드웨어 특성이 크게 다르며, 실전에서 선택 시 이러한 차이를 이해해야 합니다.

특성mv88e6xxx (Marvell)sja1105 (NXP)mt7530 (MediaTek)
대표 칩셋88E6085, 88E6190, 88E6352SJA1105, SJA1110MT7530, MT7531
태그 프로토콜DSA (4B) / EDSA (8B)tag_8021q + 메타프레임tag_mtk (4B)
태그 위치헤더 (Src MAC 뒤)VLAN 재사용헤더 (EtherType)
멀티칩지원 (daisy-chain)SJA1110에서 cascade 지원미지원
VLAN 테이블VTU (4096 엔트리)정적 구성 (SPI)VTU (4096 엔트리)
FDB 크기1024~8192 (모델 의존)10242048
TCAM/ACL일부 모델 지원미지원제한적
PTP일부 모델 지원지원 (하드웨어 타임스탬프)미지원
TSN (taprio)미지원지원 (8개 TC)미지원
MRP미지원미지원미지원
tc flower제한적미지원제한적
devlink리소스/트랩제한적제한적
구성 인터페이스MDIO 레지스터SPI + 정적 구성 블롭MDIO 레지스터
포트 수5~11 (모델 의존)55~7
CPU 인터페이스RGMII/SGMIIRGMII/SGMII/MIIRGMII/TRGMII
주요 용도임베디드 라우터, NAS, 산업용자동차, TSN, 산업용가정용 라우터 (OpenWrt)

mv88e6xxx 드라이버 특성

/* drivers/net/dsa/mv88e6xxx/chip.c */
/* Marvell 88E6xxx 시리즈의 핵심 특성 */

/* 1. ATU (Address Translation Unit) 이벤트 인터럽트 */
/* - 하드웨어 FDB 학습/에이징/위반 이벤트를 인터럽트로 통지 */
/* - switchdev FDB notify로 소프트웨어 브리지와 동기화 */

static int mv88e6xxx_g1_atu_prob_irq_thread_fn(int irq, void *dev_id)
{
    struct mv88e6xxx_chip *chip = dev_id;
    struct mv88e6xxx_atu_entry entry;
    int err;

    mv88e6xxx_reg_lock(chip);
    err = mv88e6xxx_g1_atu_op(chip, 0, MV88E6XXX_G1_ATU_OP_GET_CLR_VIOLATION);
    mv88e6xxx_reg_unlock(chip);

    if (!err) {
        /* ATU 위반 이벤트 처리 (MAC flapping 등) */
        mv88e6xxx_handle_atu_violation(chip, &entry);
    }
    return IRQ_HANDLED;
}

/* 2. EDSA 태그의 멀티칩 라우팅 */
/* - SrcDev/DstDev 필드로 최대 32개 디바이스 주소 지정 */
/* - Cross-chip 브리지에서 cascade 포트로 자동 포워딩 */

/* 3. 포트 VLAN Map (PVT) */
/* - 각 포트별 허용 출력 포트 비트맵 */
/* - 포트 격리, PVLAN 구현의 기반 */

sja1105 드라이버 특성

/* drivers/net/dsa/sja1105/sja1105_main.c */
/* NXP SJA1105 시리즈의 핵심 특성 */

/* 1. SPI 기반 정적 구성 (Static Config) */
/* - 부팅 시 전체 구성을 SPI로 일괄 전송 */
/* - 런타임 변경이 제한적 (일부 테이블만 동적 수정 가능) */

static int sja1105_static_config_upload(struct sja1105_private *priv)
{
    struct sja1105_static_config *config = &priv->static_config;
    int rc;

    /* 정적 구성 블롭을 SPI로 전송 */
    rc = sja1105_spi_send_packed_buf(priv, SPI_WRITE,
                                     SJA1105_CONFIG_START_ADDR,
                                     config->buf, config->len);
    if (rc < 0)
        return rc;

    /* 구성 적용 (스위치 리셋) */
    return sja1105_clocking_setup(priv);
}

/* 2. TSN (Time-Sensitive Networking) 지원 */
/* - taprio: 시간 기반 게이트 스케줄링 (IEEE 802.1Qbv) */
/* - CBS: 크레딧 기반 셰이퍼 (IEEE 802.1Qav) */
/* - PTP: 하드웨어 타임스탬프 (메타프레임으로 전달) */

/* 3. tag_8021q 기반 태깅 */
/* - 전용 하드웨어 태그 없음 -> VLAN 재사용 */
/* - VLAN-aware 브리지와 공존 시 VID 충돌 처리 필요 */

mt7530 드라이버 특성

/* drivers/net/dsa/mt7530.c */
/* MediaTek MT7530/MT7531 시리즈의 핵심 특성 */

/* 1. SoC 내장 스위치 */
/* - MT7621/MT7622/MT7623 SoC에 내장된 5포트 기가비트 스위치 */
/* - TRGMII (Turbo RGMII) 인터페이스로 CPU 연결 */

static enum dsa_tag_protocol
mtk_get_tag_protocol(struct dsa_switch *ds, int port,
                     enum dsa_tag_protocol mp)
{
    return DSA_TAG_PROTO_MTK;
}

/* 2. MT7531: MT7530의 개선 버전 */
/* - SGMII 인터페이스 추가 (2.5G 포트) */
/* - 개선된 VLAN 처리 */
/* - PHY 내장 (MT7530은 외장 PHY 필요) */

/* 3. OpenWrt에서 광범위하게 사용 */
/* - 가정용 라우터의 사실상 표준 DSA 드라이버 */
/* - swconfig 레거시에서 DSA로 전환 완료 */

/* MT7530 태그 구조 */
/* TX: [15:8]=DPID(목적포트마스크), [7:0]=제어플래그 */
/* RX: [15:8]=소스포트|ARL위반, [7:0]=VID|이유코드 */
static struct sk_buff *mtk_tag_xmit(struct sk_buff *skb,
                                     struct net_device *dev)
{
    struct dsa_port *dp = dsa_slave_to_port(dev);
    u8 *tag;

    if (skb_cow_head(skb, MTK_HDR_LEN) < 0)
        return NULL;

    skb_push(skb, MTK_HDR_LEN);
    dsa_alloc_etype_header(skb, MTK_HDR_LEN);
    tag = dsa_etype_header_pos_tx(skb);

    /* 특수 EtherType + 목적 포트 비트맵 */
    tag[0] = 0x40 | ((1 << dp->index) >> 8);
    tag[1] = (1 << dp->index) & 0xff;
    tag[2] = 0x00;
    tag[3] = 0x00;

    return skb;
}
드라이버 선택 가이드:
  • mv88e6xxx: 멀티칩 cascade가 필요하거나 FDB 이벤트 인터럽트가 중요한 산업용/NAS 환경
  • sja1105: TSN(taprio, CBS, PTP) 기능이 핵심인 자동차/산업 자동화 환경
  • mt7530: 비용 효율적인 가정용 라우터/AP, OpenWrt 기반 네트워크 장비

DSA LAG (Link Aggregation) 오프로드

DSA 프레임워크는 Linux bonding/team 인터페이스와 연동하여 하드웨어 LAG(Link Aggregation Group)를 스위치 칩에 오프로드할 수 있습니다. 이를 통해 CPU를 거치지 않고 라인 레이트로 LAG 멤버 간 트래픽 분배가 가능합니다.

DSA LAG 오프로드 구조 Host CPU (br0) bond0 (lan0 + lan1) + lan2 + lan3 bond0 (LAG: 802.3ad LACP) 하드웨어 해싱 + 트렁크 그룹 lan0 LAG member lan1 LAG member lan2 standalone lan3 standalone 스위치 ASIC 하드웨어 트렁크 그룹: port0+port1 -> LAG ID 0 (L2/L3/L4 해싱) LAG 오프로드 제약 하드웨어 트렁크 그룹 수 제한 (보통 4~8), 크로스-칩 LAG 제한적, LACP 타이머는 소프트웨어
# DSA LAG 설정 (bonding)

# bond 인터페이스 생성 (802.3ad LACP)
ip link add name bond0 type bond mode 802.3ad

# DSA 포트를 bond에 추가
ip link set lan0 master bond0
ip link set lan1 master bond0
ip link set bond0 up

# bond를 브리지에 추가 (선택적)
ip link add name br0 type bridge
ip link set br0 up
ip link set bond0 master br0
ip link set lan2 master br0

# LAG 상태 확인
cat /proc/net/bonding/bond0
ip -d link show bond0

# 오프로드 확인 (bridge -d에서 offload 플래그)
bridge -d link show

# 해싱 정책 설정
ip link set bond0 type bond xmit_hash_policy layer3+4
# layer2, layer2+3, layer3+4, encap2+3, encap3+4
/* DSA LAG 오프로드 콜백 */
static int my_port_lag_join(struct dsa_switch *ds, int port,
                           struct dsa_lag lag,
                           struct netdev_lag_upper_info *info,
                           struct netlink_ext_ack *extack)
{
    int lag_id = lag.id;

    /* 하드웨어 트렁크 그룹에 포트 추가 */
    hw_trunk_add_port(ds, lag_id, port);

    /* 해싱 정책 설정 */
    if (info->tx_type == NETDEV_LAG_TX_TYPE_HASH) {
        hw_trunk_set_hash(ds, lag_id, info->hash_type);
    }

    /* FDB에서 LAG 포트 번호 대신 트렁크 ID 사용 */
    hw_fdb_set_trunk_mode(ds, port, lag_id);

    return 0;
}

static int my_port_lag_leave(struct dsa_switch *ds, int port,
                            struct dsa_lag lag)
{
    /* 하드웨어 트렁크 그룹에서 포트 제거 */
    hw_trunk_remove_port(ds, lag.id, port);

    /* 개별 포트 모드로 복원 */
    hw_fdb_set_port_mode(ds, port);

    return 0;
}
LAG 오프로드 제약사항:
  • 하드웨어 트렁크 그룹 수가 제한적입니다 (보통 4~8개). 초과 시 소프트웨어 폴백 발생
  • 크로스-칩 LAG(서로 다른 스위치의 포트를 하나의 LAG로)는 일부 칩셋만 지원
  • LACP PDU(프로토콜 프레임)는 항상 CPU로 트랩되어 소프트웨어에서 처리됨
  • 해싱 알고리즘이 소프트웨어 bonding과 다를 수 있어 트래픽 분배 패턴이 상이할 수 있음

DSA 성능 튜닝

DSA 환경의 성능은 CPU 포트 대역폭, 태그 처리 오버헤드, GRO/GSO 설정, RPS/RFS 분배 정책 등 여러 요소에 의해 결정됩니다. 이 섹션에서는 실전 성능 튜닝 기법을 다룹니다.

DSA 성능 병목 지점 및 튜닝 포인트 주요 성능 병목 1. CPU 포트 대역폭 제한 (RGMII 1Gbps) 2. NAPI poll 단일 큐 (master NIC) 3. skb 재할당 (headroom/tailroom 부족) 4. 태그 처리 CPU 사이클 5. 소프트웨어 폴백 (오프로드 미지원 기능) 튜닝 포인트 1. SGMII/2.5G CPU 포트 사용 2. RPS/RFS로 멀티코어 분배 3. needed_headroom 정확히 설정 4. GRO 활성화 + flow_dissect 최적화 5. 최대한 하드웨어 오프로드 활용 일반적인 DSA 성능 수치 (참고용, 칩/SoC 의존) 하드웨어 포워딩 (오프로드): 와이어 스피드 (1~10 Gbps 포트 속도 그대로) CPU 포트 경유 (태그 처리): RGMII 기준 800~950 Mbps (태그 오버헤드 차감) 소프트웨어 포워딩 (CPU): 200~500 Mbps (SoC 성능 의존, iptables/tc 필터 추가 시 감소) 멀티칩 cascade: cascade 링크 속도가 전체 throughput 상한 (1G 링크 = 최대 1G 크로스칩) LAG 오프로드: N x 포트속도 (2포트 LAG = 2Gbps 양방향, 해싱 균등 전제)
# DSA 성능 튜닝 스크립트

# === 1. RPS (Receive Packet Steering) ===
# CPU 포트(eth0) 단일 큐 트래픽을 멀티코어로 분배
# 4코어 시스템의 경우 모든 코어 활용: 0xf
echo f > /sys/class/net/eth0/queues/rx-0/rps_cpus
echo 4096 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt

# RFS (Receive Flow Steering) 활성화
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries

# === 2. GRO 최적화 ===
# master NIC에서 GRO 활성화 (태그가 flow_dissect 지원 시)
ethtool -K eth0 gro on

# 만약 태그 해석 문제 발생 시 GRO 비활성화
# ethtool -K eth0 gro off

# === 3. interrupt coalescing ===
# master NIC 인터럽트 병합 (처리량 vs 지연시간 트레이드오프)
ethtool -C eth0 rx-usecs 50 rx-frames 32

# === 4. 큐 설정 ===
# master NIC TX 큐 길이 조정
ip link set eth0 txqueuelen 2000

# === 5. NAPI budget 조정 ===
# NAPI poll당 처리할 패킷 수 (기본값 64)
sysctl -w net.core.netdev_budget=300
sysctl -w net.core.netdev_budget_usecs=8000

# === 6. needed_headroom 확인 ===
# 태그 드라이버의 headroom 설정 확인
for dev in /sys/class/net/lan*; do
    NAME=$(basename $dev)
    echo "$NAME: headroom=$(cat /sys/class/net/$NAME/needed_headroom 2>/dev/null)"
done

# === 7. iperf3 성능 측정 ===
# 하드웨어 포워딩 vs CPU 경유 비교

# 오프로드된 경로 (lan0 -> lan1, 같은 브리지)
# (스위치가 직접 포워딩, CPU 미관여)
iperf3 -c 192.168.1.2 -B 192.168.1.1 -t 30

# CPU 경유 경로 (라우팅이 필요한 경우)
iperf3 -c 10.0.1.2 -B 10.0.0.1 -t 30

# === 8. CPU 사용률 모니터링 ===
# DSA 트래픽 처리 중 CPU 사용률 확인
mpstat -P ALL 1
# softirq 비중이 높으면 RPS 분배 검토
cat /proc/softirqs | grep NET
CPU 포트 병목 해결: RGMII(1Gbps) CPU 포트가 병목인 경우, 스위치 칩이 지원한다면 SGMII(1/2.5Gbps)나 USXGMII(10Gbps) 모드로 전환하세요. DTS에서 phy-mode = "sgmii"로 변경하고, SoC MAC이 해당 모드를 지원하는지 확인해야 합니다. MT7531은 SGMII 2.5G를 지원하며, Marvell 88E6190은 10G USXGMII도 지원합니다.
GRO와 DSA 태그 호환성: GRO가 활성화된 상태에서 DSA 태그 드라이버의 flow_dissect() 콜백이 올바르게 구현되지 않으면, 서로 다른 포트의 프레임이 하나의 GRO 세션으로 병합되어 태그 해석이 실패할 수 있습니다. 이 경우 ethtool -K eth0 gro off로 GRO를 비활성화하거나, 태그 드라이버의 flow_dissect 콜백을 수정해야 합니다. 대부분의 최신 커널(6.x) DSA 태그 드라이버는 이 문제가 해결되어 있습니다.

DSA 관련 sysfs/procfs 인터페이스

DSA 프레임워크는 sysfs를 통해 다양한 런타임 정보를 노출합니다. 디버깅 및 모니터링 시 이러한 인터페이스를 활용하면 커널 로그를 분석하지 않고도 DSA 상태를 빠르게 확인할 수 있습니다.

경로설명읽기/쓰기예시 값
/sys/class/net/lanX/dsa/tagging현재 태그 프로토콜읽기dsa, edsa, ocelot
/sys/class/net/lanX/operstate포트 동작 상태읽기up, down
/sys/class/net/lanX/speed링크 속도 (Mbps)읽기1000
/sys/class/net/lanX/duplex듀플렉스 모드읽기full
/sys/class/net/lanX/carrier캐리어(링크) 감지읽기1
/sys/class/net/lanX/mtuMTU 값읽기/쓰기1500
/sys/class/net/lanX/needed_headroom필요 headroom (바이트)읽기4, 8, 16
/sys/class/net/lanX/needed_tailroom필요 tailroom (바이트)읽기0, 2
/sys/class/net/lanX/statistics/포트 패킷 통계읽기rx_bytes, tx_bytes 등
/sys/class/net/eth0/dsa_ptrmaster의 DSA 포인터읽기(커널 내부용)
# DSA 상태 종합 조회 스크립트
#!/bin/bash

echo "===== DSA 포트 상태 종합 ====="
echo ""

for dev in /sys/class/net/lan*; do
    NAME=$(basename $dev)
    TAG=$(cat $dev/dsa/tagging 2>/dev/null || echo "N/A")
    MTU=$(cat $dev/mtu 2>/dev/null || echo "N/A")
    OPER=$(cat $dev/operstate 2>/dev/null || echo "N/A")
    SPEED=$(cat $dev/speed 2>/dev/null || echo "N/A")
    CARRIER=$(cat $dev/carrier 2>/dev/null || echo "N/A")
    HEADROOM=$(cat $dev/needed_headroom 2>/dev/null || echo "N/A")
    TAILROOM=$(cat $dev/needed_tailroom 2>/dev/null || echo "N/A")
    RX_BYTES=$(cat $dev/statistics/rx_bytes 2>/dev/null || echo "0")
    TX_BYTES=$(cat $dev/statistics/tx_bytes 2>/dev/null || echo "0")
    RX_DROP=$(cat $dev/statistics/rx_dropped 2>/dev/null || echo "0")
    TX_DROP=$(cat $dev/statistics/tx_dropped 2>/dev/null || echo "0")

    printf "%-8s tag=%-10s mtu=%-6s oper=%-6s speed=%-6s carrier=%s\n" \
           $NAME $TAG $MTU $OPER ${SPEED}Mbps $CARRIER
    printf "         headroom=%-4s tailroom=%-4s rx=%s tx=%s rx_drop=%s tx_drop=%s\n" \
           $HEADROOM $TAILROOM $RX_BYTES $TX_BYTES $RX_DROP $TX_DROP
    echo ""
done

# Master 정보
echo "===== Master (CPU 포트) ====="
MASTER=$(ip -o link show | grep 'dsa' | head -1 | awk -F'master ' '{print $2}' | awk '{print $1}')
if [ -n "$MASTER" ]; then
    echo "Master device: $MASTER"
    ip -s -d link show $MASTER
fi
devlink trap을 통한 CPU 트래핑 모니터링: devlink trap는 어떤 유형의 패킷이 CPU로 올라오는지 상세히 보여줍니다. devlink trap show으로 지원되는 트랩 목록을 확인하고, devlink trap group show으로 그룹별 통계를 확인하세요. 불필요한 트래픽이 CPU로 올라오면 성능 저하의 원인이 됩니다.

참고자료