Add documentation updates for the unified config

This commit is contained in:
Herbert Wolverson 2024-02-04 20:55:31 -06:00
parent 699e265850
commit 1ee3543eb1
7 changed files with 102 additions and 457 deletions

5
docs/ChangeNotes/v1.5.md Normal file
View File

@ -0,0 +1,5 @@
# LibreQoS v1.4 to v1.5 Change Summary
## Unified Configuration
All configuration has been moved into `/etc/lqos.conf`.

View File

@ -15,27 +15,23 @@ Now edit the file to match your setup with
sudo nano /etc/lqos.conf
```
Change `enp1s0f1` and `enp1s0f2` to match your network interfaces. It doesn't matter which one is which. Notice, it's paring the interfaces, so when you first enter enps0f<ins>**1**</ins> in the first line, the `redirect_to` parameter is enp1s0f<ins>**2**</ins> (replacing with your actual interface names).
- First Line: `name = "enp1s0f1", redirect_to = "enp1s0f2"`
- Second Line: `name = "enp1s0f2", redirect_to = "enp1s0f1"`
Change `eth0` and `eth1` to match your network interfaces. The interface facing the Internet should be specified in `to_internet`. The interfacing facing your ISP network should be in `to_network`.
Then, if using Bifrost/XDP set `use_xdp_bridge = true` under that same `[bridge]` section.
## Configure ispConfig.py
For example:
Copy ispConfig.example.py to ispConfig.py and edit as needed
```shell
cd /opt/libreqos/src/
cp ispConfig.example.py ispConfig.py
nano ispConfig.py
```toml
[bridge]
use_xdp_bridge = true
to_internet = "eth0"
to_network = "eth1"
```
- Set upstreamBandwidthCapacityDownloadMbps and upstreamBandwidthCapacityUploadMbps to match the bandwidth in Mbps of your network's upstream / WAN internet connection. The same can be done for generatedPNDownloadMbps and generatedPNUploadMbps.
- Set interfaceA to the interface facing your core router (or bridged internal network if your network is bridged)
- Set interfaceB to the interface facing your edge router
- Set ```enableActualShellCommands = True``` to allow the program to actually run the commands.
## Configure Your Network Settings
- Set `uplink_bandwidth_mbps` and `downlink_bandwidth_mbps` to match the bandwidth in Mbps of your network's upstream / WAN internet connection. The same can be done for `generated_pn_download_mbps` and `generated_pn_upload_mbps`.
- Set ```dry_run = false``` to allow the program to actually run the commands.
## Network.json

View File

