A practical guide to limiting bandwidth in QRadar

The bandwidth manager in IBM® QRadar® takes advantage of the hierarchical token bucket (HTB) queuing discipline that is offered within the Linux kernel. For specifics or more advanced usage, there are many pages on the Internet that describe what it is and how it works, including:

Note: This technical blog article is as-is and didn’t go through any extra vetting.

Before you begin, you'll have to tweak the bandwidthManagerCLI.sh file first to change the buildcpath location. To edit the bandwidthManagerCLI.sh file, type edit /opt/qradar/bin/bandwidthManagerCLI.sh. Change the third line in the file to read /opt/qradar/systemd/bin/buildcpath. Save and close the file.

Bandwidth Manager help and usage

The following code snippet lists the usage of the Bandwidth Manager:

[root@m5arch06 ~]# /opt/qradar/bin/bandwidthManagerCLI.sh
usage: BandwidthManagerCLI -a <ACTION>
          -a,--action The desired action. Recognized Actions: help, add_class, add_egress_filter,
                      delete_class, delete_egress_filter
usage: add_class <OPTIONS>
          -i,--hostID    The managed host ID which uniquely identifies the host. You can optionally
                         use '-1' to indicate you wish this to be applied to all hosts in the deployment
          -n,--name      A user-friendly name for the new configuration
          -c,--classID   The desired class ID of the class to be added
          -d,--device    The device name (e.g. eth0) which this configuration will apply to. You can
                         optionally use '*' to indicate you wish to be applied to all devices
          -h,--hostname  The managed host host name. Can be used when you don't provide the host ID
          -k,--kbpsLimit The rate limit to be applied against the class in kilobytes/sec
          -p,--parentID  The parent ID for this class
          -q,--qdiscID   The desired queuing discipline ID of the egress filter to be added
usage: add_egress_filter <OPTIONS>
          -i,--hostID           The managed host ID which uniquely identifies the host. You can
                                optionally use '-1' to indicate you wish this to be applied to all hosts in the deployment
          -n,--name             A user-friendly name for the new configuration
          -P,--protocol         The protocol for the new configuration
          -d,--device           The device name (e.g. eth0) which this configuration will apply to.
                                You can optionally use '*' to indicate you wish to be applied to all devices
          -dc,--dstCIDR         [Optional] The destination CIDR for the egress filter
          -dport,--dstPort      [Optional] The destination port for the egress filter
          -dportm,--dstPortMask [Optional] The destination port mask for the egress filter. If not
                                provided, 0xffff will be used (meaning match only that port)
          -f,--filterID         The desired filter ID to be assigned to the new egress filter
          -fl,--flowID          The filter's target flow ID (which represents the minor ID of a
                                class or qdisc). Traffic matched by this filter with go to that location
          -fp,--filterPriority  The fitler's priority relative to others attached to the same parent
                                (lower number is higher priority)
          -h,--hostname         The managed host host name. Can be used when you don't provide the
                                host ID
          -ma,--matchAll        [Optional] If "true", the filter that is created will match *all*
                                incoming packets at its priority level.
          -p,--parentID         The parent ID for this egress filter
          -q,--qdiscID          The desired queuing discipline ID of the egress filter to be added
          -sc,--sourceCIDR      [Optional] The source CIDR for the egress filter
          -sport,--srcPort      [Optional] The source port for the egress filter
          -sportm,--srcPortMask [Optional] The source port mask for the egress filter. If not
                                provided, 0xffff will be used (meaning match only that port)
usage: delete_class <OPTIONS>
          -i,--hostID   The managed host ID which uniquely identifies the host. You can optionally
                        use '-1' to indicate you wish this to be applied to all hosts in the deployment
          -c,--classID  The class ID to class to delete
          -d,--device   The device name (e.g. eth0) of the configuration to be deleted
          -h,--hostname The managed host host name. Can be used when you don't provide the host ID
          -q,--qdiscID  The queuing discipline ID of the class you wish to delete
