Last active
March 1, 2016 04:35
-
-
Save mjohnson9/198256b473cd405c3f5e to your computer and use it in GitHub Desktop.
QoS designer for RouterOS devices
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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