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アドレスを自動的に学習します:
- 初回通信時: MACアドレステーブルは空
- フレーム受信時: 送信元MACアドレスを受信ポートと関連付けて記録
- 転送決定時: 宛先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 基本課題
-
5ノード接続: 5つ目のノード(Eve)を追加し、全ノードでの通信をテストしてください。
-
MACアドレス衝突: 同じMACアドレスを持つ2つのノードを作成し、スイッチの動作を観察してください。
-
統計情報拡張: 各ポートごとの送受信フレーム数を記録する機能を追加してください。
3.10.2 応用課題
-
VLAN実装: 異なるVLANに属するノード間での通信を制限する機能を実装してください。
-
MACアドレステーブルのエージング: 一定時間使用されていないMACアドレスエントリを自動削除する機能を追加してください。
-
ポートミラーリング: 特定ポートの全トラフィックを監視ポートにコピーする機能を実装してください。
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状態