3.1 プロジェクト構造

go-network-programming/
├── go.mod
├── go.sum
├── main.go
├── packet.go
├── node.go
├── link.go
├── network_stats.go
├── bandwidth_limiter.go
├── mac_address.go    # 新規追加
├── ethernet_frame.go # 新規追加
└── switch.go         # 新規追加

この章では、スイッチを実装して複数のノードを接続できるローカルネットワークを構築します。また、MACアドレスを導入してイーサネットレベルでの通信を実現します。

3.2 MACアドレスの実装

MACアドレス(Media Access Control Address)は、ネットワークインターフェースの物理アドレスです。

ファイル名: ./mac_address.go

package main

import (
    "fmt"
    "math/rand"
    "strconv"
    "strings"
)

// MACAddress はMAC(Media Access Control)アドレスを表現する
// 実際のイーサネットで使用される6バイトの物理アドレス
type MACAddress struct {
    bytes [6]byte // 6バイトのMACAアドレス(例:aa:bb:cc:dd:ee:ff)
}

// NewMACAddress は指定されたバイト配列からMACアドレスを作成
func NewMACAddress(bytes [6]byte) MACAddress {
    return MACAddress{bytes: bytes}
}

// ParseMACAddress は文字列からMACアドレスを解析
// 例:ParseMACAddress("aa:bb:cc:dd:ee:ff")
func ParseMACAddress(s string) (MACAddress, error) {
    parts := strings.Split(s, ":")
    if len(parts) != 6 {
        return MACAddress{}, fmt.Errorf("invalid MAC address format: %s", s)
    }
    
    var mac MACAddress
    for i, part := range parts {
        val, err := strconv.ParseUint(part, 16, 8)
        if err != nil {
            return MACAddress{}, fmt.Errorf("invalid hex value in MAC address: %s", part)
        }
        mac.bytes[i] = byte(val)
    }
    
    return mac, nil
}

// RandomMACAddress はランダムなMACアドレスを生成
// ユニキャスト、ローカル管理アドレスとして生成
func RandomMACAddress() MACAddress {
    var mac MACAddress
    for i := 0; i < 6; i++ {
        mac.bytes[i] = byte(rand.Intn(256))
    }
    
    // ユニキャスト(LSBを0に)、ローカル管理(2番目のLSBを1に)に設定
    mac.bytes[0] = (mac.bytes[0] & 0xFC) | 0x02
    
    return mac
}

// String はMACアドレスの文字列表現を返す
func (mac MACAddress) String() string {
    return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
        mac.bytes[0], mac.bytes[1], mac.bytes[2],
        mac.bytes[3], mac.bytes[4], mac.bytes[5])
}

// Equals は2つのMACアドレスが等しいかチェック
func (mac MACAddress) Equals(other MACAddress) bool {
    return mac.bytes == other.bytes
}

// IsUnicast はユニキャストアドレスかチェック
func (mac MACAddress) IsUnicast() bool {
    return (mac.bytes[0] & 0x01) == 0
}

// IsBroadcast はブロードキャストアドレスかチェック
func (mac MACAddress) IsBroadcast() bool {
    return mac.bytes == [6]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
}

// IsMulticast はマルチキャストアドレスかチェック
func (mac MACAddress) IsMulticast() bool {
    return (mac.bytes[0] & 0x01) == 1 && !mac.IsBroadcast()
}

// BroadcastMAC はブロードキャストMACアドレスを返す
func BroadcastMAC() MACAddress {
    return MACAddress{bytes: [6]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}
}

3.3 イーサネットフレームの実装

MACアドレスを含むイーサネットフレーム構造を実装します。

ファイル名: ./ethernet_frame.go

package main

import (
    "fmt"
    "time"
    
    "github.com/google/uuid"
)

// EtherType はイーサネットフレームのタイプを定義
type EtherType uint16