usage: delete_egress_filter <OPTIONS>
          -i,--hostID   The managed host ID which uniquely identifies the host. You can optionally
                        use '-1' to indicate you wish this to be applied to all hosts in the deployment
          -d,--device   The device name (e.g. eth0) of the configuration to be deleted
          -f,--filterID The filter ID of the filter to delete
          -h,--hostname The managed host host name. Can be used when you don't provide the host ID
[root@m5arch06 ~]#

Terminology

This queuing discipline or HTB lets you define the properties of the tokens and bucket that are used. (We don't have to worry about this mechanism.)
Classes

Define chunks of bandwidth – minimums and maximums. They are arranged in a tree structure. For instance, you have a 10 Mbit link that is shared between QRadar and other applications. You want to ensure that QRadar never uses more than 5 Mbit/sec, so it doesn’t saturate the link. You define a class for all traffic that is limited to 5 Mbit/sec and then you can further subdivide that so that your PostgreSQL replication doesn’t affect search performance.

Filters

Select the network traffic that is placed into each of these classes. Typically done by IP/protocol/port combinations, the filters are remarkably flexible and allow very fine granularity – for our purposes, we’ll stick with the simple IP/protocol/port combinations.

Cookbook – Examples

All commands are run on the console – no need to run on a managed host. You specify which host to apply the change to in the bandwidthManagerCLI.sh command line.

Example: Slowing down replication to a host (all port 443 traffic – assuming non-encrypted)

In this example, we create a class in which we limit the bandwidth to 10 Kbps. Then, we create filters to map to that class. We want to limit these filters specifically to source port 443 (because it is the console) and to destination IP 192.0.2.0 (the managed host in this case). The host name of the console is m5arch06.
/opt/qradar/bin/bandwidthManagerCLI.sh -a add_class -h m5arch06 -n SlowReplication -c 1 -q 30 -p 0 -k 10 -d eno1
/opt/qradar/bin/bandwidthManagerCLI.sh -a add_egress_filter -h m5arch06 -n MatchHttps -d eno1 -dc 192.0.2.0/32 -f 2 -fl 1 -fp 1 -p 0 -q 30 -sport 443 -P tcp
/opt/qradar/bin/bandwidthManagerCLI.sh -a add_egress_filter -h m5arch06 -n MatchHttps -d eno1 -dc 192.0.2.0/32 -f 3 -fl 3 -fp 1 -p 0 -q 1 -sport 443 -P tcp

To test this, we forced replication on the managed host to request a full database dump and ended up with the following results:
….
  2750K .......... .......... .......... .......... ..........  4% 9.02K 1h45m
  2800K .......... .......... .......... .......... ..........  4% 9.68K 1h45m
  2850K .......... .......... .......... .......... ..........  4% 9.51K 1h45m
  2900K .......... .......... .......... .......... ..........  4% 8.14K 1h45m
  2950K .......... .......... .......... .......... ..........  5% 9.00K 1h45m
  3000K .......... .......... .......... .......... ..........  5% 9.00K 1h45m
  3050K .......... .......... .......... .......... ..........  5% 9.99K 1h44m
…..

Rates are approximately (and not exceeding) 10 Kbyte/second.

To test and see if the throttling is taking place on the console, look at the output from the class that we set up:

[root@m5arch06 ~]# tc -s class ls dev eno1 classid 30:1
class htb 30:1 root prio 1 rate 80000bit ceil 80000bit burst 1600b cburst 1600b
 Sent 8579563 bytes 7315 pkt (dropped 0, overlimits 0 requeues 0)
 rate 0bit 0pps backlog 0b 3p requeues 0
 lended: 4682 borrowed: 0 giants: 0
 tokens: -4730822 ctokens: -4730822

[root@m5arch06 ~]#

To remove the bandwidth rules, type the following commands:

/opt/qradar/bin/bandwidthManagerCLI.sh -a delete_egress_filter -h m5arch06 -d eno1 -f 2
/opt/qradar/bin/bandwidthManagerCLI.sh -a delete_egress_filter -h m5arch06 -d eno1 -f 3
/opt/qradar/bin/bandwidthManagerCLI.sh -a delete_class -h m5arch06 -c 1 -q 30 -d eno1
Note: Without removing the bandwidth rules, these filters and classes will remain persistent across reboots, etc, and QRadar will continue to respect them across patches.

Example: Limiting all bandwidth between the console and a managed host

We will be monitoring the bandwidth on the console and in the following setup:

  • m5arch06 (198.51.100.0) is the console
  • m5arch07 (192.0.2.0) is the managed host
/opt/qradar/bin/bandwidthManagerCLI.sh -a add_class -h m5arch06 -n SlowCommunicationToHost -c 1 -q 30 -p 0 -k 10 -d eno1

Add filters to match the class and queuing discipline by typing the following command:

/opt/qradar/bin/bandwidthManagerCLI.sh -a add_egress_filter -h m5arch06 -n MatchToIP -d eno1 -dc 192.0.2.0/32 -f 2 -fl 1 -fp 1 -p 0 -q 30 -P tcp
/opt/qradar/bin/bandwidthManagerCLI.sh -a add_egress_filter -h m5arch06 -n MatchToIP -d eno1 -dc 192.0.2.0/32 -f 3 -fl 3 -fp 1 -p 0 -q 1 -P tcp

If you want to throttle the bandwidth on the managed host (notice the change to the hostname that these apply to and the destination CIDR), type the following command:

/opt/qradar/bin/bandwidthManagerCLI.sh -a add_class -h m5arch07 -n SlowCommunicationToHost -c 1 -q 30 -p 0 -k 10 -d eno1
Add filters to match the class and queuing discipline by typing the following command:
/opt/qradar/bin/bandwidthManagerCLI.sh -a add_egress_filter -h m5arch07 -n MatchToIP -d eno1 -dc 198.51.100.0/32 -f 2 -fl 1 -fp 1 -p 0 -q 30 -P tcp
/opt/qradar/bin/bandwidthManagerCLI.sh -a add_egress_filter -h m5arch07 -n MatchToIP -d eno1 -dc 198.51.100.0/32 -f 3 -fl 3 -fp 1 -p 0 -q 1 -P tcp

Finally, to delete from both console and managed host, type the following command:

/opt/qradar/bin/bandwidthManagerCLI.sh -a delete_egress_filter -h m5arch06 -d eno1 -f 2
/opt/qradar/bin/bandwidthManagerCLI.sh -a delete_egress_filter -h m5arch06 -d eno1 -f 3
/opt/qradar/bin/bandwidthManagerCLI.sh -a delete_class -h m5arch06 -c 1 -q 30 -d eno1

/opt/qradar/bin/bandwidthManagerCLI.sh -a delete_egress_filter -h m5arch07 -d eno1 -f 2
/opt/qradar/bin/bandwidthManagerCLI.sh -a delete_egress_filter -h m5arch07 -d eno1 -f 3
/opt/qradar/bin/bandwidthManagerCLI.sh -a delete_class -h m5arch07 -c 1 -q 30 -d eno1

Example: Adding the changes via SQL and poking Mbeans in order for the changes to take effect (a bit quicker and more scriptable)

This example uses the same configuration as in the first example, but we insert into PostgreSQL instead of waiting for the bandwidth manager to initialize frameworks and do all that work for us.

There are two tables relevant for the bandwidth manager configuration:
  • The bandwidth_configuration table contains all of the class / bandwidth information:
    qradar=# \d bandwidth_configuration
    Table "public.bandwidth_configuration"
         Column     |          Type          |                             Modifiers                             
    ----------------+------------------------+------------------------------------------------------------
     sequenceid     | bigint                 | not null default nextval('bandwidth_configuration_seq'::regclass)
     appname        | character varying(100) | not null
     mh_id          | bigint                 | not null
     device         | character varying(100) | not null
     qdisc_id       | integer                | not null
     class_id       | integer                | not null
     parent_id      | integer                | not null
     bandwidth_kbps | bigint                 | not null
     created_by     | character varying(100) |
    
  • The bandwidth_egress_filter contains all of the filters:
    qradar=# \d bandwidth_egress_filter
    Table "public.bandwidth_egress_filter"
         Column      |          Type          |                             Modifiers                             
    -----------------+------------------------+-----------------------------------------------------------
     sequenceid      | bigint                 | not null default nextval('bandwidth_egress_filter_seq'::regclass)
     appname         | character varying(100) | not null
     mh_id           | bigint                 | not null
     device          | character varying(100) | not null
     qdisc_id        | integer                | not null
     parent_id       | integer                | not null
     filter_id       | integer                | not null
     flow_id         | integer                | not null
     filter_priority | integer                | not null
     src_port        | integer                |
     src_port_mask   | integer                |
     src_cidr        | character varying(45)  |
     dst_port        | integer                |
     dst_port_mask   | integer                |
     dst_cidr        | character varying(45)  |
     match_all       | boolean                | not null default false
     created_by      | character varying(100) |
     protocol        | character varying(4)   |
    

Using the configuration from example 1, we end up with SQL that looks like the following snippet:

INSERT INTO bandwidth_configuration (appname, mh_id, device, qdisc_id, class_id, parent_id, bandwidth_kbps, created_by) VALUES ('SlowReplication', 53, 'eno1', 30, 1, 0, 10, 'SQL_Insert');
INSERT INTO bandwidth_egress_filter (appname, mh_id, device, qdisc_id, parent_id, filter_id, flow_id, filter_priority, src_port, src_port_mask, dst_cidr, created_by, protocol) VALUES ('MatchHttps', 53, 'eno1', 30, 0, 2, 1, 1, 443, 65535, '192.0.2.0/32', 'SQL_Insert', 'ip');
INSERT INTO bandwidth_egress_filter (appname, mh_id, device, qdisc_id, parent_id, filter_id, flow_id, filter_priority, src_port, src_port_mask, dst_cidr, created_by, protocol) VALUES ('MatchHttps', 53, 'eno1', 1, 0, 3, 3, 1, 443, 65535, '192.0.2.0/32', 'SQL_Insert', 'ip');

Now, we poke the Mbean and refresh its configuration:

[root@m5arch06 ~]# /opt/qradar/support/jmx.sh 7778 'com.q1labs.hostcontext.bm:application=hostcontext.hostcontext,type=BandwidthManager' reloadConfig

Invoking operation: reloadConfig ( )
Result: true

[root@m5arch06 ~]#

And voila! The class is back:

[root@m5arch06 ~]# tc class ls dev eno1
class prio 1:1 parent 1: leaf 10:
class prio 1:2 parent 1: leaf 20:
class prio 1:3 parent 1: leaf 30:
class prio 1:4 parent 1: leaf 40:
class prio 1:5 parent 1: leaf 50:
class prio 1:6 parent 1: leaf 60:
class prio 1:7 parent 1: leaf 70:
class htb 30:1 root prio 1 rate 80000bit ceil 80000bit burst 1600b cburst 1600b
[root@m5arch06 ~]#

Then we delete the entries in the database and reload again:

[root@m5arch06 ~]# psql -U qradar -c "DELETE from bandwidth_configuration"
DELETE 1
[root@m5arch06 ~]# psql -U qradar -c "DELETE from bandwidth_egress_filter"
DELETE 2
[root@m5arch06 ~]# /opt/qradar/support/jmx.sh 7778 'com.q1labs.hostcontext.bm:application=hostcontext.hostcontext,type=BandwidthManager' reloadConfig

Invoking operation: reloadConfig ( )
Result: true

[root@m5arch06 ~]#

And voila! The class disappears again:

[root@m5arch06 ~]# tc class ls dev eno1
class prio 1:1 parent 1: leaf 10:
class prio 1:2 parent 1: leaf 20:
class prio 1:3 parent 1: leaf 30:
class prio 1:4 parent 1: leaf 40:
class prio 1:5 parent 1: leaf 50:
class prio 1:6 parent 1: leaf 60:
class prio 1:7 parent 1: leaf 70:
[root@m5arch06 ~]#