@ -2,7 +2,7 @@
## UISP Integration
First, set the relevant parameters for UISP (uispAuthToken, UISPbaseURL, etc.) in ispConfig.py.
First, set the relevant parameters for UISP (uispAuthToken, UISPbaseURL, etc.) in `/etc/lqos.conf`.
To test the UISP Integration, use
@ -14,11 +14,11 @@ On the first successful run, it will create a network.json and ShapedDevices.csv
If a network.json file exists, it will not be overwritten.
You can modify the network.json file to more accurately reflect bandwidth limits.
ShapedDevices.csv will be overwritten every time the UISP integration is run.
You have the option to run integrationUISP.py automatically on boot and every 10 minutes, which is recommended. This can be enabled by setting ```automaticImportUISP = True``` in ispConfig.py
You have the option to run integrationUISP.py automatically on boot and every 10 minutes, which is recommended. This can be enabled by setting ```enable_uisp = true``` in `/etc/lqos.conf`
## Powercode Integration
First, set the relevant parameters for Sonar (powercode_api_key, powercode_api_url, etc.) in ispConfig.py.
First, set the relevant parameters for Powercode (powercode_api_key, powercode_api_url, etc.) in `/etc/lqos.conf`.
To test the Powercode Integration, use
@ -29,11 +29,11 @@ python3 integrationPowercode.py
On the first successful run, it will create a ShapedDevices.csv file.
You can modify the network.json file manually to reflect Site/AP bandwidth limits.
ShapedDevices.csv will be overwritten every time the Powercode integration is run.
You have the option to run integrationPowercode.py automatically on boot and every 10 minutes, which is recommended. This can be enabled by setting ```automaticImportPowercode = True``` in ispConfig.py
You have the option to run integrationPowercode.py automatically on boot and every 10 minutes, which is recommended. This can be enabled by setting ```enable_powercode = true``` in `/etc/lqos.conf`
## Sonar Integration
First, set the relevant parameters for Sonar (sonar_api_key, sonar_api_url, etc.) in ispConfig.py.
First, set the relevant parameters for Sonar (sonar_api_key, sonar_api_url, etc.) in `/etc/lqos.conf`.
To test the Sonar Integration, use
@ -45,11 +45,11 @@ On the first successful run, it will create a ShapedDevices.csv file.
If a network.json file exists, it will not be overwritten.
You can modify the network.json file to more accurately reflect bandwidth limits.
ShapedDevices.csv will be overwritten every time the Sonar integration is run.
You have the option to run integrationSonar.py automatically on boot and every 10 minutes, which is recommended. This can be enabled by setting ```automaticImportSonar = True``` in ispConfig.py
You have the option to run integrationSonar.py automatically on boot and every 10 minutes, which is recommended. This can be enabled by setting ```enable_sonar = true``` in `/etc/lqos.conf`
## Splynx Integration
First, set the relevant parameters for Splynx (splynx_api_key, splynx_api_secret, etc.) in ispConfig.py.
First, set the relevant parameters for Splynx (splynx_api_key, splynx_api_secret, etc.) in `/etc/lqos.conf`.
The Splynx Integration uses Basic authentication. For using this type of authentication, please make sure you enable [Unsecure access](https://splynx.docs.apiary.io/#introduction/authentication) in your Splynx API key settings. Also the Splynx API key should be granted access to the necessary permissions.
@ -62,4 +62,4 @@ python3 integrationSplynx.py
On the first successful run, it will create a ShapedDevices.csv file.
You can manually create your network.json file to more accurately reflect bandwidth limits.
ShapedDevices.csv will be overwritten every time the Splynx integration is run.
You have the option to run integrationSplynx.py automatically on boot and every 10 minutes, which is recommended. This can be enabled by setting ```automaticImportSplynx = True``` in ispConfig.py
You have the option to run integrationSplynx.py automatically on boot and every 10 minutes, which is recommended. This can be enabled by setting ```enable_spylnx = true``` in `/etc/lqos.conf`.

View File

@ -1,180 +0,0 @@
# 'fq_codel' or 'cake diffserv4'
# 'cake diffserv4' is recommended
# sqm = 'fq_codel'
sqm = 'cake diffserv4'
# Used to passively monitor the network for before / after comparisons. Leave as False to
# ensure actual shaping. After changing this value, run "sudo systemctl restart LibreQoS.service"
monitorOnlyMode = False
# How many Mbps are available to the edge of this network.
# Any circuits, generated nodes, or network.json nodes, will all be capped at no more than this amount.
upstreamBandwidthCapacityDownloadMbps = 1000
upstreamBandwidthCapacityUploadMbps = 1000
# Consider these values your bandwidth bottleneck per-CPU-core.
# This will depend on your CPU's single-thread passmark score.
# Devices in ShapedDevices.csv without a defined ParentNode (such as if you have a flat {} network)
# will be placed under one of these generated parent node, evenly spread out across CPU cores.
# This defines the bandwidth limit for each of those generated parent nodes.
# If you are not sure what values to use, simply use the same values as upstreamBandwidthCapacityDownloadMbps and upstreamBandwidthCapacityUploadMbps
generatedPNDownloadMbps = 1000
generatedPNUploadMbps = 1000
# Interface connected to core router
interfaceA = 'eth1'
# Interface connected to edge router
interfaceB = 'eth2'
# Queue refresh scheduler (lqos_scheduler). Minutes between reloads.
queueRefreshIntervalMins = 30
# WORK IN PROGRESS. Note that interfaceA determines the "stick" interface
# I could only get scanning to work if I issued ethtool -K enp1s0f1 rxvlan off
OnAStick = False
# VLAN facing the core router
StickVlanA = 0
# VLAN facing the edge router
StickVlanB = 0
# Allow shell commands. False causes commands print to console only without being executed.
# MUST BE ENABLED FOR PROGRAM TO FUNCTION
enableActualShellCommands = True
# Add 'sudo' before execution of any shell commands. May be required depending on distribution and environment.
runShellCommandsAsSudo = False
# Allows overriding queues / CPU cores used. When set to 0, the max possible queues / CPU cores are utilized. Please
# leave as 0.
queuesAvailableOverride = 0
# Devices in in ShapedDevices.csv without defined Parent Nodes are placed in generated parent nodes.
# When set True, this option balances the subscribers across generatedPNs / CPU cores based on the subscriber's bandwidth plan.
# When set False, devices are placed in generatedPNs sequentially with a near equal number of subscribers per core.
# Whether this impacts balance across CPUs will depend on your subscribers' usage patterns, but if you are observing
# unequal CPU load, and have most subscribers without a defined Parent Node, it is recommended to try this option.
# Most subscribers average about the same bandwidth load regardless of speed plan (typically 5Mbps or so).
# Past 25,000 subscribers this option becomes inefficient and is not advised.
useBinPackingToBalanceCPU = False
# Bandwidth & Latency Graphing
influxDBEnabled = True
influxDBurl = "http://localhost:8086"
influxDBBucket = "libreqos"
influxDBOrg = "Your ISP Name Here"
influxDBtoken = ""
# NMS/CRM Integration
# Use Customer Name or Address as Circuit Name
circuitNameUseAddress = True
# Should integrationUISP overwrite network.json on each run?
overwriteNetworkJSONalways = False
# If a device shows a WAN IP within these subnets, assume they are behind NAT / un-shapable, and ignore them
ignoreSubnets = ['192.168.0.0/16']
allowedSubnets = ['100.64.0.0/10']
# Powercode Integration
automaticImportPowercode = False
powercode_api_key = ''
# Everything before :444/api/ in your Powercode instance URL
powercode_api_url = ''
# Sonar Integration
automaticImportSonar = False
sonar_api_key = ''
sonar_api_url = '' # ex 'https://company.sonar.software/api/graphql'
# If there are radios in these lists, we will try to get the clients using snmp. This requires snmpwalk to be install on the server. You can use "sudo apt-get install snmp" for that. You will also need to fill in the snmp_community.
sonar_airmax_ap_model_ids = [] # ex ['29','43']
sonar_ltu_ap_model_ids = [] # ex ['4']
snmp_community = ''
# This is for all account statuses where we should be applying QoS. If you leave it blank, we'll use any status in account marked with "Activates Account" in Sonar.
sonar_active_status_ids = []
# Splynx Integration
automaticImportSplynx = False
splynx_api_key = ''
splynx_api_secret = ''
# Everything before /api/2.0/ on your Splynx instance
splynx_api_url = 'https://YOUR_URL.splynx.app'
#Sonar Integration
automaticImportSonar = False
sonar_api_key = ''
sonar_api_url = '' # ex 'https://company.sonar.software/api/graphql'
# If there are radios in these lists, we will try to get the clients using snmp. This requires snmpwalk to be install on the server. You can use "sudo apt-get install snmp" for that. You will also need to fill in the snmp_community.
sonar_airmax_ap_model_ids = [] # ex ['29','43']
sonar_ltu_ap_model_ids = [] # ex ['4']
snmp_community = ''
# This is for all account statuses where we should be applying QoS. If you leave it blank, we'll use any status in account marked with "Activates Account" in Sonar.
sonar_active_status_ids = []
# UISP integration
automaticImportUISP = False
uispAuthToken = ''
# Everything before /nms/ on your UISP instance
UISPbaseURL = 'https://examplesite.com'
# UISP Site - enter the name of the root site in your network tree
# to act as the starting point for the tree mapping
uispSite = ''
# Strategy:
# * "flat" - create all client sites directly off the top of the tree,
# provides maximum performance - at the expense of not offering AP,
# or site options.
# * "full" - build a complete network map
uispStrategy = "full"
# Handling of UISP suspensions:
# * "none" - do not handle suspensions
# * "ignore" - do not add suspended customers to the network map
# * "slow" - limit suspended customers to 1mbps
uispSuspendedStrategy = "none"
# Assumed capacity of AirMax and LTU radios vs reported capacity by UISP. For example, 65% would be 0.65.
# For AirMax, this applies to flexible frame only. AirMax fixed frame will have capacity based on ratio.
airMax_capacity = 0.65
ltu_capacity = 0.90
# List any sites that should not be included, with each site name surrounded by '' and separated by commas
excludeSites = []
# If you use IPv6, this can be used to find associated IPv6 prefixes for your clients' IPv4 addresses, and match them
# to those devices
findIPv6usingMikrotik = False
# If you want to provide a safe cushion for speed test results to prevent customer complains, you can set this to
# 1.15 (15% above plan rate). If not, you can leave as 1.0
bandwidthOverheadFactor = 1.0
# Number to multiply the maximum/ceiling bandwidth with to determine the minimum bandwidth.
committedBandwidthMultiplier = 0.98
# For edge cases, set the respective ParentNode for these CPEs
exceptionCPEs = {}
# exceptionCPEs = {
# 'CPE-SomeLocation1': 'AP-SomeLocation1',
# 'CPE-SomeLocation2': 'AP-SomeLocation2',
# }
# API Auth
apiUsername = "testUser"
apiPassword = "changeme8343486806"
apiHostIP = "127.0.0.1"
apiHostPost = 5000
httpRestIntegrationConfig = {
'enabled': False,
'baseURL': 'https://domain',
'networkURI': '/some/path',
'shaperURI': '/some/path/etc',
'requestsConfig': {
'verify': True, # Good for Dev if your dev env doesnt have cert
'params': { # params for query string ie uri?some-arg=some-value
'search': 'hold-my-beer'
},
#'headers': {
# 'Origin': 'SomeHeaderValue',
#},
},
# If you want to store a timestamped copy/backup of both network.json and Shaper.csv each time they are updated,
# provide a path
# 'logChanges': '/var/log/libreqos'
}

View File

@ -1,226 +0,0 @@
[main]
lqos_directory = '/etc/lqos/' # /etc/lqos seems saner
lqos_bus = '/run/lqos'
[perms]
max_users = 0 # limiting connects is sane
group = 'lqos'
umask = 0770 # Restrict access to the bus to lqos group and root
[stats]
queue_check_period_us = 1000000 # 1/2 rx_usecs would be nice
[tuning]
stop_irq_balance = true
netdev_budget_usecs = 8000
netdev_budget_packets = 300
rx_usecs = 8
tx_usecs = 8
disable_rxvlan = true
disable_txvlan = true
disable_offload = [ "gso", "tso", "lro", "sg", "gro" ]
# For a two interface setup, use the following - and replace
# "enp1s0f1" and "enp1s0f2" with your network card names (obtained
# from `ip link`):
[bridge]
use_xdp_bridge = true
interface_mapping = [
{ name = "enp1s0f1", redirect_to = "enp1s0f2", scan_vlans = false },
{ name = "enp1s0f2", redirect_to = "enp1s0f1", scan_vlans = false }
]
vlan_mapping = []
# For "on a stick" (single interface mode):
# [bridge]
# use_xdp_bridge = true
# interface_mapping = [
# { name = "enp1s0f1", redirect_to = "enp1s0f1", scan_vlans = true }
# ]
# vlan_mapping = [
# { parent = "enp1s0f1", tag = 3, redirect_to = 4 },
# { parent = "enp1s0f1", tag = 4, redirect_to = 3 }
# ]
# Does the linux bridge still work? How do you set it up? It seems
# as hot as we are on all this new stuff the lowest barrier to entry
# is a default of the linux bridge.
# How does one setup a Proxmox VM? Everyone except the testbed is on a vm.
# NMS/CRM Integration
[NMS]
# If a device shows a WAN IP within these subnets...
# assume they are behind NAT / un-shapable, and ignore them
ignoreSubnets = ['192.168.0.0/16']
allowedSubnets = ['100.64.0.0/10']
# Stuff appearing on the bridge not on these networks is bad
# Spoofed traffic, non BCP38 issues from customers, etc also bad
# I am also not big on caseING variable names
mySubnets = ['x.y.z.x/22']
myTunnels = ['192.168.0.0/16'] # Say we use a subset of 10/8 or ...
[IspConfig]
# 'fq_codel' or 'cake diffserv4'
# 'cake diffserv4' is recommended
# sqm = 'fq_codel'
sqm = 'cake diffserv4'
sqm_in = 'why do we think in and out should be the same?'
sqm_out = 'why do we think in and out should be the same?'
# Used to passively monitor the network for before / after comparisons. Leave as False to
# ensure actual shaping. After changing this value, run "sudo systemctl restart LibreQoS.service"
monitorOnlyMode = False
# How many Mbps are available to the edge of this network
# Does this mean we are ALSO applying this as a shaped rate in or out of the network?
upstreamBandwidthCapacityDownloadMbps = 1000
upstreamBandwidthCapacityUploadMbps = 1000
# Devices in ShapedDevices.csv without a defined ParentNode will be placed under a generated
# parent node, evenly spread out across CPU cores. Here, define the bandwidth limit for each
# of those generated parent nodes.
# and if that is the case, why does this make sense?
generatedPNDownloadMbps = 1000
generatedPNUploadMbps = 1000
# These seem to be duplicate and incomplete from the other stuff above
# How does one (assuming we keep this file) use on a stick here?
# There should be one way only to configure on a stick mode
# We should retire these and just attach to the bridge per the rust
# Interface connected to core router
interfaceA = 'eth1'
# Interface connected to edge router
interfaceB = 'eth2'
# WORK IN PROGRESS. Note that interfaceA determines the "stick" interface
# I could only get scanning to work if I issued ethtool -K enp1s0f1 rxvlan off
OnAStick = False
# VLAN facing the core router
StickVlanA = 0
# VLAN facing the edge router
StickVlanB = 0
# Allow shell commands. False causes commands print to console only without being executed.
# MUST BE ENABLED FOR PROGRAM TO FUNCTION
enableActualShellCommands = True
# Add 'sudo' before execution of any shell commands. May be required depending on distribution and environment.
# what happens when run from systemd, vs the command line?
runShellCommandsAsSudo = False
# Allows overriding queues / CPU cores used. When set to 0, the max possible queues / CPU cores are utilized. Please leave as 0. Why?
queuesAvailableOverride = 0
# Some networks are flat - where there are no Parent Nodes defined in ShapedDevices.csv
# For such flat networks, just define network.json as {} and enable this setting
# By default, it balances the subscribers across CPU cores, factoring in their max bandwidth rates
# Past 25,000 subsribers this algorithm becomes inefficient and is not advised
useBinPackingToBalanceCPU = True
[InfluxDB]
# Bandwidth & Latency Graphing
influxDBEnabled = True
influxDBurl = "http://localhost:8086"
influxDBBucket = "libreqos"
influxDBOrg = "Your ISP Name Here"
influxDBtoken = ""
[Splynx]
# Splynx Integration
automaticImportSplynx = False
splynx_api_key = ''
splynx_api_secret = ''
# Everything before /api/2.0/ on your Splynx instance
splynx_api_url = 'https://YOUR_URL.splynx.app'
# UISP integration
[UISP]
automaticImportUISP = False
uispAuthToken = ''
# Everything before /nms/ on your UISP instance
UISPbaseURL = 'https://examplesite.com'
# UISP Site - enter the name of the root site in your network tree
# to act as the starting point for the tree mapping
uispSite = ''
# Strategy:
# * "flat" - create all client sites directly off the top of the tree,
# provides maximum performance - at the expense of not offering AP,
# or site options.
# * "full" - build a complete network map
uispStrategy = "full"
# List any sites that should not be included, with each site name surrounded by ''
# and separated by commas
excludeSites = []
# If you use IPv6, this can be used to find associated IPv6 prefixes
# for your clients' IPv4 addresses, and match them
# to those devices
findIPv6usingMikrotik = False
# If you want to provide a safe cushion for speed test results to prevent customer complaints,
# you can set this to 1.15 (15% above plan rate). If not, you can leave as 1.0
bandwidthOverheadFactor = 1.0
# For edge cases, set the respective ParentNode for these CPEs
exceptionCPEs = {}
# exceptionCPEs = {
# 'CPE-SomeLocation1': 'AP-SomeLocation1',
# 'CPE-SomeLocation2': 'AP-SomeLocation2',
# }
# API Auth
apiUsername = "testUser"
apiPassword = "changeme8343486806"
apiHostIP = "127.0.0.1"
apiHostPost = 5000
httpRestIntegrationConfig = {
'enabled': False,
'baseURL': 'https://domain',
'networkURI': '/some/path',
'shaperURI': '/some/path/etc',
'requestsConfig': {
'verify': True, # Good for Dev if your dev env doesnt have cert
'params': { # params for query string ie uri?some-arg=some-value
'search': 'hold-my-beer'
},
#'headers': {
# 'Origin': 'SomeHeaderValue',
#},
},
# If you want to store a timestamped copy/backup of both network.json and Shaper.csv each time they are updated,
# provide a path
# 'logChanges': '/var/log/libreqos'
}

View File

@ -1,17 +1,15 @@
# This file *must* be installed in `/etc/lqos.conf`.
# Change the values to match your setup.
# Where is LibreQoS installed?
lqos_directory = '/opt/libreqos/src'
version = "1.5"
lqos_directory = "/opt/libreqos/src"
node_id = "0000-0000-0000"
node_name = "Example Node"
packet_capture_time = 10
queue_check_period_ms = 1000
packet_capture_time = 10 # Number of seconds to capture packets in an analysis session
[usage_stats]
send_anonymous = true
anonymous_server = "127.0.0.1:9125"
anonymous_server = "stats.libreqos.io:9125"
[tuning]
# IRQ balance breaks XDP_Redirect, which we use. Recommended to leave as true.
stop_irq_balance = true
netdev_budget_usecs = 8000
netdev_budget_packets = 300
@ -19,27 +17,79 @@ rx_usecs = 8
tx_usecs = 8
disable_rxvlan = true
disable_txvlan = true
# What offload types should be disabled on the NIC. The defaults are recommended here.
disable_offload = [ "gso", "tso", "lro", "sg", "gro" ]
# For a two interface setup, use the following - and replace
# "enp1s0f1" and "enp1s0f2" with your network card names (obtained
# from `ip link`):
# EITHER:
[bridge]
use_xdp_bridge = true
interface_mapping = [
{ name = "enp1s0f1", redirect_to = "enp1s0f2", scan_vlans = false },
{ name = "enp1s0f2", redirect_to = "enp1s0f1", scan_vlans = false }
]
vlan_mapping = []
to_internet = "eth0"
to_network = "eth1"
# For "on a stick" (single interface mode):
# [bridge]
# use_xdp_bridge = true
# interface_mapping = [
# { name = "enp1s0f1", redirect_to = "enp1s0f1", scan_vlans = true }
# ]
# vlan_mapping = [
# { parent = "enp1s0f1", tag = 3, redirect_to = 4 },
# { parent = "enp1s0f1", tag = 4, redirect_to = 3 }
# ]
# OR:
#[single_interface]
#interface = "eth0"
#internet_vlan = 2
#network_vlan = 3
[queues]
default_sqm = "cake diffserv4"
monitor_only = false
uplink_bandwidth_mbps = 1000
downlink_bandwidth_mbps = 1000
generated_pn_download_mbps = 1000
generated_pn_upload_mbps = 1000
dry_run = false
sudo = false
#override_available_queues = 12 # This can be omitted and be 0 for Python
use_binpacking = false
[long_term_stats]
gather_stats = true
collation_period_seconds = 10
license_key = "(data)"
uisp_reporting_interval_seconds = 300
[ip_ranges]
ignore_subnets = []
allow_subnets = [ "172.16.0.0/12", "10.0.0.0/8", "100.64.0.0/16", "192.168.0.0/16" ]
[integration_common]
circuit_name_as_address = false
always_overwrite_network_json = false
queue_refresh_interval_mins = 30
[spylnx_integration]
enable_spylnx = false
api_key = ""
api_secret = ""
url = ""
[uisp_integration]
enable_uisp = false
token = ""
url = ""
site = ""
strategy = ""
suspended_strategy = ""
airmax_capacity = 0.65
ltu_capacity = 0.9
exclude_sites = []
ipv6_with_mikrotik = false
bandwidth_overhead_factor = 1.0
commit_bandwidth_multiplier = 0.98
exception_cpes = []
use_ptmp_as_parent = false
[powercode_integration]
enable_powercode = false
powercode_api_key = ""
powercode_api_url = ""
[sonar_integration]
enable_sonar = false
sonar_api_key = ""
sonar_api_url = ""
snmp_community = "public"
airmax_model_ids = [ "" ]
ltu_model_ids = [ "" ]
active_status_ids = [ "" ]

View File

@ -3,7 +3,7 @@
**The LibreQoS Daemon** is designed to run as a `systemd` service at all times. It provides:
* Load/Unload the XDP/TC programs (they unload when the program exits)
* Configure XDP/TC, based on the content of `ispConfig.py`.
* Configure XDP/TC, based on the content of `/etc/lqos.conf`.
* Includes support for "on a stick" mode, using `OnAStick = True, StickVlanA = 1, StickVlanB = 2`.
* Hosts a lightweight server offering "bus" queries for clients (such as `lqtop` and `xdp_iphash_to_cpu_cmdline`).
* See the `lqos_bus` sub-project for bus details.