const (
    EtherTypeIPv4 EtherType = 0x0800 // IPv4
    EtherTypeARP  EtherType = 0x0806 // ARP
    EtherTypeIPv6 EtherType = 0x86DD // IPv6
    EtherTypeData EtherType = 0x9999 // 独自データタイプ(学習用)
)

// EthernetFrame はイーサネットフレームを表現する
// 実際のイーサネット通信で使用されるフレーム構造
type EthernetFrame struct {
    ID          string      // フレーム識別子(デバッグ用)
    Destination MACAddress  // 宛先MACアドレス
    Source      MACAddress  // 送信元MACアドレス
    EtherType   EtherType   // フレームタイプ
    Payload     []byte      // ペイロード(上位層データ)
    Size        int         // フレーム全体のサイズ
    Timestamp   time.Time   // フレーム生成時刻
}

// NewEthernetFrame は新しいイーサネットフレームを作成
func NewEthernetFrame(src, dst MACAddress, etherType EtherType, payload []byte) *EthernetFrame {
    frame := &EthernetFrame{
        ID:          uuid.New().String(),
        Destination: dst,
        Source:      src,
        EtherType:   etherType,
        Payload:     payload,
        Size:        14 + len(payload), // イーサネットヘッダ14バイト + ペイロード
        Timestamp:   time.Now(),
    }
    return frame
}

// String はフレームの文字列表現を返す
func (frame *EthernetFrame) String() string {
    return fmt.Sprintf("EthernetFrame{ID: %s, %s -> %s, Type: 0x%04x, Size: %d bytes}",
        frame.ID[:8], frame.Source, frame.Destination, frame.EtherType, frame.Size)
}

// IsUnicast はユニキャストフレームかチェック
func (frame *EthernetFrame) IsUnicast() bool {
    return frame.Destination.IsUnicast()
}

// IsBroadcast はブロードキャストフレームかチェック
func (frame *EthernetFrame) IsBroadcast() bool {
    return frame.Destination.IsBroadcast()
}

// IsMulticast はマルチキャストフレームかチェック
func (frame *EthernetFrame) IsMulticast() bool {
    return frame.Destination.IsMulticast()
}

// Clone はフレームのコピーを作成(ブロードキャスト時に使用)
func (frame *EthernetFrame) Clone() *EthernetFrame {
    newFrame := *frame
    newFrame.ID = uuid.New().String() // 新しいIDを生成
    
    // ペイロードをコピー
    newFrame.Payload = make([]byte, len(frame.Payload))
    copy(newFrame.Payload, frame.Payload)
    
    return &newFrame
}

3.4 ノードの拡張(MACアドレス対応)

ノードにMACアドレス機能を追加します。

ファイル名: ./node.go (更新)

package main

import (
    "fmt"
    "time"
    
    "github.com/google/uuid"
)

// Node はネットワーク上のデバイスを表現する(MACアドレス対応版)
type Node struct {
    ID        string                    // ノードID
    Name      string                    // ノード名
    MACAddr   MACAddress               // MACアドレス
    inbox     chan *EthernetFrame      // 受信用フレームキュー
    outbox    chan *EthernetFrame      // 送信用フレームキュー
    links     map[string]*Link         // 接続リンク
    switchRef *Switch                  // 接続されているスイッチ(あれば)
    running   bool                     // 動作状態
    stats     *NetworkStats            // 統計情報
}

// NewNode は新しいノードを作成(MACアドレス自動生成)
func NewNode(name string) *Node {
    return &Node{
        ID:        uuid.New().String(),
        Name:      name,
        MACAddr:   RandomMACAddress(),
        inbox:     make(chan *EthernetFrame, 100),
        outbox:    make(chan *EthernetFrame, 100),
        links:     make(map[string]*Link),
        switchRef: nil,
        running:   false,
        stats:     NewNetworkStats(),
    }
}

