Skip to content

Instantly share code, notes, and snippets.

@mjohnson9
Last active March 1, 2016 04:35
Show Gist options
  • Save mjohnson9/198256b473cd405c3f5e to your computer and use it in GitHub Desktop.
Save mjohnson9/198256b473cd405c3f5e to your computer and use it in GitHub Desktop.
QoS designer for RouterOS devices
package main
import (
"bytes"
"flag"
"fmt"
"io"
"os"
"strconv"
"strings"
)
const header = `
/ip firewall mangle remove [/ip firewall mangle find]
/ipv6 firewall mangle remove [/ipv6 firewall mangle find]
/queue tree remove [/queue tree find]
/queue type
remove [:toarray "PFIFO8,PFIFO32,RED16"]
add kind=pfifo name=PFIFO8 pfifo-limit=8
add kind=pfifo name=PFIFO32 pfifo-limit=32
add kind=red name=RED16 red-burst=8 red-limit=16 red-max-threshold=12 red-min-threshold=4
`
const footer = `
/ip firewall connection remove [/ip firewall connection find]
/
`
var precedences = [...]string{"routine", "priority", "immediate", "flash", "flash_override", "critical", "inet_control", "net_control"}
type class struct {
DescriptiveName string
Name string
Value uint8
GuaranteedPercent float64
MaximumPercent float64
Queue string
}
var classes = [...]*class{
//{"Network Control", "CS6", 48, 0.01, 1, "PFIFO8"},
{"Telephony", "EF", 46, 0.085, 1, "PFIFO8"},
//{"Signaling", "CS5", 40, 0.025, 1, "PFIFO8"},
//{"Multimedia Conferencing", "AF43", 38, 0.01, 1, "PFIFO8"},
//{"Multimedia Conferencing", "AF42", 36, 0.01, 1, "PFIFO8"},
//{"Multimedia Conferencing", "AF41", 34, 0.01, 1, "PFIFO8"},
{"Real-Time Interactive", "CS4", 32, 0.10, 1, "PFIFO8"},
//{"Multimedia Streaming", "AF33", 30, 0.01, 1, "PFIFO8"},
//{"Multimedia Streaming", "AF32", 28, 0.02, 1, "PFIFO8"},
{"Multimedia Streaming", "AF31", 26, 0.15, 1, "PFIFO8"},
//{"Broadcast Video", "CS3", 24, 0.15, 1, "PFIFO8"},
//{"Low-Latency Data", "AF23", 22, 0.02, 1, "PFIFO8"},
//{"Low-Latency Data", "AF22", 20, 0.02, 1, "PFIFO8"},
//{"Low-Latency Data", "AF21", 18, 0.02, 1, "PFIFO8"},
{"Ops/Admin/Mgmt", "CS2", 16, 0.10, 1, "PFIFO8"},
//{"High-Throughput Data", "AF13", 14, 0.02, 1, "PFIFO8"},
//{"High-Throughput Data", "AF12", 12, 0.02, 1, "PFIFO8"},
{"High-Throughput Data", "AF11", 10, 0.08, 1, "PFIFO8"},
{"Standard", "BE", 0, 0.30, 1, "PFIFO8"},
{"Low-Priority Data", "CS1", 8, 0.10, 1, "RED16"},
}
func init() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s: [download-limit] [upload-limit]\n\n", os.Args[0])
flag.PrintDefaults()
}
}
var (
inboundInterface = flag.String("inbound-interface", "bridge-lan", "The interface to queue on for inbound (download) traffic")
outboundInterface = flag.String("outbound-interface", "ether1", "The interface to queue on for outbound (upload) traffic")
)
func buildFirewallRules() {
fmt.Print("/ip firewall mangle\n")
fmt.Printf(" add action=jump chain=output jump-target=do-connection-mark out-interface=%s comment=\"Mark connections\"\n", *outboundInterface)
fmt.Printf(" add action=jump chain=forward jump-target=do-connection-mark out-interface=%s comment=\"Mark connections\"\n", *outboundInterface)
//fmt.Printf(" add action=return chain=do-connection-mark comment=\"Ignore already marked connections\" disabled=no out-interface=%s connection-mark=!no-mark passthrough=no\n\n", *outboundInterface)
//fmt.Printf(" add action=mark-connection chain=do-connection-mark comment=\"Untrusted: Mark with BE\" disabled=no src-address-list=!trusted new-connection-mark=dscp_0 passthrough=no\n\n")
for _, c := range classes {
if c.Value != 0 {
continue
}
fmt.Printf(" add action=mark-connection chain=do-connection-mark comment=\"Connection: DSCP %d (%s)\" disabled=no dscp=%d new-connection-mark=dscp_%d passthrough=no\n", c.Value, c.Name, c.Value, c.Value)
}
for _, c := range classes {
if c.Value == 0 {
continue
}
fmt.Printf(" add action=mark-connection chain=do-connection-mark comment=\"Connection: DSCP %d (%s)\" disabled=no dscp=%d new-connection-mark=dscp_%d passthrough=no\n", c.Value, c.Name, c.Value, c.Value)
}
for _, c := range classes {
if c.Value != 0 {
continue
}
fmt.Printf(" add action=mark-connection chain=do-connection-mark comment=\"Connection: DSCP %d (%s) (Remaining)\" disabled=no new-connection-mark=dscp_%d passthrough=no\n", c.Value, c.Name, c.Value)
break
}
fmt.Printf("\n")
for _, c := range classes {
if c.Value != 0 {
continue
}
fmt.Printf(" add action=mark-packet chain=prerouting comment=\"Packet: DSCP %d (%s)\" disabled=no connection-mark=dscp_%d new-packet-mark=dscp_%d passthrough=no\n", c.Value, c.Name, c.Value, c.Value)
}
for _, c := range classes {
if c.Value == 0 {
continue
}
fmt.Printf(" add action=mark-packet chain=prerouting comment=\"Packet: DSCP %d (%s)\" disabled=no connection-mark=dscp_%d new-packet-mark=dscp_%d passthrough=no\n", c.Value, c.Name, c.Value, c.Value)
}
}
func buildFirewallRulesIPv6() {
fmt.Print("/ipv6 firewall mangle\n")
fmt.Printf(" add action=jump chain=forward connection-mark=no-mark jump-target=do-connection-mark src-address-list=local dst-address-list=!local\n")
//fmt.Print(" add action=return chain=do-connection-mark comment=\"Ignore already marked connections\" disabled=no connection-mark=!no-mark dst-address-list=!local src-address-list=local passthrough=no\n\n")
for _, c := range classes {
if c.Value != 0 {
continue
}
fmt.Printf(" add action=mark-connection chain=do-connection-mark comment=\"Connection: DSCP %d (%s)\" disabled=no dscp=%d new-connection-mark=dscp_%d passthrough=no\n", c.Value, c.Name, c.Value, c.Value)
}
for _, c := range classes {
if c.Value == 0 {
continue
}
fmt.Printf(" add action=mark-connection chain=do-connection-mark comment=\"Connection: DSCP %d (%s)\" disabled=no dscp=%d new-connection-mark=dscp_%d passthrough=no\n", c.Value, c.Name, c.Value, c.Value)
}
for _, c := range classes {
if c.Value != 0 {
continue
}
fmt.Printf(" add action=mark-connection chain=do-connection-mark comment=\"Connection: DSCP %d (%s) (Remaining)\" disabled=no new-connection-mark=dscp_%d passthrough=no\n", c.Value, c.Name, c.Value)
break
}
fmt.Printf("\n")
for _, c := range classes {
fmt.Printf(" add action=mark-packet chain=prerouting comment=\"Packet: DSCP %d (%s)\" disabled=no connection-mark=dscp_%d new-packet-mark=dscp_%d passthrough=no\n", c.Value, c.Name, c.Value, c.Value)
}
}
const precedenceMask = 0x07 << 3
func buildFlat(rootName, queueType, iface string, speed int64) {
for i, c := range classes {
priority := i + 1
fmt.Printf(" add name=%s_%s parent=%s priority=%d packet-mark=dscp_%d comment=\"%s: %s (%s)\"", rootName, strings.ToLower(c.Name), rootName, priority, c.Value, queueType, c.DescriptiveName, c.Name)
fmt.Printf(" queue=%s", c.Queue)
cir := float64(speed) * c.GuaranteedPercent
fmt.Printf(" limit-at=%.0f", cir)
mir := float64(speed) * c.MaximumPercent
fmt.Printf(" max-limit=%.0f", mir)
fmt.Printf("\n")
}
}
func buildForInterface(queueType, iface string, speed int64) {
rootName := "qos_" + strings.ToLower(queueType)
fmt.Printf("/queue tree\n\n add limit-at=%d max-limit=%d name=%s parent=%s priority=1 queue=PFIFO32 comment=\"%s\"\n\n", speed, speed, rootName, iface, queueType)
if len(classes) <= 8 {
buildFlat(rootName, queueType, iface, speed)
return
}
precedencesUsed := [8]bool{}
precedenceCIR := [8]int64{}
precedenceMIR := [8]int64{}
queueBuf := new(bytes.Buffer)
for _, c := range classes {
precedence := c.Value & precedenceMask >> 3
if c.Value == 8 {
precedence = 0
}
precedencesUsed[precedence] = true
precedenceName := precedences[precedence]
innerPrecedence := c.Value & (precedenceMask >> 3)
if c.Value == 0 {
innerPrecedence = 1
}
priority := 8 - innerPrecedence
if strings.HasPrefix(c.Name, "AF") {
priority = innerPrecedence
}
fmt.Fprintf(queueBuf, " add name=%s_%s parent=%s_%s priority=%d packet-mark=dscp_%d comment=\"%s: %s (%s)\"", rootName, strings.ToLower(c.Name), rootName, precedenceName, priority, c.Value, queueType, c.DescriptiveName, c.Name)
fmt.Fprintf(queueBuf, " queue=%s", c.Queue)
cir := float64(speed) * c.GuaranteedPercent
precedenceCIR[precedence] += int64(cir)
fmt.Fprintf(queueBuf, " limit-at=%.0f", cir)
mir := float64(speed) * c.MaximumPercent
if int64(mir) > precedenceMIR[precedence] {
precedenceMIR[precedence] = int64(mir)
}
fmt.Fprintf(queueBuf, " max-limit=%.0f", mir)
fmt.Fprintf(queueBuf, "\n")
}
for prec, val := range precedencesUsed[:] {
if !val {
continue
}
precedenceName := precedences[prec]
fmt.Printf(" add name=%s_%s parent=%s priority=%d limit-at=%d max-limit=%d queue=root comment=\"%s: %s (%d)\"\n", rootName, precedenceName, rootName, 8-prec, precedenceCIR[prec], precedenceMIR[prec], queueType, precedenceName, prec)
}
fmt.Printf("\n")
io.Copy(os.Stdout, queueBuf)
}
func main() {
flag.Parse()
args := flag.Args()
if len(args) < 2 {
flag.Usage()
os.Exit(2)
return
}
downloadSpeed, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "\"%s\" is not a valid interface speed.\n\n", args[0])
flag.Usage()
os.Exit(2)
return
}
uploadSpeed, err := strconv.ParseInt(args[1], 10, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "\"%s\" is not a valid interface speed.\n\n", args[0])
flag.Usage()
os.Exit(2)
return
}
fmt.Print(header)
buildFirewallRules()
fmt.Printf("\n\n")
/*buildFirewallRulesIPv6()
fmt.Printf("\n\n")*/
buildForInterface("Inbound", *inboundInterface, downloadSpeed)
fmt.Printf("\n\n")
buildForInterface("Outbound", *outboundInterface, uploadSpeed)
fmt.Printf("\n")
fmt.Print(footer)
used := float64(0)
for _, c := range classes[:] {
used += c.GuaranteedPercent
fmt.Fprintf(os.Stderr, "%s (%s): %.2f%%\n", c.DescriptiveName, c.Name, c.GuaranteedPercent*100)
}
fmt.Fprintf(os.Stderr, "Total: %.2f%%\n", used*100)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment