mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2025-02-25 18:55:32 -06:00
Delete old/v0.8 directory
This commit is contained in:
@@ -1,6 +0,0 @@
|
|||||||
AP,Max Download,Max Upload
|
|
||||||
A,500,100
|
|
||||||
C,225,50
|
|
||||||
F,500,100
|
|
||||||
R,225,50
|
|
||||||
X,500,100
|
|
||||||
|
@@ -1,291 +0,0 @@
|
|||||||
# Copyright (C) 2020 Robert Chacón
|
|
||||||
# This file is part of LibreQoS.
|
|
||||||
#
|
|
||||||
# LibreQoS is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# LibreQoS is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with LibreQoS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
# _ _ _ ___ ____
|
|
||||||
# | | (_) |__ _ __ ___ / _ \ ___/ ___|
|
|
||||||
# | | | | '_ \| '__/ _ \ | | |/ _ \___ \
|
|
||||||
# | |___| | |_) | | | __/ |_| | (_) |__) |
|
|
||||||
# |_____|_|_.__/|_| \___|\__\_\\___/____/
|
|
||||||
# v.0.81-stable
|
|
||||||
#
|
|
||||||
import random
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import io
|
|
||||||
import json
|
|
||||||
import csv
|
|
||||||
import subprocess
|
|
||||||
from subprocess import PIPE
|
|
||||||
import ipaddress
|
|
||||||
from ipaddress import IPv4Address, IPv6Address
|
|
||||||
import time
|
|
||||||
from datetime import date, datetime
|
|
||||||
from ispConfig import fqOrCAKE, upstreamBandwidthCapacityDownloadMbps, upstreamBandwidthCapacityUploadMbps, defaultClassCapacityDownloadMbps, defaultClassCapacityUploadMbps, interfaceA, interfaceB, enableActualShellCommands, runShellCommandsAsSudo
|
|
||||||
import collections
|
|
||||||
|
|
||||||
def shell(command):
|
|
||||||
if enableActualShellCommands:
|
|
||||||
if runShellCommandsAsSudo:
|
|
||||||
command = 'sudo ' + command
|
|
||||||
commands = command.split(' ')
|
|
||||||
print(command)
|
|
||||||
proc = subprocess.Popen(commands, stdout=subprocess.PIPE)
|
|
||||||
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"): # or another encoding
|
|
||||||
print(line)
|
|
||||||
else:
|
|
||||||
print(command)
|
|
||||||
|
|
||||||
def clearPriorSettings(interfaceA, interfaceB):
|
|
||||||
shell('tc filter delete dev ' + interfaceA)
|
|
||||||
shell('tc filter delete dev ' + interfaceA + ' root')
|
|
||||||
shell('tc qdisc delete dev ' + interfaceA + ' root')
|
|
||||||
shell('tc qdisc delete dev ' + interfaceA)
|
|
||||||
shell('tc filter delete dev ' + interfaceB)
|
|
||||||
shell('tc filter delete dev ' + interfaceB + ' root')
|
|
||||||
shell('tc qdisc delete dev ' + interfaceB + ' root')
|
|
||||||
shell('tc qdisc delete dev ' + interfaceB)
|
|
||||||
if runShellCommandsAsSudo:
|
|
||||||
clearMemoryCache()
|
|
||||||
|
|
||||||
def refreshShapers():
|
|
||||||
devices = []
|
|
||||||
accessPointDownloadMbps = {}
|
|
||||||
accessPointUploadMbps = {}
|
|
||||||
filterHandleCounter = 101
|
|
||||||
#Load Access Points
|
|
||||||
with open('AccessPoints.csv') as csv_file:
|
|
||||||
csv_reader = csv.reader(csv_file, delimiter=',')
|
|
||||||
next(csv_reader)
|
|
||||||
for row in csv_reader:
|
|
||||||
AP, download, upload = row
|
|
||||||
accessPointDownloadMbps[AP] = int(download)
|
|
||||||
accessPointUploadMbps[AP] = int(upload)
|
|
||||||
#Load Devices
|
|
||||||
with open('Shaper.csv') as csv_file:
|
|
||||||
csv_reader = csv.reader(csv_file, delimiter=',')
|
|
||||||
next(csv_reader)
|
|
||||||
for row in csv_reader:
|
|
||||||
deviceID, AP, mac, hostname,ipv4, ipv6, downloadMin, uploadMin, downloadMax, uploadMax = row
|
|
||||||
ipv4 = ipv4.strip()
|
|
||||||
ipv6 = ipv6.strip()
|
|
||||||
if AP == "":
|
|
||||||
AP = "none"
|
|
||||||
thisDevice = {
|
|
||||||
"id": deviceID,
|
|
||||||
"mac": mac,
|
|
||||||
"AP": AP,
|
|
||||||
"hostname": hostname,
|
|
||||||
"ipv4": ipv4,
|
|
||||||
"ipv6": ipv6,
|
|
||||||
"downloadMin": int(downloadMin),
|
|
||||||
"uploadMin": int(uploadMin),
|
|
||||||
"downloadMax": int(downloadMax),
|
|
||||||
"uploadMax": int(uploadMax),
|
|
||||||
"qdiscSrc": '',
|
|
||||||
"qdiscDst": '',
|
|
||||||
}
|
|
||||||
# If an AP is specified for a device in Shaper.csv, but AP is not listed in AccessPoints.csv, raise exception
|
|
||||||
if (AP != "none") and (AP not in accessPointDownloadMbps):
|
|
||||||
raise ValueError('AP ' + AP + ' not listed in AccessPoints.csv')
|
|
||||||
devices.append(thisDevice)
|
|
||||||
|
|
||||||
# If no AP is specified for a device in Shaper.csv, it is placed under this 'default' AP shaper, set to bandwidth max at edge
|
|
||||||
accessPointDownloadMbps['none'] = upstreamBandwidthCapacityDownloadMbps
|
|
||||||
accessPointUploadMbps['none'] = upstreamBandwidthCapacityUploadMbps
|
|
||||||
|
|
||||||
#Sort into bins by AP
|
|
||||||
result = collections.defaultdict(list)
|
|
||||||
|
|
||||||
for d in devices:
|
|
||||||
result[d['AP']].append(d)
|
|
||||||
|
|
||||||
devicesByAP = list(result.values())
|
|
||||||
|
|
||||||
#Clear Prior Configs
|
|
||||||
clearPriorSettings(interfaceA, interfaceB)
|
|
||||||
shell('tc filter delete dev ' + interfaceA + ' parent 1: u32')
|
|
||||||
shell('tc filter delete dev ' + interfaceB + ' parent 2: u32')
|
|
||||||
|
|
||||||
ipv4FiltersSrc = []
|
|
||||||
ipv4FiltersDst = []
|
|
||||||
ipv6FiltersSrc = []
|
|
||||||
ipv6FiltersDst = []
|
|
||||||
|
|
||||||
#InterfaceA
|
|
||||||
parentIDFirstPart = 1
|
|
||||||
srcOrDst = 'dst'
|
|
||||||
thisInterface = interfaceA
|
|
||||||
classIDcounter = 3
|
|
||||||
shell('tc qdisc replace dev ' + thisInterface + ' root handle 1: htb default 2 r2q 1514')
|
|
||||||
shell('tc class add dev ' + thisInterface + ' parent 1: classid 1:1 htb rate '+ str(upstreamBandwidthCapacityDownloadMbps) + 'mbit ceil ' + str(upstreamBandwidthCapacityDownloadMbps) + 'mbit')
|
|
||||||
shell('tc qdisc add dev ' + thisInterface + ' parent 1:1 ' + fqOrCAKE)
|
|
||||||
#Default class - traffic gets passed through this limiter if not otherwise classified by the Shaper.csv
|
|
||||||
shell('tc class add dev ' + thisInterface + ' parent 1:1 classid 1:2 htb rate ' + str(defaultClassCapacityDownloadMbps) + 'mbit ceil ' + str(defaultClassCapacityDownloadMbps) + 'mbit prio 5')
|
|
||||||
shell('tc qdisc add dev ' + thisInterface + ' parent 1:2 ' + fqOrCAKE)
|
|
||||||
#Create HTBs by AP
|
|
||||||
for AP in devicesByAP:
|
|
||||||
currentAPname = AP[0]['AP']
|
|
||||||
thisAPdownload = accessPointDownloadMbps[currentAPname]
|
|
||||||
thisAPupload = accessPointUploadMbps[currentAPname]
|
|
||||||
thisHTBrate = thisAPdownload
|
|
||||||
#HTBs for each AP
|
|
||||||
thisHTBclassID = '1:' + str(classIDcounter)
|
|
||||||
# Guarentee AP gets at least 1/2 of its radio capacity, allow up to its max radio capacity when network not at peak load
|
|
||||||
shell('tc class add dev ' + thisInterface + ' parent 1:1 classid ' + str(classIDcounter) + ' htb rate '+ str(round(thisHTBrate/2)) + 'mbit ceil '+ str(round(thisHTBrate)) + 'mbit prio 3')
|
|
||||||
shell('tc qdisc add dev ' + thisInterface + ' parent 1:' + str(classIDcounter) + ' ' + fqOrCAKE)
|
|
||||||
classIDcounter += 1
|
|
||||||
for device in AP:
|
|
||||||
#QDiscs for each AP
|
|
||||||
speedcap = 0
|
|
||||||
downloadMin = device['downloadMin']
|
|
||||||
downloadMax = device['downloadMax']
|
|
||||||
shell('tc class add dev ' + thisInterface + ' parent ' + thisHTBclassID + ' classid ' + str(classIDcounter) + ' htb rate '+ str(downloadMin) + 'mbit ceil '+ str(downloadMax) + 'mbit prio 3')
|
|
||||||
shell('tc qdisc add dev ' + thisInterface + ' parent 1:' + str(classIDcounter) + ' ' + fqOrCAKE)
|
|
||||||
if device['ipv4']:
|
|
||||||
parentString = '1:'
|
|
||||||
flowIDstring = '1:' + str(classIDcounter)
|
|
||||||
ipv4FiltersDst.append((device['ipv4'], parentString, flowIDstring))
|
|
||||||
if device['ipv6']:
|
|
||||||
parentString = '1:'
|
|
||||||
flowIDstring = '1:' + str(classIDcounter)
|
|
||||||
ipv6FiltersDst.append((device['ipv6'], parentString, flowIDstring))
|
|
||||||
deviceQDiscID = '1:' + str(classIDcounter)
|
|
||||||
device['qdiscDst'] = '1:' + str(classIDcounter)
|
|
||||||
classIDcounter += 1
|
|
||||||
|
|
||||||
#InterfaceB
|
|
||||||
parentIDFirstPart = 2
|
|
||||||
srcOrDst = 'src'
|
|
||||||
thisInterface = interfaceB
|
|
||||||
classIDcounter = 3
|
|
||||||
shell('tc qdisc replace dev ' + thisInterface + ' root handle 2: htb default 2 r2q 1514')
|
|
||||||
shell('tc class add dev ' + thisInterface + ' parent 2: classid 2:1 htb rate '+ str(upstreamBandwidthCapacityUploadMbps) + 'mbit ceil ' + str(upstreamBandwidthCapacityUploadMbps) + 'mbit')
|
|
||||||
shell('tc qdisc add dev ' + thisInterface + ' parent 2:1 ' + fqOrCAKE)
|
|
||||||
#Default class - traffic gets passed through this limiter if not otherwise classified by the Shaper.csv
|
|
||||||
shell('tc class add dev ' + thisInterface + ' parent 2:1 classid 2:2 htb rate ' + str(defaultClassCapacityUploadMbps) + 'mbit ceil ' + str(defaultClassCapacityUploadMbps) + 'mbit prio 5')
|
|
||||||
shell('tc qdisc add dev ' + thisInterface + ' parent 2:2 ' + fqOrCAKE)
|
|
||||||
#Create HTBs by AP
|
|
||||||
for AP in devicesByAP:
|
|
||||||
currentAPname = AP[0]['AP']
|
|
||||||
thisAPdownload = accessPointDownloadMbps[currentAPname]
|
|
||||||
thisAPupload = accessPointUploadMbps[currentAPname]
|
|
||||||
thisHTBrate = thisAPupload
|
|
||||||
#HTBs for each AP
|
|
||||||
thisHTBclassID = '2:' + str(classIDcounter)
|
|
||||||
# Guarentee AP gets at least 1/2 of its radio capacity, allow up to its max radio capacity when network not at peak load
|
|
||||||
shell('tc class add dev ' + thisInterface + ' parent 2:1 classid ' + str(classIDcounter) + ' htb rate '+ str(round(thisHTBrate/2)) + 'mbit ceil '+ str(round(thisHTBrate)) + 'mbit prio 3')
|
|
||||||
shell('tc qdisc add dev ' + thisInterface + ' parent 2:' + str(classIDcounter) + ' ' + fqOrCAKE)
|
|
||||||
classIDcounter += 1
|
|
||||||
for device in AP:
|
|
||||||
#QDiscs for each AP
|
|
||||||
speedcap = 0
|
|
||||||
uploadMin = device['uploadMin']
|
|
||||||
uploadMax = device['uploadMax']
|
|
||||||
shell('tc class add dev ' + thisInterface + ' parent ' + thisHTBclassID + ' classid ' + str(classIDcounter) + ' htb rate '+ str(uploadMin) + 'mbit ceil '+ str(uploadMax) + 'mbit prio 3')
|
|
||||||
shell('tc qdisc add dev ' + thisInterface + ' parent 2:' + str(classIDcounter) + ' ' + fqOrCAKE)
|
|
||||||
if device['ipv4']:
|
|
||||||
parentString = '2:'
|
|
||||||
flowIDstring = '2:' + str(classIDcounter)
|
|
||||||
ipv4FiltersSrc.append((device['ipv4'], parentString, flowIDstring))
|
|
||||||
if device['ipv6']:
|
|
||||||
parentString = '2:'
|
|
||||||
flowIDstring = '2:' + str(classIDcounter)
|
|
||||||
ipv6FiltersSrc.append((device['ipv6'], parentString, flowIDstring))
|
|
||||||
device['qdiscSrc'] = '2:' + str(classIDcounter)
|
|
||||||
classIDcounter += 1
|
|
||||||
|
|
||||||
#IPv4 Hash Filters
|
|
||||||
shell('tc filter add dev ' + interfaceA + ' parent 1: protocol all u32')
|
|
||||||
shell('tc filter add dev ' + interfaceB + ' parent 2: protocol all u32')
|
|
||||||
|
|
||||||
#Dst
|
|
||||||
interface = interfaceA
|
|
||||||
shell('tc filter add dev ' + interface + ' parent 1: protocol ip handle 3: u32 divisor 256')
|
|
||||||
|
|
||||||
for i in range (256):
|
|
||||||
hexID = str(hex(i)).replace('0x','')
|
|
||||||
for ipv4Filter in ipv4FiltersDst:
|
|
||||||
ipv4, parent, classid = ipv4Filter
|
|
||||||
if '/' in ipv4:
|
|
||||||
ipv4 = ipv4.split('/')[0]
|
|
||||||
if (ipv4.split('.', 3)[3]) == str(i):
|
|
||||||
filterHandle = hex(filterHandleCounter)
|
|
||||||
shell('tc filter add dev ' + interface + ' handle ' + filterHandle + ' protocol ip parent 1: u32 ht 3:' + hexID + ': match ip dst ' + ipv4 + ' flowid ' + classid)
|
|
||||||
filterHandleCounter += 1
|
|
||||||
shell('tc filter add dev ' + interface + ' protocol ip parent 1: u32 ht 800: match ip dst 0.0.0.0/0 hashkey mask 0x000000ff at 16 link 3:')
|
|
||||||
|
|
||||||
#Src
|
|
||||||
interface = interfaceB
|
|
||||||
shell('tc filter add dev ' + interface + ' parent 2: protocol ip handle 4: u32 divisor 256')
|
|
||||||
|
|
||||||
for i in range (256):
|
|
||||||
hexID = str(hex(i)).replace('0x','')
|
|
||||||
for ipv4Filter in ipv4FiltersSrc:
|
|
||||||
ipv4, parent, classid = ipv4Filter
|
|
||||||
if '/' in ipv4:
|
|
||||||
ipv4 = ipv4.split('/')[0]
|
|
||||||
if (ipv4.split('.', 3)[3]) == str(i):
|
|
||||||
filterHandle = hex(filterHandleCounter)
|
|
||||||
shell('tc filter add dev ' + interface + ' handle ' + filterHandle + ' protocol ip parent 2: u32 ht 4:' + hexID + ': match ip src ' + ipv4 + ' flowid ' + classid)
|
|
||||||
filterHandleCounter += 1
|
|
||||||
shell('tc filter add dev ' + interface + ' protocol ip parent 2: u32 ht 800: match ip src 0.0.0.0/0 hashkey mask 0x000000ff at 12 link 4:')
|
|
||||||
|
|
||||||
#IPv6 Hash Filters
|
|
||||||
#Dst
|
|
||||||
interface = interfaceA
|
|
||||||
shell('tc filter add dev ' + interface + ' parent 1: handle 5: protocol ipv6 u32 divisor 256')
|
|
||||||
|
|
||||||
for ipv6Filter in ipv6FiltersDst:
|
|
||||||
ipv6, parent, classid = ipv6Filter
|
|
||||||
withoutCIDR = ipv6.split('/')[0]
|
|
||||||
third = str(IPv6Address(withoutCIDR).exploded).split(':',5)[3]
|
|
||||||
usefulPart = third[:2]
|
|
||||||
hexID = usefulPart
|
|
||||||
filterHandle = hex(filterHandleCounter)
|
|
||||||
shell('tc filter add dev ' + interface + ' handle ' + filterHandle + ' protocol ipv6 parent 1: u32 ht 5:' + hexID + ': match ip6 dst ' + ipv6 + ' flowid ' + classid)
|
|
||||||
filterHandleCounter += 1
|
|
||||||
filterHandle = hex(filterHandleCounter)
|
|
||||||
shell('tc filter add dev ' + interface + ' protocol ipv6 parent 1: u32 ht 800:: match ip6 dst ::/0 hashkey mask 0x0000ff00 at 28 link 5:')
|
|
||||||
filterHandleCounter += 1
|
|
||||||
|
|
||||||
#Src
|
|
||||||
interface = interfaceB
|
|
||||||
shell('tc filter add dev ' + interface + ' parent 2: handle 6: protocol ipv6 u32 divisor 256')
|
|
||||||
|
|
||||||
for ipv6Filter in ipv6FiltersSrc:
|
|
||||||
ipv6, parent, classid = ipv6Filter
|
|
||||||
withoutCIDR = ipv6.split('/')[0]
|
|
||||||
third = str(IPv6Address(withoutCIDR).exploded).split(':',5)[3]
|
|
||||||
usefulPart = third[:2]
|
|
||||||
hexID = usefulPart
|
|
||||||
filterHandle = hex(filterHandleCounter)
|
|
||||||
shell('tc filter add dev ' + interface + ' handle ' + filterHandle + ' protocol ipv6 parent 2: u32 ht 6:' + hexID + ': match ip6 src ' + ipv6 + ' flowid ' + classid)
|
|
||||||
filterHandleCounter += 1
|
|
||||||
filterHandle = hex(filterHandleCounter)
|
|
||||||
shell('tc filter add dev ' + interface + ' protocol ipv6 parent 2: u32 ht 800:: match ip6 src ::/0 hashkey mask 0x0000ff00 at 12 link 6:')
|
|
||||||
filterHandleCounter += 1
|
|
||||||
|
|
||||||
#Save devices to file to allow for statistics runs
|
|
||||||
with open('devices.json', 'w') as outfile:
|
|
||||||
json.dump(devices, outfile)
|
|
||||||
|
|
||||||
#Done
|
|
||||||
currentTimeString = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
|
||||||
print("Successful run completed on " + currentTimeString)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
refreshShapers()
|
|
||||||
print("Program complete")
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# v0.8 (IPv4 & IPv6) (Stable)
|
|
||||||
|
|
||||||
- Released: 2 July 2021
|
|
||||||
|
|
||||||
## Installation Guide
|
|
||||||
- 📄 [LibreQoS 0.8 Installation and Usage Guide - Proxmox and Ubuntu 20.04 LTS](https://github.com/rchac/LibreQoS/wiki/LibreQoS-v0.8-Installation-&-Usage-Guide----Proxmox-and-Ubuntu-20.04)
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Dual stack: client can be shaped by same qdisc for both IPv4 and IPv6
|
|
||||||
|
|
||||||
- Up to 1000 clients (IPv4/IPv6)
|
|
||||||
|
|
||||||
- Real world asymmetrical throughput: between 2Gbps and 4.5Gbps depending on CPU single thread performance.
|
|
||||||
|
|
||||||
- HTB+fq_codel or HTB+cake
|
|
||||||
|
|
||||||
- Shape Clients by Access Point / Node capacity
|
|
||||||
|
|
||||||
- TC filters split into groups through hashing filters to increase throughput
|
|
||||||
|
|
||||||
- Simple client management via csv file
|
|
||||||
|
|
||||||
- Simple statistics - table shows top 20 subscribers by packet loss, with APs listed
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
- Qdisc locking problem limits throughput of HTB used in v0.8 (solved in v0.9). Tested up to 4Gbps/500Mbps asymmetrical throughput using Microsoft Ethr with n=500 streams. High quantities of small packets will reduce max throughput in practice.
|
|
||||||
|
|
||||||
- Linux tc hash tables can only handle ~4000 rules each. This limits total possible clients to 1000 in v0.8.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
ID,AP,MAC,Hostname,IPv4,IPv6,Download Min,Upload Min,Download Max,Upload Max
|
|
||||||
3001,A,32:3B:FE:B0:92:C1,CPE-Customer1,100.126.0.77,2001:495:1f0f:58a::4/64,25,8,115,18
|
|
||||||
3002,C,AE:EC:D3:70:DD:36,CPE-Customer2,100.126.0.78,2001:495:1f0f:58a::8/64,25,8,115,18
|
|
||||||
3003,F,1C:1E:60:69:88:9A,CPE-Customer3,100.126.0.79,2001:495:1f0f:58a::12/64,25,8,115,18
|
|
||||||
3004,R,11:B1:63:C4:DA:4C,CPE-Customer4,100.126.0.80,2001:495:1f0f:58a::16/64,25,8,115,18
|
|
||||||
3005,X,46:2F:B5:C2:0B:15,CPE-Customer5,100.126.0.81,2001:495:1f0f:58a::20/64,25,8,115,18
|
|
||||||
|
@@ -1,25 +0,0 @@
|
|||||||
#'fq_codel' or 'cake'
|
|
||||||
# Cake requires many specific packages and kernel changes:
|
|
||||||
# https://www.bufferbloat.net/projects/codel/wiki/Cake/
|
|
||||||
# https://github.com/dtaht/tc-adv
|
|
||||||
fqOrCAKE = 'fq_codel'
|
|
||||||
|
|
||||||
# How many Mbps are available to the edge of this network
|
|
||||||
upstreamBandwidthCapacityDownloadMbps = 1000
|
|
||||||
upstreamBandwidthCapacityUploadMbps = 1000
|
|
||||||
|
|
||||||
# Traffic from devices not specified in Shaper.csv will be rate limited by an HTB of this many Mbps
|
|
||||||
defaultClassCapacityDownloadMbps = 1000
|
|
||||||
defaultClassCapacityUploadMbps = 1000
|
|
||||||
|
|
||||||
# Interface connected to core router
|
|
||||||
interfaceA = 'eth1'
|
|
||||||
|
|
||||||
# Interface connected to edge router
|
|
||||||
interfaceB = 'eth2'
|
|
||||||
|
|
||||||
# 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
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import time
|
|
||||||
import schedule
|
|
||||||
from datetime import date
|
|
||||||
from LibreQoS import refreshShapers
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
refreshShapers()
|
|
||||||
schedule.every().day.at("04:00").do(refreshShapers)
|
|
||||||
while True:
|
|
||||||
schedule.run_pending()
|
|
||||||
time.sleep(60) # wait one minute
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
import os
|
|
||||||
import subprocess
|
|
||||||
from subprocess import PIPE
|
|
||||||
import io
|
|
||||||
import decimal
|
|
||||||
import json
|
|
||||||
from operator import itemgetter
|
|
||||||
from prettytable import PrettyTable
|
|
||||||
from ispConfig import fqOrCAKE
|
|
||||||
|
|
||||||
def getStatistics():
|
|
||||||
tcShowResults = []
|
|
||||||
command = 'tc -s qdisc show'
|
|
||||||
commands = command.split(' ')
|
|
||||||
proc = subprocess.Popen(commands, stdout=subprocess.PIPE)
|
|
||||||
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"): # or another encoding
|
|
||||||
tcShowResults.append(line)
|
|
||||||
allQDiscStats = []
|
|
||||||
thisFlow = {}
|
|
||||||
thisFlowStats = {}
|
|
||||||
withinCorrectChunk = False
|
|
||||||
for line in tcShowResults:
|
|
||||||
expecting = "qdisc " + fqOrCAKE
|
|
||||||
if expecting in line:
|
|
||||||
thisFlow['qDiscID'] = line.split(' ')[6]
|
|
||||||
withinCorrectChunk = True
|
|
||||||
elif ("Sent " in line) and withinCorrectChunk:
|
|
||||||
items = line.split(' ')
|
|
||||||
thisFlowStats['GigabytesSent'] = str(round((int(items[2]) * 0.000000001), 1))
|
|
||||||
thisFlowStats['PacketsSent'] = int(items[4])
|
|
||||||
thisFlowStats['droppedPackets'] = int(items[7].replace(',',''))
|
|
||||||
thisFlowStats['overlimitsPackets'] = int(items[9])
|
|
||||||
thisFlowStats['requeuedPackets'] = int(items[11].replace(')',''))
|
|
||||||
if thisFlowStats['PacketsSent'] > 0:
|
|
||||||
overlimitsFreq = (thisFlowStats['overlimitsPackets']/thisFlowStats['PacketsSent'])
|
|
||||||
else:
|
|
||||||
overlimitsFreq = -1
|
|
||||||
elif ('backlog' in line) and withinCorrectChunk:
|
|
||||||
items = line.split(' ')
|
|
||||||
thisFlowStats['backlogBytes'] = int(items[2].replace('b',''))
|
|
||||||
thisFlowStats['backlogPackets'] = int(items[3].replace('p',''))
|
|
||||||
thisFlowStats['requeues'] = int(items[5])
|
|
||||||
elif ('maxpacket' in line) and withinCorrectChunk:
|
|
||||||
items = line.split(' ')
|
|
||||||
thisFlowStats['maxPacket'] = int(items[3])
|
|
||||||
thisFlowStats['dropOverlimit'] = int(items[5])
|
|
||||||
thisFlowStats['newFlowCount'] = int(items[7])
|
|
||||||
thisFlowStats['ecnMark'] = int(items[9])
|
|
||||||
elif ("new_flows_len" in line) and withinCorrectChunk:
|
|
||||||
items = line.split(' ')
|
|
||||||
thisFlowStats['newFlowsLen'] = int(items[3])
|
|
||||||
thisFlowStats['oldFlowsLen'] = int(items[5])
|
|
||||||
if thisFlowStats['PacketsSent'] == 0:
|
|
||||||
thisFlowStats['percentageDropped'] = 0
|
|
||||||
else:
|
|
||||||
thisFlowStats['percentageDropped'] = thisFlowStats['droppedPackets']/thisFlowStats['PacketsSent']
|
|
||||||
withinCorrectChunk = False
|
|
||||||
thisFlow['stats'] = thisFlowStats
|
|
||||||
allQDiscStats.append(thisFlow)
|
|
||||||
thisFlowStats = {}
|
|
||||||
thisFlow = {}
|
|
||||||
#Load shapableDevices
|
|
||||||
updatedFlowStats = []
|
|
||||||
with open('devices.json', 'r') as infile:
|
|
||||||
devices = json.load(infile)
|
|
||||||
for shapableDevice in devices:
|
|
||||||
shapableDeviceqdiscSrc = shapableDevice['qdiscSrc']
|
|
||||||
shapableDeviceqdiscDst = shapableDevice['qdiscDst']
|
|
||||||
for device in allQDiscStats:
|
|
||||||
deviceFlowID = device['qDiscID']
|
|
||||||
if shapableDeviceqdiscSrc == deviceFlowID:
|
|
||||||
name = shapableDevice['hostname']
|
|
||||||
AP = shapableDevice['AP']
|
|
||||||
ipv4 = shapableDevice['ipv4']
|
|
||||||
ipv6 = shapableDevice['ipv6']
|
|
||||||
srcOrDst = 'src'
|
|
||||||
tempDict = {'name': name, 'AP': AP, 'ipv4': ipv4, 'ipv6': ipv6, 'srcOrDst': srcOrDst}
|
|
||||||
device['identification'] = tempDict
|
|
||||||
updatedFlowStats.append(device)
|
|
||||||
if shapableDeviceqdiscDst == deviceFlowID:
|
|
||||||
name = shapableDevice['hostname']
|
|
||||||
AP = shapableDevice['AP']
|
|
||||||
ipv4 = shapableDevice['ipv4']
|
|
||||||
ipv6 = shapableDevice['ipv6']
|
|
||||||
srcOrDst = 'dst'
|
|
||||||
tempDict = {'name': name, 'AP': AP, 'ipv4': ipv4, 'ipv6': ipv6, 'srcOrDst': srcOrDst}
|
|
||||||
device['identification'] = tempDict
|
|
||||||
updatedFlowStats.append(device)
|
|
||||||
mergedStats = []
|
|
||||||
for item in updatedFlowStats:
|
|
||||||
if item['identification']['srcOrDst'] == 'src':
|
|
||||||
newStat = {
|
|
||||||
'identification': {
|
|
||||||
'name': item['identification']['name'],
|
|
||||||
'AP': item['identification']['AP'],
|
|
||||||
'ipv4': item['identification']['ipv4'],
|
|
||||||
'ipv6': item['identification']['ipv6']
|
|
||||||
},
|
|
||||||
'src': {
|
|
||||||
'GigabytesSent': item['stats']['GigabytesSent'],
|
|
||||||
'PacketsSent': item['stats']['PacketsSent'],
|
|
||||||
'droppedPackets': item['stats']['droppedPackets'],
|
|
||||||
'overlimitsPackets': item['stats']['overlimitsPackets'],
|
|
||||||
'requeuedPackets': item['stats']['requeuedPackets'],
|
|
||||||
'backlogBytes': item['stats']['backlogBytes'],
|
|
||||||
'backlogPackets': item['stats']['backlogPackets'],
|
|
||||||
'requeues': item['stats']['requeues'],
|
|
||||||
'maxPacket': item['stats']['maxPacket'],
|
|
||||||
'dropOverlimit': item['stats']['dropOverlimit'],
|
|
||||||
'newFlowCount': item['stats']['newFlowCount'],
|
|
||||||
'ecnMark': item['stats']['ecnMark'],
|
|
||||||
'newFlowsLen': item['stats']['newFlowsLen'],
|
|
||||||
'oldFlowsLen': item['stats']['oldFlowsLen'],
|
|
||||||
'percentageDropped': item['stats']['percentageDropped'],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mergedStats.append(newStat)
|
|
||||||
for item in updatedFlowStats:
|
|
||||||
if item['identification']['srcOrDst'] == 'dst':
|
|
||||||
ipv4 = item['identification']['ipv4']
|
|
||||||
ipv6 = item['identification']['ipv6']
|
|
||||||
newStat = {
|
|
||||||
'dst': {
|
|
||||||
'GigabytesSent': item['stats']['GigabytesSent'],
|
|
||||||
'PacketsSent': item['stats']['PacketsSent'],
|
|
||||||
'droppedPackets': item['stats']['droppedPackets'],
|
|
||||||
'overlimitsPackets': item['stats']['overlimitsPackets'],
|
|
||||||
'requeuedPackets': item['stats']['requeuedPackets'] ,
|
|
||||||
'backlogBytes': item['stats']['backlogBytes'],
|
|
||||||
'backlogPackets': item['stats']['backlogPackets'],
|
|
||||||
'requeues': item['stats']['requeues'],
|
|
||||||
'maxPacket': item['stats']['maxPacket'],
|
|
||||||
'dropOverlimit': item['stats']['dropOverlimit'],
|
|
||||||
'newFlowCount': item['stats']['newFlowCount'],
|
|
||||||
'ecnMark': item['stats']['ecnMark'],
|
|
||||||
'newFlowsLen': item['stats']['newFlowsLen'],
|
|
||||||
'oldFlowsLen': item['stats']['oldFlowsLen'],
|
|
||||||
'percentageDropped': item['stats']['percentageDropped']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for item2 in mergedStats:
|
|
||||||
if ipv4 in item2['identification']['ipv4']:
|
|
||||||
item2 = item2.update(newStat)
|
|
||||||
elif ipv6 in item2['identification']['ipv6']:
|
|
||||||
item2 = item2.update(newStat)
|
|
||||||
return mergedStats
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
mergedStats = getStatistics()
|
|
||||||
|
|
||||||
# Display table of Customer CPEs with most packets dropped
|
|
||||||
x = PrettyTable()
|
|
||||||
x.field_names = ["Device", "AP", "IPv4", "IPv6", "UL Dropped", "DL Dropped", "GB Down/Up"]
|
|
||||||
sortableList = []
|
|
||||||
pickTop = 30
|
|
||||||
for stat in mergedStats:
|
|
||||||
name = stat['identification']['name']
|
|
||||||
AP = stat['identification']['AP']
|
|
||||||
ipv4 = stat['identification']['ipv4']
|
|
||||||
ipv6 = stat['identification']['ipv6']
|
|
||||||
srcDropped = stat['src']['percentageDropped']
|
|
||||||
dstDropped = stat['dst']['percentageDropped']
|
|
||||||
GBuploadedString = stat['src']['GigabytesSent']
|
|
||||||
GBdownloadedString = stat['dst']['GigabytesSent']
|
|
||||||
GBstring = GBuploadedString + '/' + GBdownloadedString
|
|
||||||
avgDropped = (srcDropped + dstDropped)/2
|
|
||||||
sortableList.append((name, AP, ipv4, ipv6, srcDropped, dstDropped, avgDropped, GBstring))
|
|
||||||
res = sorted(sortableList, key = itemgetter(4), reverse = True)[:pickTop]
|
|
||||||
for stat in res:
|
|
||||||
name, AP, ipv4, ipv6, srcDropped, dstDropped, avgDropped, GBstring = stat
|
|
||||||
if not name:
|
|
||||||
name = ipv4
|
|
||||||
srcDroppedString = "{0:.4%}".format(srcDropped)
|
|
||||||
dstDroppedString = "{0:.4%}".format(dstDropped)
|
|
||||||
x.add_row([name, AP, ipv4, ipv6, srcDroppedString, dstDroppedString, GBstring])
|
|
||||||
print(x)
|
|
||||||
Reference in New Issue
Block a user