// NewNodeWithMAC は指定されたMACアドレスでノードを作成
func NewNodeWithMAC(name string, macAddr MACAddress) *Node {
    return &Node{
        ID:        uuid.New().String(),
        Name:      name,
        MACAddr:   macAddr,
        inbox:     make(chan *EthernetFrame, 100),
        outbox:    make(chan *EthernetFrame, 100),
        links:     make(map[string]*Link),
        switchRef: nil,
        running:   false,
        stats:     NewNetworkStats(),
    }
}

// Start はノードの動作を開始
func (n *Node) Start() {
    if n.running {
        return
    }
    n.running = true
    
    go n.processFrames()
    fmt.Printf("Node %s started (MAC: %s)\n", n.Name, n.MACAddr)
}

// Stop はノードの動作を停止
func (n *Node) Stop() {
    if !n.running {
        return
    }
    n.running = false
    close(n.inbox)
    close(n.outbox)
    fmt.Printf("Node %s stopped\n", n.Name)
    n.stats.Print()
}

// SendFrame は指定された宛先MACアドレスにフレームを送信
func (n *Node) SendFrame(dstMAC MACAddress, data []byte) error {
    if !n.running {
        return fmt.Errorf("node %s is not running", n.Name)
    }
    
    frame := NewEthernetFrame(n.MACAddr, dstMAC, EtherTypeData, data)
    
    // スイッチに接続されている場合はスイッチ経由で送信
    if n.switchRef != nil {
        return n.switchRef.SendFrame(frame, n)
    }
    
    // 直接リンクがある場合はリンク経由で送信
    for _, link := range n.links {
        if link.CanReachMAC(dstMAC) {
            return link.SendFrame(frame)
        }
    }
    
    return fmt.Errorf("no route to MAC address %s", dstMAC)
}

// SendData は名前指定でデータを送信(簡易版)
func (n *Node) SendData(destinationName string, data []byte) error {
    // 簡易的に名前からMACアドレスを推測
    // 実際のネットワークではARP等でMACアドレスを解決する
    if n.switchRef != nil {
        targetMAC := n.switchRef.FindMACByName(destinationName)
        if targetMAC != nil {
            return n.SendFrame(*targetMAC, data)
        }
    }
    
    return fmt.Errorf("cannot resolve MAC address for %s", destinationName)
}

// Broadcast はブロードキャストでデータを送信
func (n *Node) Broadcast(data []byte) error {
    if !n.running {
        return fmt.Errorf("node %s is not running", n.Name)
    }
    
    frame := NewEthernetFrame(n.MACAddr, BroadcastMAC(), EtherTypeData, data)
    
    if n.switchRef != nil {
        return n.switchRef.SendFrame(frame, n)
    }
    
    return fmt.Errorf("no switch connected for broadcast")
}

// ReceiveFrame は受信したフレームを返す
func (n *Node) ReceiveFrame() *EthernetFrame {
    if !n.running {
        return nil
    }
    
    select {
    case frame := <-n.inbox:
        return frame
    case <-time.After(1 * time.Second):
        return nil
    }
}

// ConnectToSwitch はスイッチに接続
func (n *Node) ConnectToSwitch(sw *Switch) {
    n.switchRef = sw
    sw.ConnectNode(n)
}

// AddLink はリンクを追加
func (n *Node) AddLink(link *Link) {
    n.links[link.ID] = link
    fmt.Printf("Link added to node %s\n", n.Name)
}

// processFrames はフレーム処理のメインループ
func (n *Node) processFrames() {
    for n.running {
        select {
        case frame := <-n.outbox:
            if frame != nil {
                fmt.Printf("Node %s processing outgoing: %s\n", n.Name, frame)
            }
        default:
            time.Sleep(10 * time.Millisecond)
        }
    }
}

func (n *Node) String() string {
    return fmt.Sprintf("Node{Name: %s, MAC: %s, ID: %s}", 
        n.Name, n.MACAddr, n.ID[:8])
}

3.5 スイッチの実装

複数のノードを接続するイーサネットスイッチを実装します。

