soobook
NETWORK

Ethernet

Ethernet frame, MAC address, switch forwarding, ARP 흐름을 정리

Ethernet은 단순히 랜선을 뜻하지 않는다. 같은 링크 안에서 frame을 만들고, MAC address로 목적지를 찾고, switch가 그 frame을 넘겨주는 Layer 2 기술 묶음이다.

Intro

브라우저 주소창에 soobook.io를 입력하거나, 서버에서 GET /api/users 같은 요청을 보낸다고 해보자.

우리 눈에는 HTTP 요청 하나처럼 보인다. 조금 더 내려가면 DNS, TCP, TLS, IP 같은 단어가 나온다.

하지만 그 packet이 실제로 첫 번째 장비까지 가려면 더 낮은 층의 전달 방식이 필요하다. 노트북은 공유기나 access point로 보내야 하고, 서버는 NIC를 통해 top-of-rack switch로 보내야 한다. VM host는 gateway나 virtual switch로 넘겨야 한다.

이때 자주 만나는 링크가 Ethernet이다.

Ethernet은 “어느 웹사이트로 가는가”를 직접 보지 않는다. 대신 현재 link에서 다음 장비가 누구인지, 즉 destination MAC address가 무엇인지 보고 frame을 넘긴다. 인터넷 전체 경로를 한 번에 해결하는 기술이 아니라, 매 hop에서 다음 hop으로 data를 넘기는 Layer 2 기술이다.

그래서 Ethernet을 이해할 때는 속도보다 먼저 frame을 봐야 한다. HTTP 요청이든 API response든, 상위 계층에서 내려온 data는 결국 Ethernet frame에 담겨 가까운 다음 장비로 이동한다.

{
  "diagram": "html-diagram",
  "variant": "explainer",
  "title": "Ethernet frame",
  "width": 980,
  "height": 360,
  "mobileWidth": 900,
  "regions": [
    {
      "id": "wire-region",
      "label": "on the wire\\nframe fields",
      "x": 34,
      "y": 58,
      "width": 912,
      "height": 196
    }
  ],
  "nodes": [
    {
      "id": "preamble",
      "kind": "note",
      "label": "Preamble\\nSFD",
      "caption": "sync",
      "x": 102,
      "y": 164,
      "width": 104,
      "height": 72
    },
    {
      "id": "dst",
      "kind": "nic",
      "label": "Destination\\nMAC",
      "caption": "6 bytes",
      "x": 238,
      "y": 164,
      "width": 128,
      "height": 72
    },
    {
      "id": "src",
      "kind": "nic",
      "label": "Source\\nMAC",
      "caption": "6 bytes",
      "x": 382,
      "y": 164,
      "width": 128,
      "height": 72
    },
    {
      "id": "type",
      "kind": "switch",
      "label": "Type\\nor Length",
      "caption": "2 bytes",
      "x": 526,
      "y": 164,
      "width": 128,
      "height": 72
    },
    {
      "id": "payload",
      "kind": "memory",
      "label": "Payload",
      "caption": "46-1500 bytes",
      "x": 690,
      "y": 164,
      "width": 160,
      "height": 72
    },
    {
      "id": "fcs",
      "kind": "note",
      "label": "FCS",
      "caption": "4 bytes",
      "x": 854,
      "y": 164,
      "width": 96,
      "height": 72
    }
  ],
  "links": [
    {
      "id": "frame-path",
      "points": [[154, 164], [174, 164], [238, 164], [382, 164], [526, 164], [690, 164], [806, 164]],
      "arrow": false,
      "flow": {
        "speed": "fast",
        "count": 2,
        "emphasis": "strong"
      }
    }
  ],
  "labels": [
    {
      "text": "header",
      "x": 382,
      "y": 250
    },
    {
      "text": "upper-layer data",
      "x": 690,
      "y": 250
    },
    {
      "text": "error check",
      "x": 854,
      "y": 250
    }
  ],
  "callouts": [
    {
      "id": "min-frame",
      "tone": "primary",
      "anchor": "top",
      "x": 488,
      "y": 286,
      "width": 338,
      "text": "minimum Ethernet frame은 destination MAC부터 FCS까지 64 bytes다."
    }
  ]
}

Frame

Ethernet을 아주 쉽게 말하면, 가까운 장비에게 데이터를 건네기 위한 포장 규칙이다.

내가 보낸 HTTP 요청이나 API 응답은 그대로 랜선 위를 흐르지 않는다. 먼저 IP packet이 되고, 그 IP packet은 다시 Ethernet이 이해할 수 있는 포장지 안에 들어간다.

이 포장된 덩어리를 frame이라고 부른다.

Frame 겉면에는 “이 link에서 누가 받아야 하는지”, “누가 보냈는지”, “안에 든 내용물이 어떤 protocol인지”, “전송 중 망가지지 않았는지” 같은 정보가 붙는다.

IP packet은 Ethernet payload 안에 들어간다. Ethernet header는 같은 link 안에서 어디로 보낼지를 나타내고, IP header는 network layer에서 최종 목적지를 나타낸다.

