Loading content...
Not all network traffic is equal. A video conference call demands consistent low latency—a 200ms delay ruins the conversation. A file backup can tolerate seconds of delay without impact. A stock trading system requires microsecond precision. Traditional networks handle this through static QoS configurations: mark packets, configure queues, hope the static mappings match actual traffic.
SDN transforms QoS from static configuration to dynamic management. With visibility into application flows and programmable control over queuing and scheduling, SDN can provide precise quality guarantees that adapt in real-time. When a video call starts, the network allocates priority. When it ends, those resources return to the general pool. When network conditions change, QoS policies react automatically.
This dynamic QoS capability proves essential in modern networks where traffic patterns shift constantly, applications have diverse requirements, and static configurations cannot anticipate every scenario. SDN makes the network aware of application needs and capable of responding intelligently.
By the end of this page, you will understand SDN QoS management including traffic classification, priority queuing with OpenFlow, rate limiting with meters, bandwidth guarantees, application-aware QoS, and integration with real-time application requirements.
Quality of Service encompasses multiple dimensions of network performance, each controllable through SDN mechanisms.
1. Bandwidth:
2. Latency:
3. Jitter:
4. Packet Loss:
OpenFlow Queue Support:
OpenFlow switches can direct traffic to specific queues:
Flow Rule:
Match: ip_dscp=46 (EF - Expedited Forwarding)
Actions:
- Set-Queue(queue_id=0) # High priority queue
- Output(port=1)
Queue Configuration:
Queues are configured on switches (often via OF-Config or OVSDB):
Queue 0 (Premium):
- Min rate: 1 Gbps
- Max rate: 10 Gbps
- Priority: Strict
Queue 1 (Standard):
- Min rate: 100 Mbps
- Max rate: 5 Gbps
- Priority: Weighted (60%)
Queue 2 (Best Effort):
- Min rate: 0
- Max rate: available
- Priority: Weighted (40%)
OpenFlow Meters:
Meters provide rate limiting and traffic policing:
Meter 1:
Bands:
- Type: DROP
Rate: 1000000 kbps (1 Gbps)
Burst: 100000 kb
Flow Rule:
Match: ip_src=10.1.1.0/24
Actions:
- Meter(1) # Apply rate limit
- Output(port=1)
Queues provide prioritization—which traffic goes first when there's contention. Meters provide rate limiting—enforcing maximum rates regardless of contention. Effective QoS typically uses both: meters to police aggregate rates, queues to prioritize within those limits.
QoS begins with classification—identifying which traffic belongs to which service class.
1. Header-Based Classification:
Classify based on packet headers (OpenFlow match fields):
VoIP Traffic:
Match: ip_proto=UDP, udp_dst=5060 (SIP)
Match: ip_proto=UDP, udp_dst=16384-32767 (RTP)
Video Streaming:
Match: tcp_dst=443, ip_dst=video.example.com
Database Sync:
Match: ip_src=10.2.2.0/24, tcp_dst=5432
2. DSCP-Based Classification:
Applications or edge devices mark packets with DiffServ Code Points:
DSCP 46 (EF) → Voice
DSCP 34 (AF41) → Video
DSCP 26 (AF31) → Critical Data
DSCP 0 (BE) → Best Effort
SDN rules map DSCP to queues:
Flow Rule:
Match: ip_dscp=46
Actions: Set-Queue(0), Output(NORMAL)
3. Application-Aware Classification:
Controller identifies applications through:
Dynamic Classification:
Traditional QoS classifies based on static rules. SDN enables:
Scenario: Zoom meeting starts
1. Zoom client initiates connection
2. Application proxy informs controller: "User Alice starting video call"
3. Controller installs high-priority rules for Alice's flow
4. Voice/video packets get premium treatment
5. Meeting ends → Controller removes priority rules
6. Resources available for other users
Per-User/Per-Application Policies:
Policy: "Executive users get premium for video calls"
1. User authenticates → controller learns user role
2. User starts video → controller detects application
3. Executive + video → apply premium QoS
4. Regular user + video → standard QoS
Same application, different treatment based on context.
| Method | Classification Speed | Accuracy | SDN Implementation |
|---|---|---|---|
| Port/Protocol | Line rate | Low (many apps use 443) | OpenFlow match |
| DSCP marking | Line rate | Medium (trust boundary issues) | OpenFlow match |
| IP addresses | Line rate | Medium (changes for cloud) | OpenFlow match |
| Deep Packet Inspection | May require sampling | High | External DPI + controller |
| Application metadata | Event-driven | High | Integration with app layer |
| Behavioral heuristics | Requires analysis | Medium-High | Controller ML/analytics |
With TLS everywhere, deep packet inspection becomes difficult. SDN QoS increasingly relies on: traffic metadata (timing patterns, flow sizes), integration with application stacks (apps report their needs), endpoint cooperation (clients mark their own traffic), and machine learning on encrypted traffic patterns.
Queuing determines the order in which packets are transmitted when output capacity is constrained.
1. Strict Priority:
Highest priority queue always served first:
Queue 0 (Strict): Always sent first
Queue 1: Only served when Queue 0 empty
Queue 2: Only served when Queues 0,1 empty
Risk: Lower priority queues may starve if high priority traffic is constant.
2. Weighted Fair Queuing (WFQ):
Queues share bandwidth according to weights:
Queue 0: Weight 50 → Gets 50% of bandwidth
Queue 1: Weight 30 → Gets 30% of bandwidth
Queue 2: Weight 20 → Gets 20% of bandwidth
Benefit: All queues get minimum guaranteed share.
3. Hybrid (Priority + WFQ):
Combine strict priority for critical traffic with weighted sharing for others:
Queue 0: Strict priority (voice—small, latency-critical)
Queue 1-3: Weighted (video, data, bulk)
Voice always goes first (but limited bandwidth)
Remaining bandwidth shared by weight among others
Queue Configuration via OVSDB:
Open vSwitch Database Protocol configures queues:
{
"Queue": {
"uuid": "queue0",
"other_config": {
"min-rate": "1000000000",
"max-rate": "10000000000",
"priority": "0"
}
},
"QoS": {
"type": "linux-htb",
"queues": {
"0": {"queue": "queue0"},
"1": {"queue": "queue1"},
"2": {"queue": "queue2"}
}
}
}
Flow Rules Using Queues:
# Voice traffic to priority queue
ovs-ofctl add-flow br0 \
"ip,nw_proto=17,tp_dst=5060,actions=set_queue:0,output:1"
# Video traffic to standard queue
ovs-ofctl add-flow br0 \
"ip,nw_proto=6,tp_dst=443,nw_dst=video.cdn.com,actions=set_queue:1,output:1"
# Default to best effort
ovs-ofctl add-flow br0 \
"ip,actions=set_queue:2,output:1"
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
"""SDN QoS Management ApplicationImplements dynamic traffic classification and queue assignment""" from dataclasses import dataclassfrom typing import Dict, List, Optional, Setfrom enum import Enum class TrafficClass(Enum): VOICE = "voice" # Highest priority, strict VIDEO = "video" # High priority, guaranteed CRITICAL_DATA = "critical" # Business critical STANDARD = "standard" # Normal traffic BULK = "bulk" # Background, best effort @dataclassclass QueueConfig: queue_id: int min_rate_bps: int max_rate_bps: int priority: int # 0 = highest scheduling: str # "strict" or "weighted" weight: int = 0 # For weighted scheduling @dataclassclass QoSPolicy: name: str traffic_class: TrafficClass match_criteria: Dict # OpenFlow match fields dscp_marking: Optional[int] = None class SDNQoSManager: """ SDN Quality of Service management application. Provides dynamic traffic classification and queue assignment. """ def __init__(self, controller, ovsdb_client): self.controller = controller self.ovsdb = ovsdb_client # Queue configurations per traffic class self.queue_configs = { TrafficClass.VOICE: QueueConfig( queue_id=0, min_rate_bps=10_000_000, # 10 Mbps guaranteed max_rate_bps=100_000_000, # 100 Mbps max priority=0, scheduling="strict" ), TrafficClass.VIDEO: QueueConfig( queue_id=1, min_rate_bps=500_000_000, # 500 Mbps guaranteed max_rate_bps=5_000_000_000, # 5 Gbps max priority=1, scheduling="weighted", weight=40 ), TrafficClass.CRITICAL_DATA: QueueConfig( queue_id=2, min_rate_bps=200_000_000, # 200 Mbps guaranteed max_rate_bps=2_000_000_000, # 2 Gbps max priority=2, scheduling="weighted", weight=30 ), TrafficClass.STANDARD: QueueConfig( queue_id=3, min_rate_bps=100_000_000, # 100 Mbps guaranteed max_rate_bps=1_000_000_000, # 1 Gbps max priority=3, scheduling="weighted", weight=20 ), TrafficClass.BULK: QueueConfig( queue_id=4, min_rate_bps=0, # No guarantee max_rate_bps=1_000_000_000, # 1 Gbps max priority=4, scheduling="weighted", weight=10 ), } self.policies: List[QoSPolicy] = [] self.active_sessions: Dict[str, TrafficClass] = {} def initialize_queues(self, switch_id: str): """Configure queues on a switch via OVSDB.""" for traffic_class, config in self.queue_configs.items(): self.ovsdb.create_queue( switch_id=switch_id, queue_id=config.queue_id, min_rate=config.min_rate_bps, max_rate=config.max_rate_bps, priority=config.priority ) # Create QoS configuration linking queues self.ovsdb.create_qos( switch_id=switch_id, qos_type="linux-htb", # Hierarchical Token Bucket queues=[c.queue_id for c in self.queue_configs.values()] ) def add_policy(self, policy: QoSPolicy): """Add QoS classification policy.""" self.policies.append(policy) self._install_policy_rules(policy) def _install_policy_rules(self, policy: QoSPolicy): """Install OpenFlow rules for a QoS policy.""" queue_config = self.queue_configs[policy.traffic_class] for switch_id in self.controller.get_all_switches(): actions = [] # Optionally mark DSCP if policy.dscp_marking is not None: actions.append({ "type": "SET_FIELD", "field": "ip_dscp", "value": policy.dscp_marking }) # Assign to queue actions.append({ "type": "SET_QUEUE", "queue_id": queue_config.queue_id }) # Forward normally actions.append({ "type": "OUTPUT", "port": "NORMAL" }) self.controller.install_flow( switch_id=switch_id, priority=1000, match=policy.match_criteria, actions=actions ) def on_session_start( self, session_id: str, user_id: str, application: str, flow_match: Dict ): """ Handle application session start. Install appropriate QoS for the session. """ # Determine traffic class based on application and user traffic_class = self._classify_session(user_id, application) self.active_sessions[session_id] = traffic_class # Install session-specific QoS rules queue_config = self.queue_configs[traffic_class] for switch_id in self.controller.get_all_switches(): self.controller.install_flow( switch_id=switch_id, priority=2000, # Higher than policy rules match=flow_match, actions=[ {"type": "SET_QUEUE", "queue_id": queue_config.queue_id}, {"type": "OUTPUT", "port": "NORMAL"} ], cookie=hash(session_id) ) print(f"Session {session_id}: {application} → {traffic_class.value}") def on_session_end(self, session_id: str): """Handle application session end. Release QoS resources.""" if session_id in self.active_sessions: del self.active_sessions[session_id] # Remove session-specific rules for switch_id in self.controller.get_all_switches(): self.controller.delete_flows( switch_id=switch_id, cookie=hash(session_id) ) def _classify_session(self, user_id: str, application: str) -> TrafficClass: """Classify session based on user and application.""" # Application-based classification app_classes = { "zoom": TrafficClass.VIDEO, "teams": TrafficClass.VIDEO, "webex": TrafficClass.VIDEO, "voip": TrafficClass.VOICE, "softphone": TrafficClass.VOICE, "database": TrafficClass.CRITICAL_DATA, "backup": TrafficClass.BULK, "sync": TrafficClass.BULK, } base_class = app_classes.get(application.lower(), TrafficClass.STANDARD) # User-based upgrade (executives get priority) if self._is_executive(user_id): if base_class == TrafficClass.STANDARD: return TrafficClass.CRITICAL_DATA return base_class def _is_executive(self, user_id: str) -> bool: """Check if user has executive privileges.""" # Integration with identity management return False # Placeholder def get_queue_statistics(self, switch_id: str) -> Dict: """Get queue statistics for monitoring.""" stats = {} for traffic_class, config in self.queue_configs.items(): queue_stats = self.controller.get_queue_stats( switch_id, config.queue_id ) stats[traffic_class.value] = { "tx_bytes": queue_stats.get("tx_bytes", 0), "tx_packets": queue_stats.get("tx_packets", 0), "tx_errors": queue_stats.get("tx_errors", 0), "queue_depth": queue_stats.get("queue_depth", 0) } return stats def adapt_to_congestion(self, switch_id: str, port: int): """ Dynamically adjust QoS parameters based on congestion. Called when congestion detected on a port. """ # Get current queue depths stats = self.get_queue_statistics(switch_id) # If voice queue building up, reduce video allocation voice_depth = stats[TrafficClass.VOICE.value]["queue_depth"] if voice_depth > 100: # Threshold in packets # Temporarily reduce video max rate video_config = self.queue_configs[TrafficClass.VIDEO] self.ovsdb.update_queue( switch_id=switch_id, queue_id=video_config.queue_id, max_rate=video_config.max_rate_bps // 2 ) print(f"Congestion: Reduced video bandwidth on {switch_id}") # Default policies for common traffic typesDEFAULT_POLICIES = [ QoSPolicy( name="voip-sip", traffic_class=TrafficClass.VOICE, match_criteria={"ip_proto": 17, "udp_dst": 5060}, dscp_marking=46 # EF ), QoSPolicy( name="voip-rtp", traffic_class=TrafficClass.VOICE, match_criteria={"ip_proto": 17, "udp_dst": "16384-32767"}, dscp_marking=46 # EF ), QoSPolicy( name="video-conferencing", traffic_class=TrafficClass.VIDEO, match_criteria={"ip_proto": 17, "udp_dst": "19302-19309"}, # WebRTC dscp_marking=34 # AF41 ), QoSPolicy( name="database-traffic", traffic_class=TrafficClass.CRITICAL_DATA, match_criteria={"tcp_dst": 5432}, # PostgreSQL dscp_marking=26 # AF31 ),]Monitor queue depths to detect congestion before it causes drops. Growing queues indicate demand exceeding capacity. SDN controllers can react by load balancing traffic to alternate paths, temporarily reducing low-priority allocations, or alerting operators to capacity issues.
OpenFlow meters provide traffic policing—enforcing rate limits regardless of queue priority.
Meter Structure:
Meter ID: 1
Flags: KBPS (rate in kilobits per second)
Bands:
Band 1:
Type: DROP
Rate: 1000000 kbps (1 Gbps)
Burst Size: 100000 kb
Band Types:
1. Per-User Rate Limiting:
Scenario: Limit each user to 100 Mbps
Meter per user:
Meter 1000 + user_id
Rate: 100 Mbps
Action: DROP
Flow Rules:
Match: ip_src=user_ip
Actions: Meter(user_meter), Output
2. Per-Tenant Bandwidth Guarantee:
Tenant A: Guaranteed 10 Gbps, burst to 15 Gbps
Meter configuration:
Band 1: DSCP_REMARK at 10 Gbps (lower priority above)
Band 2: DROP at 15 Gbps (hard limit)
Traffic up to 10G: Full priority
Traffic 10-15G: Lower priority, may be dropped under congestion
Traffic above 15G: Dropped immediately
3. DDoS Mitigation:
Detect attack → Install rate-limiting meter
Meter:
Rate: 10 Mbps (throttle attack traffic)
Band: DROP
Apply to attacking source IP
Legitimate traffic continues normally
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
"""SDN Rate Limiting with OpenFlow MetersDemonstrates per-user, per-tenant, and security metering""" from dataclasses import dataclassfrom typing import Dict, List, Optionalfrom enum import Enum class MeterBandType(Enum): DROP = "drop" DSCP_REMARK = "dscp_remark" @dataclassclass MeterBand: band_type: MeterBandType rate_kbps: int burst_kbps: int dscp_value: Optional[int] = None # For DSCP_REMARK @dataclassclass Meter: meter_id: int bands: List[MeterBand] flags: List[str] # KBPS, PKTPS, etc. class MeteringManager: """ SDN metering application for rate limiting. """ def __init__(self, controller): self.controller = controller self.user_meters: Dict[str, int] = {} # user_id -> meter_id self.next_meter_id = 1000 def create_per_user_limit( self, user_id: str, rate_mbps: int, burst_mbps: int = None ) -> int: """Create rate limit for a specific user.""" burst_mbps = burst_mbps or rate_mbps * 2 meter_id = self._allocate_meter_id() meter = Meter( meter_id=meter_id, bands=[ MeterBand( band_type=MeterBandType.DROP, rate_kbps=rate_mbps * 1000, burst_kbps=burst_mbps * 1000 ) ], flags=["KBPS", "BURST"] ) # Install meter on all switches for switch_id in self.controller.get_all_switches(): self.controller.install_meter(switch_id, meter) self.user_meters[user_id] = meter_id return meter_id def apply_user_meter(self, user_id: str, user_ip: str): """Apply user's rate limit to their traffic.""" meter_id = self.user_meters.get(user_id) if not meter_id: return for switch_id in self.controller.get_all_switches(): # Meter outbound traffic self.controller.install_flow( switch_id=switch_id, priority=500, match={"ip_src": user_ip}, actions=[ {"type": "METER", "meter_id": meter_id}, {"type": "OUTPUT", "port": "NORMAL"} ] ) # Meter inbound traffic self.controller.install_flow( switch_id=switch_id, priority=500, match={"ip_dst": user_ip}, actions=[ {"type": "METER", "meter_id": meter_id}, {"type": "OUTPUT", "port": "NORMAL"} ] ) def create_tiered_tenant_limit( self, tenant_id: str, guaranteed_mbps: int, max_mbps: int ) -> int: """ Create two-tier limit for tenant. - Up to guaranteed: Full priority - Above guaranteed: Reduced priority (may be dropped under congestion) - Above max: Hard drop """ meter_id = self._allocate_meter_id() meter = Meter( meter_id=meter_id, bands=[ # Soft limit: remarking above guaranteed MeterBand( band_type=MeterBandType.DSCP_REMARK, rate_kbps=guaranteed_mbps * 1000, burst_kbps=guaranteed_mbps * 1500, dscp_value=0 # Best effort above limit ), # Hard limit: drop above maximum MeterBand( band_type=MeterBandType.DROP, rate_kbps=max_mbps * 1000, burst_kbps=max_mbps * 1200 ) ], flags=["KBPS", "BURST"] ) for switch_id in self.controller.get_all_switches(): self.controller.install_meter(switch_id, meter) return meter_id def create_attack_mitigation_meter( self, source_ip: str, limit_mbps: int = 10 ) -> int: """ Create severe rate limit for attack mitigation. """ meter_id = self._allocate_meter_id() # Harsh rate limit for attack traffic meter = Meter( meter_id=meter_id, bands=[ MeterBand( band_type=MeterBandType.DROP, rate_kbps=limit_mbps * 1000, burst_kbps=limit_mbps * 1000 # No burst ) ], flags=["KBPS"] ) # Install meter and apply to source for switch_id in self.controller.get_all_switches(): self.controller.install_meter(switch_id, meter) self.controller.install_flow( switch_id=switch_id, priority=65000, # High priority to catch first match={"ip_src": source_ip}, actions=[ {"type": "METER", "meter_id": meter_id}, {"type": "OUTPUT", "port": "NORMAL"} ] ) return meter_id def get_meter_statistics(self, switch_id: str, meter_id: int) -> Dict: """Get statistics for a meter.""" stats = self.controller.get_meter_stats(switch_id, meter_id) return { "meter_id": meter_id, "flow_count": stats.get("flow_count", 0), "packet_in_count": stats.get("packet_in_count", 0), "byte_in_count": stats.get("byte_in_count", 0), "bands": [ { "packet_count": band.get("packet_band_count", 0), "byte_count": band.get("byte_band_count", 0) } for band in stats.get("band_stats", []) ] } def update_user_limit(self, user_id: str, new_rate_mbps: int): """Dynamically update user's rate limit.""" meter_id = self.user_meters.get(user_id) if not meter_id: return new_bands = [ MeterBand( band_type=MeterBandType.DROP, rate_kbps=new_rate_mbps * 1000, burst_kbps=new_rate_mbps * 2000 ) ] for switch_id in self.controller.get_all_switches(): self.controller.modify_meter( switch_id=switch_id, meter_id=meter_id, bands=new_bands ) def _allocate_meter_id(self) -> int: meter_id = self.next_meter_id self.next_meter_id += 1 return meter_idSwitches have finite meter table capacity. Per-flow metering at scale may exhaust meter entries. For large deployments, aggregate meters (per-subnet, per-tenant) or hierarchical metering reduces table pressure while maintaining reasonable control granularity.
True QoS requires consistent treatment across the entire path, not just at individual switches.
Challenge:
Traffic classified as premium at ingress must receive premium treatment at every hop:
[Edge Switch] → [Aggregation] → [Core] → [WAN] → [Dest Edge]
↓ ↓ ↓ ↓ ↓
Queue 0 Queue 0 Queue 0 Premium Queue 0
Inconsistent treatment at any hop undermines end-to-end QoS.
SDN Solution:
Controller enforces consistent policy network-wide:
Modern SDN integrates with applications for QoS:
Intent-Based Approach:
Application declares:
{
"service": "video-conference",
"participants": ["user1", "user2"],
"requirements": {
"latency": "<100ms",
"jitter": "<20ms",
"bandwidth": "5 Mbps per participant"
},
"duration": "60 minutes"
}
Controller:
1. Computes paths meeting latency requirement
2. Reserves bandwidth on selected paths
3. Installs priority queue rules
4. Monitors SLA throughout session
5. Releases resources when session ends
Inter-domain QoS challenges:
SDN approaches:
SDN enables closed-loop SLA enforcement:
1. Application requests QoS (latency < 50ms)
2. Controller selects path and installs rules
3. Monitoring detects latency at 60ms (SLA breach)
4. Controller re-evaluates:
- Alternative path available with 30ms latency?
- Can other traffic be de-prioritized?
- Should application be notified of degradation?
5. Controller takes corrective action
6. Latency returns to 45ms (SLA restored)
This proactive management maintains QoS despite changing conditions.
True QoS requires admission control—rejecting new requests when capacity is exhausted rather than degrading existing services. SDN can implement admission control by tracking reserved bandwidth per path/link and refusing new high-priority reservations when thresholds are reached.
SDN transforms QoS from static configuration to dynamic, application-aware service management. Let's consolidate the key concepts:
Module Complete:
With QoS management covered, you have now completed the SDN Applications module. You've learned how SDN enables:
These applications demonstrate SDN's transformative potential—turning networks from static infrastructure into programmable, intelligent systems that adapt to application needs and changing conditions.
You now understand how SDN enables sophisticated QoS management through programmable classification, queuing, and metering. This capability—differentiating service quality dynamically based on application requirements—represents a fundamental advancement over static QoS configurations. Combined with the other SDN applications covered in this module, you have a comprehensive understanding of how SDN transforms network operations.