ファイル名: ./switch.go

package main

import (
    "fmt"
    "sync"
    "time"
    
    "github.com/google/uuid"
)

// SwitchPort はスイッチのポートを表現
type SwitchPort struct {
    ID       string
    Number   int
    Node     *Node
    Link     *Link
    Active   bool
    Stats    *NetworkStats
}

// Switch はイーサネットスイッチを実装
// 複数のノードを接続し、MACアドレスベースでフレームを転送
type Switch struct {
    ID          string
    Name        string
    Ports       map[int]*SwitchPort          // ポート番号 -> ポート
    MACTable    map[MACAddress]*SwitchPort   // MACアドレス -> ポート(学習テーブル)
    BcastDomain []*SwitchPort               // ブロードキャストドメイン
    running     bool
    mu          sync.RWMutex                // MACテーブルの排他制御
    stats       *NetworkStats
}

// NewSwitch は新しいスイッチを作成
func NewSwitch(name string) *Switch {
    return &Switch{
        ID:          uuid.New().String(),
        Name:        name,
        Ports:       make(map[int]*SwitchPort),
        MACTable:    make(map[MACAddress]*SwitchPort),
        BcastDomain: make([]*SwitchPort, 0),
        running:     false,
        stats:       NewNetworkStats(),
    }
}

// Start はスイッチの動作を開始
func (sw *Switch) Start() {
    if sw.running {
        return
    }
    sw.running = true
    
    // ブロードキャストドメインを更新
    sw.updateBroadcastDomain()
    
    fmt.Printf("Switch %s started (%d ports)\n", sw.Name, len(sw.Ports))
}

// Stop はスイッチの動作を停止
func (sw *Switch) Stop() {
    if !sw.running {
        return
    }
    sw.running = false
    
    fmt.Printf("Switch %s stopped\n", sw.Name)
    sw.PrintMACTable()
    sw.stats.Print()
}

// ConnectNode はノードをスイッチに接続
func (sw *Switch) ConnectNode(node *Node) *SwitchPort {
    portNum := len(sw.Ports) + 1
    
    port := &SwitchPort{
        ID:     uuid.New().String(),
        Number: portNum,
        Node:   node,
        Active: true,
        Stats:  NewNetworkStats(),
    }
    
    sw.Ports[portNum] = port
    sw.updateBroadcastDomain()
    
    fmt.Printf("Node %s connected to switch %s port %d\n", 
        node.Name, sw.Name, portNum)
    
    return port
}

// SendFrame はフレームを送信(スイッチの中核機能)
func (sw *Switch) SendFrame(frame *EthernetFrame, sourceNode *Node) error {
    if !sw.running {
        return fmt.Errorf("switch %s is not running", sw.Name)
    }
    
    // 送信元ノードのポートを特定
    var sourcePort *SwitchPort
    for _, port := range sw.Ports {
        if port.Node == sourceNode {
            sourcePort = port
            break
        }
    }
    
    if sourcePort == nil {
        return fmt.Errorf("source node not connected to switch")
    }
    
    // MACアドレス学習:送信元MACアドレスをテーブルに記録
    sw.learnMAC(frame.Source, sourcePort)
    
    // 統計情報を更新
    sw.stats.RecordSentPacket(&Packet{
        Size: frame.Size,
        Source: sourceNode.Name,
        Destination: frame.Destination.String(),
    })
    
    // フレームの配送処理
    if frame.IsBroadcast() {
        return sw.broadcastFrame(frame, sourcePort)
    } else {
        return sw.forwardFrame(frame, sourcePort)
    }
}

// learnMAC はMACアドレスを学習テーブルに記録
func (sw *Switch) learnMAC(macAddr MACAddress, port *SwitchPort) {
    sw.mu.Lock()
    defer sw.mu.Unlock()
    
    if existingPort, exists := sw.MACTable[macAddr]; exists {
        if existingPort != port {
            fmt.Printf("Switch %s: MAC %s moved from port %d to port %d\n",
                sw.Name, macAddr, existingPort.Number, port.Number)
        }
    } else {
        fmt.Printf("Switch %s: Learned MAC %s on port %d\n",
            sw.Name, macAddr, port.Number)
    }
    
    sw.MACTable[macAddr] = port
}