일반적인 Ethernet II frame은 다음처럼 볼 수 있다.

FieldSizeMeaning
Preamble + SFD8 bytesreceiver가 bit stream의 시작을 맞추는 동기화 영역
Destination MAC6 bytes이 frame을 받을 link-layer 목적지
Source MAC6 bytes이 frame을 보낸 link-layer 출발지
EtherType2 bytespayload가 IPv4, ARP, IPv6 같은 어떤 protocol인지 표시
Payload46-1500 bytes상위 계층 data
FCS4 bytesframe 손상 여부를 확인하는 checksum

보통 말하는 64-byte minimum frame은 Destination MAC부터 FCS까지를 센다. Preamble, SFD, inter-frame gap은 그 계산에서 빼는 경우가 많다.

Payload가 46 bytes보다 작으면 padding이 붙는다. 예를 들어 ARP packet처럼 작은 payload도 Ethernet 최소 frame 크기를 맞춰야 한다.

반대로 흔한 Ethernet MTU 1500은 Ethernet payload에 들어가는 IP packet의 최대 크기로 이해하면 된다.

ping -s 1500이 곧 Ethernet frame 1500 bytes를 보낸다는 뜻은 아니다. IP payload, IP header, ICMP header, Ethernet header가 어느 층에서 계산되는지 분리해서 봐야 한다.

MAC

MAC address는 Ethernet frame이 같은 link 안에서 목적지를 찾는 데 쓰는 48-bit 주소다.

예를 들면 이런 모양이다.

52:54:00:12:34:56

앞쪽 bit들은 제조사 식별자나 locally administered 여부 같은 의미를 가질 수 있고, 나머지는 interface를 구분하는 값으로 쓰인다.

MAC address에는 몇 가지 특별한 형태가 있다.

  • ff:ff:ff:ff:ff:ff: broadcast address
  • 첫 octet의 least significant bit가 1인 주소: multicast address
  • locally administered bit가 켜진 주소: VM, container, overlay network에서 자주 보이는 주소

MAC address는 IP address와 다르다. IP address는 subnet과 routing을 통해 network 경로를 표현하고, MAC address는 현재 link에서 다음 hop 장치를 찾는 데 쓰인다.

라우터를 지나면 Ethernet header는 새로 만들어진다. 내 노트북이 인터넷의 어떤 서버로 packet을 보낼 때 Ethernet destination MAC은 그 서버의 MAC이 아니라 기본 gateway의 MAC이다.

Switch

Hub 시대의 Ethernet은 같은 선을 여러 장비가 공유하는 느낌에 가까웠다.

현대 Ethernet은 대부분 switch 기반이다. 각 host는 switch port에 붙고, switch는 frame의 MAC address를 보고 어느 port로 내보낼지 결정한다.

{
  "diagram": "html-diagram",
  "variant": "explainer",
  "title": "Switch forwarding",
  "width": 980,
  "height": 500,
  "mobileWidth": 900,
  "regions": [
    {
      "id": "access-lan",
      "label": "same VLAN\\nsame broadcast domain",
      "x": 34,
      "y": 52,
      "width": 912,
      "height": 396
    }
  ],
  "nodes": [
    {
      "id": "host-a",
      "kind": "host",
      "label": "Host A",
      "caption": "MAC A",
      "x": 148,
      "y": 252,
      "width": 132,
      "height": 70
    },
    {
      "id": "switch",
      "kind": "switch",
      "label": "Switch",
      "caption": "MAC table",
      "x": 490,
      "y": 252,
      "width": 150,
      "height": 78
    },
    {
      "id": "host-b",
      "kind": "host",
      "label": "Host B",
      "caption": "MAC B",
      "x": 820,
      "y": 184,
      "width": 132,
      "height": 70
    },
    {
      "id": "host-c",
      "kind": "host",
      "label": "Host C",
      "caption": "MAC C",
      "x": 820,
      "y": 350,
      "width": 132,
      "height": 70
    },
    {
      "id": "table",
      "kind": "memory",
      "label": "MAC table",
      "caption": "A -> port 1\\nB -> port 2",
      "x": 490,
      "y": 384,
      "width": 170,
      "height": 82
    }
  ],
  "links": [
    {
      "id": "a-to-switch",
      "points": [[214, 252], [415, 252]],
      "flow": {
        "speed": "fast",
        "count": 2,
        "emphasis": "strong"
      }
    },
    {
      "id": "switch-to-b",
      "points": [[565, 238], [664, 184], [754, 184]],
      "flow": {
        "speed": "fast",
        "count": 2,
        "emphasis": "strong"
      }
    },
    {
      "id": "unknown-to-c",
      "points": [[565, 266], [664, 350], [754, 350]],
      "dashed": true,
      "tone": "muted",
      "flow": {
        "speed": "slow",
        "emphasis": "muted"
      }
    },
    {
      "id": "learn-table",
      "points": [[490, 291], [490, 343]],
      "dashed": true,
      "flow": false
    }
  ],
  "labels": [
    {
      "text": "1. source MAC 학습",
      "x": 330,
      "y": 216
    },
    {
      "text": "2. dest lookup",
      "x": 668,
      "y": 145
    },
    {
      "text": "unknown이면 flood",
      "x": 674,
      "y": 306
    }
  ],
  "badges": [
    {
      "id": "step-1",
      "text": "1",
      "x": 286,
      "y": 252
    },
    {
      "id": "step-2",
      "text": "2",
      "x": 630,
      "y": 184
    }
  ]
}

