Segment Routing คืออะไรและทำไมต้องมี Testing Strategy
Segment Routing (SR) เป็น Source Routing Architecture ที่ออกแบบมาเพื่อลดความซับซ้อนของ Network โดยกำจัด Protocol หลายตัวที่เคยใช้ใน MPLS Network แบบเดิมเช่น LDP และ RSVP-TE แทนที่ด้วยการใช้ Segment Identifier (SID) ที่แนบไปกับ packet header เพื่อบอกเส้นทางที่ packet ต้องผ่าน
Segment Routing มี 2 Data Plane หลักคือ SR-MPLS ที่ใช้ MPLS Label Stack เป็น Segment Identifier เหมาะสำหรับ network ที่มี MPLS อยู่แล้ว และ SRv6 ที่ใช้ IPv6 Extension Header เป็น Segment Identifier เหมาะสำหรับ network ที่ต้องการ programmability สูงและรองรับ Network Function Chaining
การทดสอบ Segment Routing ต้องครอบคลุมทั้ง Control Plane (IS-IS/OSPF SR Extensions, BGP-LS) และ Data Plane (MPLS forwarding, SRv6 encapsulation) รวมถึง Failure Scenarios เช่น Link Failure, Node Failure และ TI-LFA (Topology-Independent Loop-Free Alternate) convergence time
Testing Strategy ที่ดีสำหรับ Segment Routing ต้องแบ่งเป็น 4 ระดับคือ Unit Test สำหรับ configuration syntax, Functional Test สำหรับ protocol behavior, Integration Test สำหรับ end-to-end path validation และ Performance Test สำหรับ convergence time และ throughput
ออกแบบ Test Plan สำหรับ Segment Routing
Test Plan สำหรับ Segment Routing ควรครอบคลุม test cases หลักดังนี้
# test_plan_segment_routing.yaml
test_plan:
name: "Segment Routing Validation"
version: "2.0"
topology: "spine-leaf-sr"
test_suites:
- name: "Control Plane Tests"
tests:
- id: CP-001
name: "IS-IS SR adjacency formation"
description: "ตรวจสอบว่า IS-IS adjacency ขึ้นครบทุก link"
expected: "All adjacencies in UP state"
priority: critical
- id: CP-002
name: "Prefix SID allocation"
description: "ตรวจสอบว่า Prefix SID ถูกต้องและไม่ซ้ำกัน"
expected: "Unique SID per prefix in SRGB range"
priority: critical
- id: CP-003
name: "Adjacency SID assignment"
description: "ตรวจสอบ Adj-SID บนทุก interface"
expected: "Adj-SID assigned per adjacency"
priority: high
- id: CP-004
name: "BGP-LS SR information"
description: "ตรวจสอบว่า BGP-LS export SR TLVs ถูกต้อง"
expected: "SR TLVs visible in SDN controller"
priority: high
- name: "Data Plane Tests"
tests:
- id: DP-001
name: "SR-MPLS label forwarding"
description: "ส่ง traffic ผ่าน SR-MPLS label stack"
expected: "Traffic forwarded correctly via label stack"
priority: critical
- id: DP-002
name: "SRv6 encapsulation"
description: "ตรวจสอบ SRv6 SRH encap/decap"
expected: "Correct SRH with segment list"
priority: critical
- id: DP-003
name: "ECMP load balancing"
description: "ตรวจสอบ ECMP across equal-cost SR paths"
expected: "Traffic distributed evenly"
priority: high
- name: "Failure Recovery Tests"
tests:
- id: FR-001
name: "TI-LFA link failure"
description: "จำลอง link failure และวัด convergence time"
expected: "Convergence < 50ms with TI-LFA"
priority: critical
- id: FR-002
name: "Node failure recovery"
description: "จำลอง node failure และตรวจสอบ traffic reroute"
expected: "Traffic rerouted via backup path"
priority: critical
ตั้งค่า Lab Environment ด้วย Containerlab
Containerlab เป็นเครื่องมือสร้าง Network Lab ด้วย Container ที่รองรับ Network OS หลายยี่ห้อเช่น Nokia SR Linux, Arista cEOS, Cisco XRd และ FRRouting
# ติดตั้ง Containerlab
sudo bash -c "$(curl -sL https://get.containerlab.dev)"
# ตรวจสอบเวอร์ชัน
containerlab version
# containerlab version 0.54.2
# sr-lab.clab.yml — Topology สำหรับทดสอบ Segment Routing
name: sr-testing-lab
topology:
nodes:
spine1:
kind: nokia_srlinux
image: ghcr.io/nokia/srlinux:24.3.2
startup-config: configs/spine1.cfg
spine2:
kind: nokia_srlinux
image: ghcr.io/nokia/srlinux:24.3.2
startup-config: configs/spine2.cfg
leaf1:
kind: nokia_srlinux
image: ghcr.io/nokia/srlinux:24.3.2
startup-config: configs/leaf1.cfg
leaf2:
kind: nokia_srlinux
image: ghcr.io/nokia/srlinux:24.3.2
startup-config: configs/leaf2.cfg
leaf3:
kind: nokia_srlinux
image: ghcr.io/nokia/srlinux:24.3.2
startup-config: configs/leaf3.cfg
client1:
kind: linux
image: alpine:latest
client2:
kind: linux
image: alpine:latest
links:
- endpoints: ["spine1:e1-1", "leaf1:e1-49"]
- endpoints: ["spine1:e1-2", "leaf2:e1-49"]
- endpoints: ["spine1:e1-3", "leaf3:e1-49"]
- endpoints: ["spine2:e1-1", "leaf1:e1-50"]
- endpoints: ["spine2:e1-2", "leaf2:e1-50"]
- endpoints: ["spine2:e1-3", "leaf3:e1-50"]
- endpoints: ["leaf1:e1-1", "client1:eth1"]
- endpoints: ["leaf3:e1-1", "client2:eth1"]
# สร้าง Lab
sudo containerlab deploy --topo sr-lab.clab.yml
# ดูสถานะ
sudo containerlab inspect --topo sr-lab.clab.yml
# +---+------------------+---------+------+-------+
# | # | Name | Kind | IPv4 | IPv6 |
# +---+------------------+---------+------+-------+
# | 1 | clab-sr-spine1 | srlinux | ... | ... |
# | 2 | clab-sr-spine2 | srlinux | ... | ... |
# | 3 | clab-sr-leaf1 | srlinux | ... | ... |
# +---+------------------+---------+------+-------+
# เข้าถึง node
ssh admin@clab-sr-testing-lab-spine1
ตัวอย่าง SR Configuration สำหรับ Nokia SR Linux
# configs/spine1.cfg — Nokia SR Linux SR-MPLS Configuration
set / network-instance default
set / network-instance default router-id 10.0.0.1
set / network-instance default protocols isis instance main
set / network-instance default protocols isis instance main net [ 49.0001.0100.0000.0001.00 ]
set / network-instance default protocols isis instance main level-capability L2
set / network-instance default protocols isis instance main interface ethernet-1/1.0
set / network-instance default protocols isis instance main interface ethernet-1/2.0
set / network-instance default protocols isis instance main interface ethernet-1/3.0
set / network-instance default protocols isis instance main interface system0.0 passive true
# Segment Routing Configuration
set / network-instance default segment-routing
set / network-instance default segment-routing mpls
set / network-instance default segment-routing mpls global-block label-range start 16000 end 23999
set / network-instance default protocols isis instance main segment-routing mpls
set / network-instance default protocols isis instance main segment-routing prefix-sid-map
set / network-instance default protocols isis instance main interface system0.0 segment-routing mpls ipv4-node-sid index 1
# TI-LFA Configuration
set / network-instance default protocols isis instance main interface ethernet-1/1.0 ti-lfa enable true
set / network-instance default protocols isis instance main interface ethernet-1/2.0 ti-lfa enable true
set / network-instance default protocols isis instance main interface ethernet-1/3.0 ti-lfa enable true
เขียน Automated Test ด้วย PyATS และ Robot Framework
PyATS เป็น Network Testing Framework จาก Cisco ที่รองรับ multi-vendor ใช้สำหรับเขียน automated test สำหรับ network devices
# ติดตั้ง PyATS
pip install pyats[full] genie
# testbed.yaml — PyATS Testbed Definition
testbed:
name: sr-testing-lab
credentials:
default:
username: admin
password: NokiaSrl1!
devices:
spine1:
os: linux
type: srlinux
connections:
defaults:
class: unicon.Unicon
cli:
protocol: ssh
ip: clab-sr-testing-lab-spine1
port: 22
leaf1:
os: linux
type: srlinux
connections:
defaults:
class: unicon.Unicon
cli:
protocol: ssh
ip: clab-sr-testing-lab-leaf1
port: 22
leaf3:
os: linux
type: srlinux
connections:
defaults:
class: unicon.Unicon
cli:
protocol: ssh
ip: clab-sr-testing-lab-leaf3
port: 22
---
# test_sr_control_plane.py — PyATS Test Script
from pyats import aetest
from pyats.topology import loader
import re
import json
class CommonSetup(aetest.CommonSetup):
@aetest.subsection
def connect_devices(self, testbed):
for device in testbed.devices.values():
device.connect(log_stdout=False)
class TestISISAdjacency(aetest.Testcase):
@aetest.test
def verify_isis_adjacencies(self, testbed):
for name, device in testbed.devices.items():
output = device.execute("info from state /network-instance default protocols isis adjacency")
adj_count = output.count("state up")
self.passed(f"{name}: {adj_count} adjacencies UP") if adj_count > 0 \
else self.failed(f"{name}: No ISIS adjacencies UP")
class TestPrefixSID(aetest.Testcase):
@aetest.test
def verify_prefix_sids(self, testbed):
sids_seen = set()
for name, device in testbed.devices.items():
output = device.execute("info from state /network-instance default segment-routing mpls")
sid_matches = re.findall(r'ipv4-node-sid\s+index\s+(\d+)', output)
for sid in sid_matches:
if sid in sids_seen:
self.failed(f"Duplicate SID {sid} on {name}")
sids_seen.add(sid)
self.passed(f"All SIDs unique: {sids_seen}")
class TestTILFA(aetest.Testcase):
@aetest.test
def verify_tilfa_backup_paths(self, testbed):
spine1 = testbed.devices['spine1']
output = spine1.execute("info from state /network-instance default protocols isis interface * ti-lfa")
if "backup-available" not in output and "protect" not in output:
self.failed("TI-LFA backup paths not available")
self.passed("TI-LFA backup paths configured")
class CommonCleanup(aetest.CommonCleanup):
@aetest.subsection
def disconnect_devices(self, testbed):
for device in testbed.devices.values():
device.disconnect()
# รัน test
# pyats run job test_sr_control_plane.py --testbed-file testbed.yaml
ทดสอบ SR-MPLS และ SRv6 Data Plane
ใช้ scapy และ iperf3 สำหรับทดสอบ data plane forwarding ของ Segment Routing
#!/usr/bin/env python3
# test_sr_dataplane.py — ทดสอบ SR-MPLS Data Plane ด้วย Scapy
from scapy.all import *
from scapy.contrib.mpls import MPLS
import subprocess
import time
import sys
def test_sr_mpls_forwarding(src_ip, dst_ip, label_stack):
"""ทดสอบ SR-MPLS forwarding ด้วย label stack"""
# สร้าง packet พร้อม MPLS label stack
pkt = Ether(dst="ff:ff:ff:ff:ff:ff")
for i, label in enumerate(label_stack):
s_bit = 1 if i == len(label_stack) - 1 else 0
pkt = pkt / MPLS(label=label, s=s_bit, ttl=64)
pkt = pkt / IP(src=src_ip, dst=dst_ip) / ICMP()
print(f"Sending SR-MPLS packet with label stack: {label_stack}")
print(f" Source: {src_ip} -> Destination: {dst_ip}")
# ส่ง packet และรอ response
ans, unans = srp(pkt, iface="eth1", timeout=5, verbose=0)
if ans:
print(f" Result: PASS - Received {len(ans)} responses")
return True
else:
print(f" Result: FAIL - No response received")
return False
def test_srv6_encapsulation(src_ip, dst_ip, segment_list):
"""ทดสอบ SRv6 encapsulation"""
pkt = (
Ether(dst="ff:ff:ff:ff:ff:ff") /
IPv6(src=src_ip, dst=segment_list[0]) /
IPv6ExtHdrSegmentRouting(
segleft=len(segment_list) - 1,
addresses=segment_list[::-1]
) /
IP(src="10.1.1.1", dst="10.3.1.1") /
ICMP()
)
print(f"Sending SRv6 packet with segment list: {segment_list}")
ans, unans = srp(pkt, iface="eth1", timeout=5, verbose=0)
return len(ans) > 0
def test_ecmp_distribution(dst_ip, num_flows=100):
"""ทดสอบ ECMP distribution ด้วย different source ports"""
results = {}
for i in range(num_flows):
src_port = 10000 + i
pkt = IP(dst=dst_ip) / UDP(sport=src_port, dport=5001)
# ใช้ traceroute เพื่อดู path
ans, _ = traceroute(dst_ip, maxttl=5, verbose=0)
# เก็บ next-hop
if ans:
next_hop = ans[0][1].src
results[next_hop] = results.get(next_hop, 0) + 1
print(f"ECMP Distribution ({num_flows} flows):")
for hop, count in sorted(results.items()):
pct = count / num_flows * 100
print(f" {hop}: {count} flows ({pct:.1f}%)")
def test_convergence_time(interface_to_fail):
"""วัด convergence time เมื่อ link failure"""
print(f"Testing convergence time for {interface_to_fail} failure")
# เริ่ม continuous ping
ping_proc = subprocess.Popen(
["ping", "-i", "0.01", "-c", "1000", "10.3.1.1"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
# รอ 2 วินาทีแล้ว shut interface
time.sleep(2)
subprocess.run(["ip", "link", "set", interface_to_fail, "down"])
# รอ ping เสร็จ
stdout, _ = ping_proc.communicate()
output = stdout.decode()
# วิเคราะห์ผล
lost_match = re.search(r'(\d+)% packet loss', output)
if lost_match:
loss_pct = float(lost_match.group(1))
# convergence time = จำนวน lost packets * interval (10ms)
lost_packets = int(re.search(r'(\d+) received', output).group(1))
convergence_ms = (1000 - lost_packets) * 10
print(f" Packet loss: {loss_pct}%")
print(f" Estimated convergence: {convergence_ms}ms")
return convergence_ms < 50 # TI-LFA target
# คืนค่า interface
subprocess.run(["ip", "link", "set", interface_to_fail, "up"])
if __name__ == "__main__":
# SR-MPLS Test: label stack [16001, 16003] = spine1 -> leaf3
test_sr_mpls_forwarding("10.1.1.1", "10.3.1.1", [16001, 16003])
# ECMP Test
test_ecmp_distribution("10.3.1.1", num_flows=50)
Integration Testing กับ SDN Controller
ทดสอบการทำงานร่วมกันระหว่าง Segment Routing กับ SDN Controller ผ่าน BGP-LS และ PCEP
#!/usr/bin/env python3
# test_sdn_integration.py — ทดสอบ SDN Controller Integration
import requests
import json
import time
SDN_URL = "http://sdn-controller:8181"
AUTH = ("admin", "admin")
def test_bgp_ls_topology():
"""ตรวจสอบว่า SDN Controller ได้รับ topology จาก BGP-LS"""
r = requests.get(
f"{SDN_URL}/restconf/operational/network-topology:network-topology",
auth=AUTH,
headers={"Accept": "application/json"}
)
topology = r.json()
nodes = topology.get("network-topology", {}).get("topology", [{}])[0].get("node", [])
links = topology.get("network-topology", {}).get("topology", [{}])[0].get("link", [])
print(f"Topology received via BGP-LS:")
print(f" Nodes: {len(nodes)}")
print(f" Links: {len(links)}")
# ตรวจสอบว่ามี SR SID information
for node in nodes:
sr_info = node.get("sr-node-attributes", {})
srgb = sr_info.get("srgb", {})
if srgb:
print(f" {node['node-id']}: SRGB={srgb}")
assert len(nodes) >= 5, f"Expected >= 5 nodes, got {len(nodes)}"
assert len(links) >= 12, f"Expected >= 12 links, got {len(links)}"
return True
def test_sr_policy_provisioning():
"""ทดสอบการ provision SR Policy ผ่าน SDN Controller"""
policy = {
"sr-policy": {
"name": "test-policy-1",
"headend": "10.0.0.1",
"color": 100,
"endpoint": "10.0.0.5",
"candidate-paths": {
"preference": 100,
"segment-lists": [
{
"weight": 1,
"segments": [
{"sid": 16002, "type": "node-sid"},
{"sid": 16005, "type": "node-sid"}
]
}
]
}
}
}
r = requests.post(
f"{SDN_URL}/restconf/config/sr-policy:sr-policies",
auth=AUTH,
headers={"Content-Type": "application/json"},
json=policy
)
print(f"SR Policy provisioning: {r.status_code}")
assert r.status_code in [200, 201, 204], f"Failed: {r.text}"
# ตรวจสอบว่า policy ถูกติดตั้งบน headend
time.sleep(5)
r = requests.get(
f"{SDN_URL}/restconf/operational/sr-policy:sr-policies",
auth=AUTH,
headers={"Accept": "application/json"}
)
policies = r.json().get("sr-policies", {}).get("sr-policy", [])
active = [p for p in policies if p.get("state") == "active"]
print(f"Active SR Policies: {len(active)}")
return len(active) > 0
def test_sr_te_path_computation():
"""ทดสอบ Path Computation สำหรับ SR-TE"""
request_body = {
"path-computation-request": {
"source": "10.0.0.1",
"destination": "10.0.0.5",
"constraints": {
"bandwidth": "1000000000",
"latency-max": "10",
"exclude-nodes": ["10.0.0.3"],
"metric-type": "te-metric"
}
}
}
r = requests.post(
f"{SDN_URL}/restconf/operations/path-computation:compute-path",
auth=AUTH,
headers={"Content-Type": "application/json"},
json=request_body
)
result = r.json()
path = result.get("path-computation-response", {}).get("computed-path", {})
sid_list = path.get("segment-list", [])
print(f"Computed SR-TE path:")
print(f" SID list: {[s['sid'] for s in sid_list]}")
print(f" Metric: {path.get('metric', 'N/A')}")
print(f" Latency: {path.get('latency', 'N/A')}ms")
return len(sid_list) > 0
if __name__ == "__main__":
results = {}
results["BGP-LS Topology"] = test_bgp_ls_topology()
results["SR Policy Provisioning"] = test_sr_policy_provisioning()
results["SR-TE Path Computation"] = test_sr_te_path_computation()
print("\n=== Test Summary ===")
for test, passed in results.items():
status = "PASS" if passed else "FAIL"
print(f" [{status}] {test}")
FAQ คำถามที่พบบ่อย
Q: SR-MPLS กับ SRv6 ควรเลือกใช้ตัวไหน?
A: SR-MPLS เหมาะสำหรับ network ที่มี MPLS infrastructure อยู่แล้วและต้องการ migration ที่ง่าย ส่วน SRv6 เหมาะกับ network ใหม่ที่ต้องการ programmability สูงและรองรับ Network Function Chaining แต่ SRv6 มี overhead สูงกว่าเนื่องจากใช้ IPv6 header ที่ใหญ่กว่า MPLS label
Q: TI-LFA convergence time ควรเป็นเท่าไหร่?
A: Target ที่แนะนำคือต่ำกว่า 50ms สำหรับ link failure ซึ่ง TI-LFA สามารถทำได้เพราะ backup path ถูก pre-computed ไว้แล้วใน FIB เมื่อ failure เกิดขึ้น router เพียงแค่สลับไปใช้ backup path โดยไม่ต้องรอ IGP convergence
Q: Containerlab กับ GNS3/EVE-NG ต่างกันอย่างไร?
A: Containerlab ใช้ container-based approach ทำให้เบาและเร็วกว่ามาก สามารถ define topology เป็น YAML file ที่ version control ได้ เหมาะสำหรับ CI/CD pipeline ส่วน GNS3/EVE-NG เหมาะกับการสร้าง lab ที่ต้องการ GUI และรองรับ hardware-based images
Q: จะทดสอบ Segment Routing บน Production Network ได้อย่างไร?
A: ใช้ OAM (Operations Administration and Maintenance) tools ที่ built-in มากับ SR เช่น SR-MPLS OAM ที่รองรับ ping และ traceroute ผ่าน label stack, SRv6 OAM ที่ใช้ ICMPv6 และ Performance Measurement ที่วัด delay/loss ได้โดยไม่กระทบ traffic จริง ไม่ควรรัน active scan บน production โดยตรง
Q: PyATS รองรับ vendor ไหนบ้าง?
A: PyATS รองรับ Cisco IOS/IOS-XE/IOS-XR/NX-OS เป็นหลัก แต่ผ่าน Unicon library สามารถเชื่อมต่อกับ device อื่นๆได้เช่น Nokia SR Linux, Arista EOS, Juniper Junos และ Linux-based devices สำหรับ vendor ที่ไม่รองรับ native สามารถใช้ NETCONF/RESTCONF หรือ SSH command parsing แทน
