⚠️ Modernization Notice This post was written in 2018 against Cisco IOS XE / ISG on the ASR 1000. Behavior around parameterized QoS, CoA, and policy-map chaining has changed across releases. Treat the configs and outputs here as a record of what I tested at the time, and validate against the current IOS XE configuration guides before deploying.

Overview

On the ASR 1000 (and IOS XE in general) there are broadly two ways to apply QoS to subscriber sessions: per-session and per-flow. They cannot be applied at the same time on the same session, so the first decision is which model fits your service.

Per-session policing applies to the whole session — every flow is treated the same. It can be installed at session start from the Access-Accept, or applied/changed later via CoA. The Cisco-Session-Info AVP carries the rates, for example:

1
Cisco-Session-Info=QU;512000;256000;D;512000;256000

Look up the Cisco RADIUS CoA Interface Guide — Appendix A documents this attribute in detail. Because it is a single, general policer for the whole session, you cannot prioritize particular traffic types (VoIP, etc.) with it.

Per-flow QoS is applied through a regular MQC policy-map, which lets you classify and treat traffic differently. There are three ways to deploy it:

  1. Define and apply the QoS policy from the CLI.
  2. Define the QoS policy in the CLI, but apply it from RADIUS.
  3. Define and apply the QoS policy entirely from RADIUS — this is what Cisco calls Parameterized QoS.

Two important caveats before the examples:

  • You cannot apply per-session and per-flow QoS simultaneously.
  • The QoS order of operation is different on the ASR 1000 / IOS XE. Read Hierarchical Color-Aware Policing carefully before relying on input policing or marking.

Further reading:


Base ISG Configuration

The following is the relevant (non-exhaustive) ISG/PPPoE configuration that the examples build on:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
aaa server radius dynamic-author
 client X.X.X.X server-key X
 port 1645
 auth-type any
 ignore session-key
!
policy-map type service INTERNET
 service local
 ip unnumbered Loopback10
!
policy-map type control PPPOE_TEST
 class type control INTERNET event service-start
  1 service-policy type service unapply name UNAUTH_PPP_SERVICE
  2 service-policy type service identifier service-name
 !
 class type control INTERNET event service-stop
  1 service-policy type service unapply identifier service-name
  2 service-policy type service name UNAUTH_PPP_SERVICE
 !
 class type control always event session-start
  1 service-policy type service name PBHK
  2 authenticate aaa list pppoe
!
aaa policy interface-config allow-subinterface
!
bba-group pppoe yapa
 virtual-template 3
!
interface Virtual-Template3
 mtu 1492
 ip unnumbered Loopback10
 ip verify unicast reverse-path
 no ip split-horizon
 ip tcp adjust-mss 1452
 no logging event link-status
 load-interval 30
 no peer default ip address
 ppp authentication pap pppoe
 ppp authorization pppoe
 ppp accounting pppoe
 service-policy type control PPPOE_TEST
 end

Per-Session Example

The subscriber’s rates are carried in the AAA profile:

1
2
3
testdsl3@testadsl Cisco-Service-Info += QU;512000;256000;D;512000;256000
testdsl3@testadsl cisco-avpair      += ip:addr-pool=xdslpool
testdsl3@testadsl Cisco-Account-Info += AINTERNET

After the session comes up, the per-user policer reflects those rates:

1
2
3
4
5
6
show subscriber session username testdsl3@testadsl
……….
Policing:
 Class-id  Dir  Avg. Rate  Normal Burst  Excess Burst  Source
 0         In   512000     256000        0             Peruser
 1         Out  512000     256000        0             Peruser

Changing the rate with CoA

Push a new rate to the live session with a Change-Filter-Request:

1
2
3
radpwtst -s 192.168.1.1 -secret XXXX -noauth -noacct -code Change-Filter-Request -trace 4 \
  Cisco-Account-Info="S1.1.1.1:vrf-id=INTERNET" \
  Cisco-Service-Info="QU;100000;256000;D;100000;256000"

The policer updates in place:

1
2
3
4
5
6
show subscriber session username testdsl3@testadsl
……….
Policing:
 Class-id  Dir  Avg. Rate  Normal Burst  Excess Burst  Source
 0         In   100000     256000        0             Peruser
 1         Out  100000     256000        0             Peruser