Switch의 기본 동작은 단순하다.

먼저 switch는 frame이 들어올 때 source MAC과 들어온 port를 함께 기록한다.

예를 들어 Host A가 switch의 port 1에 연결돼 있고, Host A의 MAC address가 AA:AA라고 해보자.

Host A가 frame을 보내면 frame header에는 Source MAC = AA:AA가 들어 있다.

Switch는 그 frame이 port 1로 들어온 것을 보고 MAC table에 AA:AA -> port 1을 저장한다.

이게 source MAC 학습이다.

중요한 점은 switch가 destination MAC으로 학습하는 것이 아니라 source MAC으로 학습한다는 것이다.

Frame이 port 1에서 들어왔으면, 그 frame을 보낸 장비는 적어도 port 1 방향에 있다는 것을 확실히 알 수 있기 때문이다.

그 다음 switch는 destination MAC을 조회한다.

  1. Source MAC과 들어온 port를 MAC table에 기록한다.
  2. Destination MAC이 table에 있으면 해당 port로만 보낸다.
  3. Destination MAC이 없으면 같은 VLAN 안의 다른 port로 flood한다. (들어온 포트 말고 나머지로 전부 뿌린다)
  4. Broadcast frame도 같은 broadcast domain 안에서 flood한다.
  5. 오래된 MAC table entry는 aging으로 지운다.

이 덕분에 같은 switch에 여러 host가 붙어 있어도 모든 frame이 모든 port로 계속 흘러가지는 않는다.

다만 broadcast와 unknown unicast는 여전히 넓게 퍼질 수 있다. 그래서 VLAN, router boundary, loop prevention이 중요해진다.

ARP(Address Resolution Protocol)

IPv4에서 host가 같은 subnet의 IP address로 packet을 보내려면 먼저 그 IP에 대응하는 MAC address를 알아야 한다.

이때 쓰는 것이 ARP다.

Host A가 192.0.2.20에게 보내고 싶지만 MAC을 모른다고 해보자.

  1. Host A는 ARP request를 broadcast로 보낸다. (같은 네트워크 구역 안의 모두에게 보낸다)
  2. 같은 broadcast domain의 모든 host가 request를 본다.
  3. 192.0.2.20을 가진 Host B가 자기 MAC을 담아 ARP reply를 보낸다.
  4. Host A는 ARP cache에 192.0.2.20 -> MAC B를 저장한다.
  5. 이후 IP packet을 Ethernet frame에 넣을 때 destination MAC으로 MAC B를 쓴다.

ARP는 IP routing을 대체하지 않는다. 같은 subnet에 있으면 목적지 host의 MAC을 찾고, 다른 subnet이면 gateway의 MAC을 찾는다.

Path

실제 packet이 같은 subnet 안에서 이동하는 과정을 한 번에 보면 이렇다.

  1. Application이 data를 보낸다.
  2. TCP 또는 UDP segment가 만들어진다.
  3. IP packet이 만들어진다.
  4. Host는 destination IP가 같은 subnet인지 확인한다.
  5. 같은 subnet이면 ARP cache에서 destination MAC을 찾는다.
  6. 없으면 ARP request를 보낸다.
  7. Ethernet frame을 만든다.
  8. NIC가 frame을 link로 보낸다.
  9. Switch가 source MAC을 학습하고 destination MAC을 lookup한다.
  10. 목적지 host의 NIC가 frame을 받고 FCS를 확인한다.
  11. Payload가 IP layer로 올라간다.

다른 subnet이면 5번에서 찾는 MAC이 destination host가 아니라 default gateway의 MAC으로 바뀐다. Router를 지나면 다음 link에 맞는 새 Ethernet frame이 만들어진다.

Summary

Ethernet은 link 안에서 frame을 전달하는 기술이다.

Frame에는 source MAC, destination MAC, payload, FCS가 들어간다. IP packet은 그 payload 안에 실린다.

Switch는 source MAC을 학습하고 destination MAC을 lookup해서 frame을 보낸다. 모르면 flood하고, broadcast도 같은 broadcast domain 안에서 퍼진다.

ARP는 IPv4 address를 Ethernet MAC address로 바꾸는 glue 역할을 한다. 다른 subnet으로 나갈 때는 최종 목적지가 아니라 gateway의 MAC을 찾는다.

결국 이 글에서 잡아야 할 핵심은 하나다. Ethernet은 frame을 만들고, 같은 link 안에서 다음 hop에게 넘긴다.

References