// forwardFrame はユニキャストフレームを転送
func (sw *Switch) forwardFrame(frame *EthernetFrame, sourcePort *SwitchPort) error {
    sw.mu.RLock()
    targetPort, known := sw.MACTable[frame.Destination]
    sw.mu.RUnlock()
    
    if known && targetPort.Active {
        // 宛先MACアドレスが学習済み:該当ポートにのみ送信
        return sw.deliverToPort(frame, targetPort, sourcePort)
    } else {
        // 宛先MACアドレス未学習:ブロードキャスト(フラッディング)
        fmt.Printf("Switch %s: Unknown destination %s, flooding\n", 
            sw.Name, frame.Destination)
        return sw.broadcastFrame(frame, sourcePort)
    }
}

// broadcastFrame はフレームをブロードキャスト
func (sw *Switch) broadcastFrame(frame *EthernetFrame, sourcePort *SwitchPort) error {
    fmt.Printf("Switch %s: Broadcasting frame %s\n", sw.Name, frame.ID[:8])
    
    var lastError error
    successCount := 0
    
    // 送信元ポート以外の全ポートに転送
    for _, port := range sw.BcastDomain {
        if port != sourcePort && port.Active {
            // フレームをクローンして送信
            clonedFrame := frame.Clone()
            if err := sw.deliverToPort(clonedFrame, port, sourcePort); err != nil {
                lastError = err
            } else {
                successCount++
            }
        }
    }
    
    if successCount == 0 && lastError != nil {
        return lastError
    }
    
    return nil
}

// deliverToPort は指定されたポートにフレームを配送
func (sw *Switch) deliverToPort(frame *EthernetFrame, targetPort *SwitchPort, sourcePort *SwitchPort) error {
    if targetPort.Node == nil || !targetPort.Node.running {
        return fmt.Errorf("target port %d is not active", targetPort.Number)
    }
    
    // フレームをノードの受信キューに配送
    select {
    case targetPort.Node.inbox <- frame:
        targetPort.Stats.RecordReceivedPacket(&Packet{
            Size: frame.Size,
            Source: frame.Source.String(),
            Destination: targetPort.Node.Name,
        })
        
        fmt.Printf("Switch %s: Frame delivered from port %d to port %d\n",
            sw.Name, sourcePort.Number, targetPort.Number)
        return nil
    case <-time.After(10 * time.Millisecond):
        return fmt.Errorf("failed to deliver frame to port %d (queue full)", 
            targetPort.Number)
    }
}

// FindMACByName は名前からMACアドレスを検索(簡易版)
func (sw *Switch) FindMACByName(name string) *MACAddress {
    for _, port := range sw.Ports {
        if port.Node != nil && port.Node.Name == name {
            return &port.Node.MACAddr
        }
    }
    return nil
}

// updateBroadcastDomain はブロードキャストドメインを更新
func (sw *Switch) updateBroadcastDomain() {
    sw.BcastDomain = make([]*SwitchPort, 0, len(sw.Ports))
    for _, port := range sw.Ports {
        if port.Active {
            sw.BcastDomain = append(sw.BcastDomain, port)
        }
    }
}

// PrintMACTable はMACアドレステーブルを表示
func (sw *Switch) PrintMACTable() {
    sw.mu.RLock()
    defer sw.mu.RUnlock()
    
    fmt.Printf("=== MAC Address Table: %s ===\n", sw.Name)
    if len(sw.MACTable) == 0 {
        fmt.Println("No MAC addresses learned")
    } else {
        for mac, port := range sw.MACTable {
            fmt.Printf("  %s -> Port %d (%s)\n", mac, port.Number, port.Node.Name)
        }
    }
    fmt.Println("===============================")
}