Per-Flow QoS

Method 2.1 — Define and apply from the CLI

The simplest case: attach a policy-map directly under the virtual-template so it applies to every session. This is rarely useful on its own, because you still need a way to vary the parameters per customer (via RADIUS or similar). I’m not including a configuration for it since it is trivial.

Method 2.2 — Define in the CLI, apply from RADIUS

Here the class-maps and ACLs live on the ISG, but RADIUS selects or modifies the policy per customer.

Example 1 — policy-map name per customer. RADIUS sends a policy-map name in the Access-Accept. This means every customer or speed tier needs its own policy-map configured with that customer’s up/down speeds and service classes. It works, but the configuration grows with every speed group. (No config shown — it is straightforward.)

Example 2 — one generic policy-map, modified via RADIUS. A generic parent/child policy-map is defined once in the CLI and then changed dynamically with RADIUS attributes (add/modify classes).

MQC CLI configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
access-list 102 remark BB_QoS_UP
access-list 102 permit ip any host X.X.X.X
access-list 101 remark BB_QoS_DOWN
access-list 101 permit ip host X.X.X.X any

class-map match-all BB_QoS_VOIP_DOWN
 match access-group 101
class-map match-all BB_QoS_VOIP_UP
 match access-group 102

policy-map BB_GENERIC_PARENT_DOWN
 class class-default
  shape average 200000
  service-policy BB_QoS_VOIP_DOWN
policy-map BB_QoS_VOIP_DOWN
 class BB_QoS_VOIP_DOWN
  priority percent 30

policy-map BB_GENERIC_PARENT_UP
 class class-default
  police cir 5120000 bc 160000 conform-action transmit exceed-action drop
  service-policy BB_QoS_VOIP_UP
policy-map BB_QoS_VOIP_UP
 class BB_QoS_VOIP_UP
  set mpls experimental imposition 0
 class class-default
  set mpls experimental imposition 0

Subscriber profile:

1
2
3
4
testdsl3@testadsl cisco-avpair += ip:sub-qos-policy-in=BB_GENERIC_PARENT_UP
testdsl3@testadsl cisco-avpair += ip:sub-qos-policy-out=BB_GENERIC_PARENT_DOWN
testdsl3@testadsl cisco-avpair += ip:addr-pool=xdslpool
testdsl3@testadsl Cisco-Account-Info += AINTERNET

To change the parent shaper/policer on a live session via CoA, chain the class-default of the parent policy. Watch the doubling: ISG applies twice the value sent in the CoA message because of subscriber service police percent-factor 100 (for policing) or subscriber service shaper percent-factor 100 (for shaping). You can also change other class values the same way.

1
2
3
4
5
6
7
radpwtst -s X.X.X.X -secret X -noauth -noacct -code Change-Filter-Request -trace 4 \
  Cisco-Account-Info="S1.1.1.1:vrf-id=INTERNET" \
  cisco-avpair="ip:qos-policy-in=add-class(sub,(class-default),police(220000))"

radpwtst -s X.X.X.X -secret X -noauth -noacct -code Change-Filter-Request -trace 4 \
  Cisco-Account-Info="S1.1.1.1:vrf-id=INTERNET" \
  cisco-avpair="ip:qos-policy-out=add-class(sub,(class-default),shape(150400))"

The resulting policy shows the doubled values — a requested police(220000) lands as cir 440000, and the input service-policy name encodes the chained value:

1
2
3
4
Service-policy input: BB_GENERIC_PARENT_UP$class-default;police=440000,0,0$BB_QoS_VOIP_UP$class-default
  Class-map: class-default (match-any)
    police:
        cir 440000 bps, bc 13750 bytes, be 13750 bytes

The same parameters can also be set in the initial Access-Accept instead of via CoA — a general policy-map is configured on the ISG and its values are set from the subscriber’s bandwidth profile (a form of dynamic template):

1
2
3
4
5
6
testdsl3@testadsl cisco-avpair += ip:addr-pool=xdslpool
testdsl3@testadsl cisco-avpair += ip:qos-policy-out=add-class(sub,(class-default),shape(160000))
testdsl3@testadsl Cisco-Account-Info += AINTERNET
testdsl3@testadsl cisco-avpair += ip:sub-qos-policy-in=BB_GENERIC_PARENT_UP
testdsl3@testadsl cisco-avpair += ip:qos-policy-in=add-class(sub,(class-default),police(220000))
testdsl3@testadsl cisco-avpair += ip:sub-qos-policy-out=BB_GENERIC_PARENT_DOWN

Full show policy-map interface Vi2.1 for both the CoA case (session 887) and the initial Access-Accept case (session 888): show-policy-map-perflow-example2.md.

Example 3 — generic parent with an empty child class. Real deployments mix many up/down speed combinations, and only some customers buy voice service. Defining a separate policy-map per combination bloats the configuration. In Example 2 the child policy already carried a default voice class; here I instead apply an empty child policy, which is what lets RADIUS add the customer’s classes onto the parent policy-map later.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
policy-map BB_GENERIC_CHILD_DOWN
! Empty child policy is required to add additional classes via radius
policy-map BB_GENERIC_PARENT_DOWN
 class class-default
  shape average 20000000
  service-policy BB_GENERIC_CHILD_DOWN

policy-map BB_GENERIC_CHILD_UP
! Empty child policy is required to add additional classes via radius
policy-map BB_GENERIC_PARENT_UP
 class class-default
  police cir 512000 bc 16000 conform-action set-mpls-exp-topmost-transmit 0 exceed-action drop violate-action drop
  service-policy BB_GENERIC_CHILD_UP

A word on physical vs. service speed. This generic-shaper approach regulates the customer to their purchased service speed, which is fine on its own. But if you also rely on the QoS hierarchy for congestion management, the parent shaper must not exceed the customer’s physical access speed. If the service speed is higher than the line sync rate, congestion happens at the physical layer before the QoS mechanism ever sees the configured rate as “congested,” and prioritization breaks. (See Broadband Forum TR-147, sections 6.1–6.2.)

For example, a customer buys 10 Mb down / 1 Mb up, but the DSLAM syncs at 4 Mb. Regulating them to 10 Mb is harmless, but QoS expects the 10 Mb shaper to be the congestion point — which never happens, because the real bottleneck is the 4 Mb line.

The fix is to make RADIUS aware of the physical sync rate. The DSLAM can insert PPPoE intermediate-agent tags (dsl-sync-rate), and RADIUS can use those when setting the customer’s QoS parameters and for reporting. ANCP is another way to learn the line rate.

Input (CE → PE) classes. Below I add a BB_QoS_VOIP_UP qos-group that is not defined in the CLI-defined BB_GENERIC_PARENT_UP (the class-map itself must still be pre-defined in the CLI). Some QoS actions are not supported on input — for example set-mpls-exp-topmost-transmit. Read Hierarchical Color-Aware Policing for the input-policing limitations.

1
2
3
4
5
6
cisco-avpair = "ip:qos-policy-in=add-class(sub,(class-default,BB_QoS_VOIP_UP),police(128000),set-qos-grp(43))"
! adds the voice class
cisco-avpair = "ip:sub-qos-policy-in=BB_GENERIC_PARENT_UP"
! the default policy-map
cisco-avpair = "ip:qos-policy-in=add-class(sub,(class-default),police(1500000))"
! changes the police bandwidth for the whole session

If you want to change the overall session bandwidth (for fair-usage reasons) without affecting an upper class like voice, add a default class to the child policy at the rate you want:

1
2
cisco-avpair = "ip:qos-policy-in=add-class(sub,(class-default,class-default),police(50000))"
! adds a default class to the child policy-map

Output (PE → CE) classes. Similar to the input policy, but with more classes:

1
2
3
4
5
cisco-avpair = "ip:qos-policy-out=add-class(sub,(class-default,BB_QoS_VOIP_DOWN),police(128000),pri-level(1))"
cisco-avpair = "ip:qos-policy-out=add-class(sub,(class-default,BB_QoS_IPTV_DOWN),police(1000000),pri-level(2))"
cisco-avpair = "ip:qos-policy-out=add-class(sub,(class-default),shape(1500000))"
cisco-avpair = "ip:qos-policy-out=add-class(sub,(class-default,class-default),shape(350000))"
cisco-avpair = "ip:sub-qos-policy-out=BB_GENERIC_PARENT_DOWN"