func (sw *Switch) String() string {
    return fmt.Sprintf("Switch{Name: %s, Ports: %d, MACs: %d}", 
        sw.Name, len(sw.Ports), len(sw.MACTable))
}

3.6 メイン関数での動作テスト

複数ノードをスイッチで接続するテストコードです。

ファイル名: ./main.go (更新)

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("=== スイッチネットワークシミュレーション ===")
    
    // スイッチを作成
    switch1 := NewSwitch("SW1")
    
    // 4つのノードを作成
    alice := NewNode("Alice")
    bob := NewNode("Bob")
    charlie := NewNode("Charlie")
    david := NewNode("David")
    
    // ノードをスイッチに接続
    alice.ConnectToSwitch(switch1)
    bob.ConnectToSwitch(switch1)
    charlie.ConnectToSwitch(switch1)
    david.ConnectToSwitch(switch1)
    
    // システム開始
    switch1.Start()
    alice.Start()
    bob.Start()
    charlie.Start()
    david.Start()
    
    fmt.Printf("\n=== 初期状態のMACテーブル ===\n")
    switch1.PrintMACTable()
    
    // AliceがBobに送信(ユニキャスト)
    fmt.Printf("\n=== Test 1: AliceからBobへユニキャスト ===\n")
    message1 := []byte("Hello Bob, this is Alice!")
    err := alice.SendData("Bob", message1)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
    }
    
    time.Sleep(100 * time.Millisecond)
    received1 := bob.ReceiveFrame()
    if received1 != nil {
        fmt.Printf("Bob received: %s\n", string(received1.Payload))
    }
    
    // BobがAliceに返信(学習済みMACアドレス宛)
    fmt.Printf("\n=== Test 2: BobからAliceへ返信 ===\n")
    message2 := []byte("Hi Alice, nice to hear from you!")
    err = bob.SendData("Alice", message2)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
    }
    
    time.Sleep(100 * time.Millisecond)
    received2 := alice.ReceiveFrame()
    if received2 != nil {
        fmt.Printf("Alice received: %s\n", string(received2.Payload))
    }
    
    // Charlieがブロードキャスト
    fmt.Printf("\n=== Test 3: Charlieからブロードキャスト ===\n")
    message3 := []byte("Hello everyone! This is Charlie speaking.")
    err = charlie.Broadcast(message3)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
    }
    
    time.Sleep(100 * time.Millisecond)
    
    // 全員が受信確認
    fmt.Println("Checking broadcast reception:")
    if frame := alice.ReceiveFrame(); frame != nil {
        fmt.Printf("  Alice received: %s\n", string(frame.Payload))
    }
    if frame := bob.ReceiveFrame(); frame != nil {
        fmt.Printf("  Bob received: %s\n", string(frame.Payload))
    }
    if frame := david.ReceiveFrame(); frame != nil {
        fmt.Printf("  David received: %s\n", string(frame.Payload))
    }
    
    // DavidがCharlie宛に送信(学習済み)
    fmt.Printf("\n=== Test 4: DavidからCharlie宛(学習済み) ===\n")
    message4 := []byte("Charlie, this is David responding!")
    err = david.SendData("Charlie", message4)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
    }
    
    time.Sleep(100 * time.Millisecond)
    received4 := charlie.ReceiveFrame()
    if received4 != nil {
        fmt.Printf("Charlie received: %s\n", string(received4.Payload))
    }
    
    // 最終状態のMACテーブルを表示
    fmt.Printf("\n=== 最終状態のMACテーブル ===\n")
    switch1.PrintMACTable()
    
    // システム停止
    fmt.Printf("\n=== システム終了 ===\n")
    alice.Stop()
    bob.Stop()
    charlie.Stop()
    david.Stop()
    switch1.Stop()
}

3.7 期待される出力例