With this, all customer inbound traffic is policed to 3 Mb, the BB_GENERIC_PARENT_UP voice class gets 256 K, and the default class gets 1 M.

Handling the speed mismatch in RADIUS. I also wrote a small bit of logic on the RADIUS side to solve the physical-speed problem, using the DSLAM’s dsl-sync-rate PPPoE tag:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
bba-group pppoe yapa
 virtual-template 1
 vendor-tag circuit-id service
 vendor-tag remote-id service
 vendor-tag dsl-sync-rate service

policy-map BB_GENERIC_ERROR_DOWN
 class class-default
  shape average 1000000
  service-policy BB_GENERIC_CHILD_DOWN
policy-map BB_GENERIC_ERROR_UP
 class class-default
  police cir 512000 bc 16000 conform-action set-mpls-exp-topmost-transmit 0 exceed-action drop violate-action drop
  service-policy BB_GENERIC_CHILD_UP

RADIUS compares the customer’s physical (sync) speed against their service speed and decides which policy-maps to send:

1
2
3
4
5
if customer service speed < physical speed        -> send BB_GENERIC_ERROR policy maps
if a customer-specific policy-map is defined       -> send it (enterprise customers with dedicated QoS)
if sum of all class speeds < physical speed        -> send BB_GENERIC_ERROR policy maps
if sum of all class speeds < customer speed        -> send BB_GENERIC_ERROR policy maps
else                                               -> send the customer's class and speed parameters

Because the applied policy name shows up in the CDRs, you can also report which customers ended up on the ERROR policy-maps.

Method 2.3 — Define and apply entirely from RADIUS (Parameterized QoS)

This is the method documented in the ASR 9000 configuration guide. On the ASR 1000 the policy is expected to be defined in the CLI (Configuring Parameterized QoS Policy Through RADIUS), so I never had a clean way to try the full ASR 9000 style. I did attempt a minimal version on the ASR 1000 — just a shaper under the default class, built entirely from RADIUS — but the session never came up:

1
2
3
testdsl3@testadsl cisco-avpair += ip:addr-pool=xdslpool
testdsl3@testadsl Cisco-Account-Info += AINTERNET
testdsl3@testadsl cisco-avpair += ip:qos-policy-out=add-class(sub, (class-default), shape(4000))

The authorization fails while building the service-policy:

1
Oct 23 11:52:48 TR: SSS AAA AUTHOR [uid:904]: AAA service-policy config for testdsl3@testadsl had errors

Full SSS PM / AAA AUTHOR debug for this failed attempt: parameterized-qos-from-radius-failure.md.


Reader Tip: FreeRADIUS and the kbps→bps gotcha

A reader, Harald Kapper, later shared a useful FreeRADIUS pointer for per-session custom values on the ASR 1000. With FreeRADIUS you can send a cisco-avpair like the following (and store it in the radreply/groupreply table if you use a database):

1
qos-policy-out=add-class(sub,(class-default), shape(%{request:Actual-Data-Rate-Downstream}000))

Depending on the FreeRADIUS version (v2 vs v3), the back-ticks around the expansion may or may not be needed. The trailing 000 matters: the BRAS receives the data rate in kbps via the DSL line-rate attribute, while QoS on the ASR 1000 expects bps.


Summary

  • The ASR 1000 offers per-session (one policer for the whole session, via Cisco-Service-Info / CoA) and per-flow (MQC policy-map) QoS, and the two cannot be combined on the same session.
  • For real broadband deployments, the practical pattern is one generic parent/child policy-map defined in the CLI, parameterized per customer from RADIUS (Example 2/3), rather than a policy-map per speed tier.
  • Mind the percent-factor 100 doubling when sending rates over CoA.
  • Match the parent shaper to the customer’s physical line rate, not just their service speed — otherwise the QoS hierarchy never sees the real congestion point. Feed the line rate to RADIUS via dsl-sync-rate PPPoE tags or ANCP, and fall back to ERROR policy-maps when the numbers don’t add up.
  • Fully RADIUS-defined parameterized QoS (method 2.3) is an ASR 9000 feature; on the ASR 1000 I could not get it to come up and reverted to CLI-defined, RADIUS-parameterized policies.

References


The content of this post was created by the author; AI was used for editing and restructuring.