=== スイッチネットワークシミュレーション ===
Node Alice connected to switch SW1 port 1
Node Bob connected to switch SW1 port 2
Node Charlie connected to switch SW1 port 3
Node David connected to switch SW1 port 4
Switch SW1 started (4 ports)
Node Alice started (MAC: 02:a1:b2:c3:d4:e5)
Node Bob started (MAC: 02:f6:g7:h8:i9:j0)
Node Charlie started (MAC: 02:k1:l2:m3:n4:o5)
Node David started (MAC: 02:p6:q7:r8:s9:t0)

=== 初期状態のMACテーブル ===
=== MAC Address Table: SW1 ===
No MAC addresses learned
===============================

=== Test 1: AliceからBobへユニキャスト ===
Switch SW1: Learned MAC 02:a1:b2:c3:d4:e5 on port 1
Switch SW1: Unknown destination 02:f6:g7:h8:i9:j0, flooding
Switch SW1: Broadcasting frame 12345678
Switch SW1: Frame delivered from port 1 to port 2
Switch SW1: Frame delivered from port 1 to port 3
Switch SW1: Frame delivered from port 1 to port 4
Bob received: Hello Bob, this is Alice!

=== Test 2: BobからAliceへ返信 ===
Switch SW1: Learned MAC 02:f6:g7:h8:i9:j0 on port 2
Switch SW1: Frame delivered from port 2 to port 1
Alice received: Hi Alice, nice to hear from you!

=== Test 3: Charlieからブロードキャスト ===
Switch SW1: Learned MAC 02:k1:l2:m3:n4:o5 on port 3
Switch SW1: Broadcasting frame abcdef12
Switch SW1: Frame delivered from port 3 to port 1
Switch SW1: Frame delivered from port 3 to port 2
Switch SW1: Frame delivered from port 3 to port 4
Checking broadcast reception:
  Alice received: Hello everyone! This is Charlie speaking.
  Bob received: Hello everyone! This is Charlie speaking.
  David received: Hello everyone! This is Charlie speaking.

=== Test 4: DavidからCharlie宛(学習済み) ===
Switch SW1: Learned MAC 02:p6:q7:r8:s9:t0 on port 4
Switch SW1: Frame delivered from port 4 to port 3
Charlie received: Charlie, this is David responding!

=== 最終状態のMACテーブル ===
=== MAC Address Table: SW1 ===
  02:a1:b2:c3:d4:e5 -> Port 1 (Alice)
  02:f6:g7:h8:i9:j0 -> Port 2 (Bob)
  02:k1:l2:m3:n4:o5 -> Port 3 (Charlie)
  02:p6:q7:r8:s9:t0 -> Port 4 (David)
===============================

=== システム終了 ===
Node Alice stopped
=== Network Statistics ===
Duration: 1.234s
Packets Sent: 2
Packets Received: 2
Bytes Sent: 89
Bytes Received: 89
Throughput: 576.45 Kbps
Packet Loss Rate: 0.00%
=========================
Node Bob stopped
Node Charlie stopped
Node David stopped
Switch SW1 stopped
=== MAC Address Table: SW1 ===
  02:a1:b2:c3:d4:e5 -> Port 1 (Alice)
  02:f6:g7:h8:i9:j0 -> Port 2 (Bob)
  02:k1:l2:m3:n4:o5 -> Port 3 (Charlie)
  02:p6:q7:r8:s9:t0 -> Port 4 (David)
===============================
=== Network Statistics ===
Duration: 1.234s
Packets Sent: 4
Packets Received: 7
Bytes Sent: 234
Bytes Received: 678
Throughput: 4.4 Mbps
Packet Loss Rate: 0.00%
=========================

3.8 重要な概念の解説

3.8.1 MACアドレス学習

スイッチは受信フレームの送信元MACアドレスを自動的に学習します:

  1. 初回通信時: MACアドレステーブルは空
  2. フレーム受信時: 送信元MACアドレスを受信ポートと関連付けて記録
  3. 転送決定時: 宛先MACアドレスがテーブルにあれば該当ポートのみに送信

3.8.2 フラッディング(未学習アドレス処理)

宛先MACアドレスがテーブルに未登録の場合:

  • 全ポートに転送:送信元ポート以外の全てのアクティブポートに送信
  • 学習機会の提供:宛先ノードからの応答でMACアドレスを学習
  • 実際のスイッチ動作:初期状態や新規参加ノードで発生

3.8.3 ブロードキャスト通信

MACアドレス ff:ff:ff:ff:ff:ff への送信:

  • 全ノードが受信:ネットワーク内の全デバイスに配信
  • ARP、DHCPで使用:アドレス解決、IP自動設定で必須
  • ブロードキャストストーム注意:ループがあると無限に転送され続ける

3.8.4 並行処理とスレッドセーフティ

// MACテーブルへの安全なアクセス
sw.mu.Lock()         // 書き込み時はロック
sw.MACTable[mac] = port
sw.mu.Unlock()

sw.mu.RLock()        // 読み込み時は読み込み専用ロック
port := sw.MACTable[mac]
sw.mu.RUnlock()

複数のノードが同時にフレームを送信してもデータ競合が発生しません。

3.9 実行方法

# プロジェクトディレクトリで実行
go run .

3.10 練習問題

3.10.1 基本課題

  1. 5ノード接続: 5つ目のノード(Eve)を追加し、全ノードでの通信をテストしてください。

  2. MACアドレス衝突: 同じMACアドレスを持つ2つのノードを作成し、スイッチの動作を観察してください。

  3. 統計情報拡張: 各ポートごとの送受信フレーム数を記録する機能を追加してください。

3.10.2 応用課題

  1. VLAN実装: 異なるVLANに属するノード間での通信を制限する機能を実装してください。

  2. MACアドレステーブルのエージング: 一定時間使用されていないMACアドレスエントリを自動削除する機能を追加してください。

  3. ポートミラーリング: 特定ポートの全トラフィックを監視ポートにコピーする機能を実装してください。

3.10.3 サンプル解答(5ノード接続)

// main.goに追加
eve := NewNode("Eve")
eve.ConnectToSwitch(switch1)
eve.Start()

// EveからAlice宛にメッセージ
message5 := []byte("Hello Alice, this is Eve!")
err = eve.SendData("Alice", message5)
if err != nil {
    fmt.Printf("Error: %v\n", err)
}

3.11 実装のポイントと最適化

3.11.1 メモリ効率

// フレームクローン時のメモリ使用量に注意
func (frame *EthernetFrame) Clone() *EthernetFrame {
    newFrame := *frame                    // 構造体コピー
    newFrame.Payload = make([]byte, len(frame.Payload)) // ペイロードは新規作成
    copy(newFrame.Payload, frame.Payload) // データコピー
    return &newFrame
}

3.11.2 パフォーマンス考慮

  • チャネルバッファサイズ: ノードの受信キューを適切なサイズに設定
  • タイムアウト設定: ネットワーク遅延を考慮した配送タイムアウト
  • 並行処理: 複数フレームの同時処理でスループット向上

3.12 現実のネットワークとの対応

実装要素 現実の対応
MACアドレス イーサネットNICの物理アドレス
EthernetFrame 802.3イーサネットフレーム
Switch レイヤー2スイッチ(Catalyst等)
MACテーブル CAM(Content Addressable Memory)
フラッディング 未知ユニキャストフレーム処理
ブロードキャスト 同一VLAN内での一斉配信

3.13 次章への準備

第4章では、MACアドレス学習の詳細ループ回避機能を実装します:

  • スパニングツリープロトコル(STP): ループ検出と回避
  • BPDU(Bridge Protocol Data Unit): スイッチ間通信
  • ブロードキャストストーム: 無限ループの危険性
  • ポート状態管理: Blocking、Learning、Forwarding状態