From f571acb5d5426bc7601c3bdfdcb6e8c65c24ad5d Mon Sep 17 00:00:00 2001 From: Louis Royer Date: Thu, 21 Sep 2023 17:47:42 +0200 Subject: [PATCH 1/7] Add trunks to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index fd1fb01..d578aaf 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ *log Vagrantfile trunk +trunks trunks_amd64 # Dependency directories (remove the comment below to include it) From fd9cf0b7b82bc7a2b72067779da4a926488b37df Mon Sep 17 00:00:00 2001 From: Louis Royer Date: Thu, 21 Sep 2023 17:51:18 +0200 Subject: [PATCH 2/7] Fix flush order Filters should be deleted first (to avoid error messages). --- runtime/trunks.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/trunks.go b/runtime/trunks.go index 4566350..7aa0917 100644 --- a/runtime/trunks.go +++ b/runtime/trunks.go @@ -58,10 +58,10 @@ func runSYSCTL(args ...string) error { func FlushTables() error { log.Println("Flushing tables") err := runIPtables("-F", "-t", "mangle") - runTC("qdisc", "del", "dev", Trunks.NIC.GW, "root") runTC("filter", "del", "dev", Trunks.NIC.GW) - runTC("qdisc", "del", "dev", Trunks.NIC.ST, "root") + runTC("qdisc", "del", "dev", Trunks.NIC.GW, "root") runTC("filter", "del", "dev", Trunks.NIC.ST) + runTC("qdisc", "del", "dev", Trunks.NIC.ST, "root") return err } From f8694db55dcb8b57cac190a07ceed23712169f69 Mon Sep 17 00:00:00 2001 From: Louis Royer Date: Thu, 21 Sep 2023 17:58:44 +0200 Subject: [PATCH 3/7] Refactor to use OOP --- main.go | 7 +-- runtime/acm.go | 28 +++++----- runtime/config.go | 31 ++++++----- runtime/init.go | 45 ++++++++------- runtime/trunks.go | 136 +++++++++++++++++++++++----------------------- 5 files changed, 124 insertions(+), 123 deletions(-) diff --git a/main.go b/main.go index 85e90ee..0004a3b 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "os" trunks "github.com/shynuu/trunks/runtime" - "github.com/urfave/cli/v2" ) @@ -57,21 +56,21 @@ func main() { }, }, Action: func(c *cli.Context) error { - err := trunks.InitTrunks(config, qos, logs) + trunksConfig, err := trunks.InitTrunks(config, qos, logs, acm) if err != nil { fmt.Println("Init error, exiting...") os.Exit(1) } if flush { - err = trunks.FlushTables() + err = trunksConfig.FlushTables() if err != nil { fmt.Println("Impossible to flush tables, exiting...") os.Exit(1) } } - trunks.Run(acm) + trunksConfig.Run() return nil }, } diff --git a/runtime/acm.go b/runtime/acm.go index 6eb7756..6598c28 100644 --- a/runtime/acm.go +++ b/runtime/acm.go @@ -10,33 +10,33 @@ import ( ) // RunACM simulate the used by the DVB-S2/RCS2 system -func RunACM(qos bool) { - if Trunks.CurrentACM == nil || Trunks.ACMCounter >= Trunks.CurrentACM.Duration { +func (t *TrunksConfig) RunACM(qos bool) { + if t.CurrentACM == nil || t.ACMCounter >= t.CurrentACM.Duration { log.Println("Changing link capacity") - var l = len(Trunks.ACMList) + var l = len(t.ACMList) rand.Seed(time.Now().UnixNano()) var index int = rand.Intn(l) - Trunks.CurrentACM = Trunks.ACMList[index] + t.CurrentACM = t.ACMList[index] var forward string var retun string if qos { - forward = fmt.Sprintf("%dmbit", int64(math.Round(Trunks.Bandwidth.Forward*Trunks.CurrentACM.Weight))-1) - retun = fmt.Sprintf("%dmbit", int64(math.Round(Trunks.Bandwidth.Return*Trunks.CurrentACM.Weight))-1) - runTC("class", "change", "dev", Trunks.NIC.GW, "parent", "1:0", "classid", "1:20", "htb", "rate", retun, "prio", "1") - runTC("class", "change", "dev", Trunks.NIC.ST, "parent", "1:0", "classid", "1:20", "htb", "rate", forward, "prio", "1") + forward = fmt.Sprintf("%dmbit", int64(math.Round(t.Bandwidth.Forward*t.CurrentACM.Weight))-1) + retun = fmt.Sprintf("%dmbit", int64(math.Round(t.Bandwidth.Return*t.CurrentACM.Weight))-1) + runTC("class", "change", "dev", t.NIC.GW, "parent", "1:0", "classid", "1:20", "htb", "rate", retun, "prio", "1") + runTC("class", "change", "dev", t.NIC.ST, "parent", "1:0", "classid", "1:20", "htb", "rate", forward, "prio", "1") } else { - forward = fmt.Sprintf("%dmbit", int64(math.Round(Trunks.Bandwidth.Forward*Trunks.CurrentACM.Weight))) - retun = fmt.Sprintf("%dmbit", int64(math.Round(Trunks.Bandwidth.Return*Trunks.CurrentACM.Weight))) - runTC("class", "change", "dev", Trunks.NIC.GW, "parent", "1:0", "classid", "1:1", "htb", "rate", retun) - runTC("class", "change", "dev", Trunks.NIC.ST, "parent", "1:0", "classid", "1:1", "htb", "rate", forward) + forward = fmt.Sprintf("%dmbit", int64(math.Round(t.Bandwidth.Forward*t.CurrentACM.Weight))) + retun = fmt.Sprintf("%dmbit", int64(math.Round(t.Bandwidth.Return*t.CurrentACM.Weight))) + runTC("class", "change", "dev", t.NIC.GW, "parent", "1:0", "classid", "1:1", "htb", "rate", retun) + runTC("class", "change", "dev", t.NIC.ST, "parent", "1:0", "classid", "1:1", "htb", "rate", forward) } log.Println("Setting the forward link bandwidth at", forward) log.Println("Setting the return link bandwidth at", retun) - Trunks.ACMCounter = 0 + t.ACMCounter = 0 } - Trunks.ACMCounter++ + t.ACMCounter++ } diff --git a/runtime/config.go b/runtime/config.go index d796b61..cb08fd0 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -8,17 +8,17 @@ import ( ) // ParseConf read the yaml file and populate the Config instancce -func ParseConf(file string) error { +func ParseConf(file string) (*TrunksConfig, error) { + var trunksConfig *TrunksConfig path, err := filepath.Abs(file) yamlFile, err := ioutil.ReadFile(path) if err != nil { - return err + return nil, err } - err = yaml.Unmarshal(yamlFile, &Trunks) - if err != nil { - return err + if err := yaml.Unmarshal(yamlFile, &trunksConfig); err != nil { + return nil, err } - return nil + return trunksConfig, nil } // Interfaces struct @@ -47,13 +47,14 @@ type ACM struct { // TrunksConfig struct type TrunksConfig struct { - NIC NIC `yaml:"nic"` - Bandwidth Bandwidth `yaml:"bandwidth"` - Delay Delay `yaml:"delay"` - ACMList []*ACM `yaml:"acm"` - QoS bool - Logs string - ACMCounter int - ACMIndex int - CurrentACM *ACM + NIC NIC `yaml:"nic"` + Bandwidth Bandwidth `yaml:"bandwidth"` + Delay Delay `yaml:"delay"` + ACMList []*ACM `yaml:"acm"` + QoS bool + Logs string + ACMEnabled bool + ACMCounter int + ACMIndex int + CurrentACM *ACM } diff --git a/runtime/init.go b/runtime/init.go index 676f84c..3d3256c 100644 --- a/runtime/init.go +++ b/runtime/init.go @@ -21,10 +21,10 @@ func Exists(path string) (bool, error) { } // CheckInterfaces checks if the interfaces exist -func CheckInterfaces(st string, gw string) error { +func (t *TrunksConfig) CheckInterfaces() error { path := "/sys/class/net/%s/operstate" - ifST := fmt.Sprintf(path, st) - ifGW := fmt.Sprintf(path, gw) + ifST := fmt.Sprintf(path, t.NIC.ST) + ifGW := fmt.Sprintf(path, t.NIC.GW) var err1, err2 error existST, _ := Exists(ifST) if !existST { @@ -44,9 +44,9 @@ func CheckInterfaces(st string, gw string) error { return nil } -func FindInterfaces() error { - ip_st := Trunks.NIC.ST - ip_gw := Trunks.NIC.GW +func (t *TrunksConfig) FindInterfaces() error { + ip_st := t.NIC.ST + ip_gw := t.NIC.GW ifaces, err := net.Interfaces() if err != nil { log.Printf("Error reading interfaces: %+v\n", err.Error()) @@ -61,11 +61,11 @@ func FindInterfaces() error { for _, a := range addrs { switch v := a.(type) { case *net.IPNet: - if v.IP.To4().String() == Trunks.NIC.GW { - Trunks.NIC.GW = i.Name + if v.IP.To4().String() == t.NIC.GW { + t.NIC.GW = i.Name } - if v.IP.To4().String() == Trunks.NIC.ST { - Trunks.NIC.ST = i.Name + if v.IP.To4().String() == t.NIC.ST { + t.NIC.ST = i.Name } } @@ -73,12 +73,12 @@ func FindInterfaces() error { } var err1, err2 error - if ip_st == Trunks.NIC.ST { + if ip_st == t.NIC.ST { err1 = errors.New("[L3] Interface for ST not found") log.Println(err1.Error()) } - if ip_gw == Trunks.NIC.GW { + if ip_gw == t.NIC.GW { err2 = errors.New("[L3] Interface for GW not found") log.Println(err2.Error()) } @@ -91,17 +91,20 @@ func FindInterfaces() error { } // InitTrunks initialize the trunks module -func InitTrunks(file string, qos bool, logs string) error { - err := ParseConf(file) +func InitTrunks(file string, qos bool, logs string, acm bool) (*TrunksConfig, error) { + t, err := ParseConf(file) if err != nil { - return err + return nil, err } - err = CheckInterfaces(Trunks.NIC.ST, Trunks.NIC.GW) - if err != nil { + if err := t.CheckInterfaces(); err != nil { log.Println("Interfaces configuration by IP") - err = FindInterfaces() + if err := t.FindInterfaces(); err != nil { + return nil, err + } } - Trunks.QoS = qos - Trunks.Logs = logs - return err + + t.QoS = qos + t.Logs = logs + t.ACMEnabled = acm + return t, nil } diff --git a/runtime/trunks.go b/runtime/trunks.go index 7aa0917..d8e0602 100644 --- a/runtime/trunks.go +++ b/runtime/trunks.go @@ -6,13 +6,12 @@ import ( "math" "os" "os/exec" + "strings" "time" "github.com/go-co-op/gocron" ) -var Trunks *TrunksConfig - func runIPtables(args ...string) error { cmd := exec.Command("iptables", args...) cmd.Stderr = os.Stderr @@ -55,133 +54,132 @@ func runSYSCTL(args ...string) error { return nil } -func FlushTables() error { +func (t *TrunksConfig) FlushTables() error { log.Println("Flushing tables") err := runIPtables("-F", "-t", "mangle") - runTC("filter", "del", "dev", Trunks.NIC.GW) - runTC("qdisc", "del", "dev", Trunks.NIC.GW, "root") - runTC("filter", "del", "dev", Trunks.NIC.ST) - runTC("qdisc", "del", "dev", Trunks.NIC.ST, "root") + runTC("filter", "del", "dev", t.NIC.GW) + runTC("qdisc", "del", "dev", t.NIC.GW, "root") + runTC("filter", "del", "dev", t.NIC.ST) + runTC("qdisc", "del", "dev", t.NIC.ST, "root") return err } // Run the Trunk link -func Run(acm bool) { +func (t *TrunksConfig) Run() { runSYSCTL("net.ipv4.ip_forward=1") - if !Trunks.QoS { + if !t.QoS { log.Println("Running without QoS") - forward := fmt.Sprintf("%dmbit", int64(math.Round(Trunks.Bandwidth.Forward))) - retun := fmt.Sprintf("%dmbit", int64(math.Round(Trunks.Bandwidth.Return))) - delay := fmt.Sprintf("%dms", int64(math.Round(Trunks.Delay.Value/2))) - offset := fmt.Sprintf("%dms", int64(math.Round(Trunks.Delay.Offset/2))) - jitter := Trunks.Delay.Offset > 1 + forward := fmt.Sprintf("%dmbit", int64(math.Round(t.Bandwidth.Forward))) + retun := fmt.Sprintf("%dmbit", int64(math.Round(t.Bandwidth.Return))) + delay := fmt.Sprintf("%dms", int64(math.Round(t.Delay.Value/2))) + offset := fmt.Sprintf("%dms", int64(math.Round(t.Delay.Offset/2))) + jitter := t.Delay.Offset > 1 // qlen formula: 1.5 * bandwidth[bits/s] * latency[s] / mtu[bits] - qlenForward := fmt.Sprintf("%d", int64(math.Round(1.5 * (Trunks.Bandwidth.Forward * 1000000) * (Trunks.Delay.Value / (2 * 1000)) / (8 * 1500)))) - qlenReturn := fmt.Sprintf("%d", int64(math.Round(1.5 * (Trunks.Bandwidth.Return * 1000000) * (Trunks.Delay.Value / (2 * 1000)) / (8 * 1500)))) + qlenForward := fmt.Sprintf("%d", int64(math.Round(1.5*(t.Bandwidth.Forward*1000000)*(t.Delay.Value/(2*1000))/(8*1500)))) + qlenReturn := fmt.Sprintf("%d", int64(math.Round(1.5*(t.Bandwidth.Return*1000000)*(t.Delay.Value/(2*1000))/(8*1500)))) log.Println("Configure IPTABLES") - runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", Trunks.NIC.ST, "-j", "MARK", "--set-mark", "10") - runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", Trunks.NIC.GW, "-j", "MARK", "--set-mark", "20") + runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", t.NIC.ST, "-j", "MARK", "--set-mark", "10") + runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", t.NIC.GW, "-j", "MARK", "--set-mark", "20") log.Println("Configure TC") - runTC("qdisc", "add", "dev", Trunks.NIC.GW, "root", "handle", "1:0", "htb", "default", "30") - runTC("class", "add", "dev", Trunks.NIC.GW, "parent", "1:0", "classid", "1:1", "htb", "rate", retun, "burst", "30k", "cburst", "30k") + runTC("qdisc", "add", "dev", t.NIC.GW, "root", "handle", "1:0", "htb", "default", "30") + runTC("class", "add", "dev", t.NIC.GW, "parent", "1:0", "classid", "1:1", "htb", "rate", retun, "burst", "30k", "cburst", "30k") if jitter { - runTC("qdisc", "add", "dev", Trunks.NIC.GW, "parent", "1:1", "handle", "2:0", "netem", "delay", delay, offset, "distribution", "normal", "limit", qlenReturn) + runTC("qdisc", "add", "dev", t.NIC.GW, "parent", "1:1", "handle", "2:0", "netem", "delay", delay, offset, "distribution", "normal", "limit", qlenReturn) } else { - runTC("qdisc", "add", "dev", Trunks.NIC.GW, "parent", "1:1", "handle", "2:0", "netem", "delay", delay, "limit", qlenReturn) + runTC("qdisc", "add", "dev", t.NIC.GW, "parent", "1:1", "handle", "2:0", "netem", "delay", delay, "limit", qlenReturn) } - runTC("filter", "add", "dev", Trunks.NIC.GW, "protocol", "ip", "parent", "1:0", "prio", "1", "handle", "10", "fw", "flowid", "1:1") + runTC("filter", "add", "dev", t.NIC.GW, "protocol", "ip", "parent", "1:0", "prio", "1", "handle", "10", "fw", "flowid", "1:1") - runTC("qdisc", "add", "dev", Trunks.NIC.ST, "root", "handle", "1:0", "htb", "default", "30") - runTC("class", "add", "dev", Trunks.NIC.ST, "parent", "1:0", "classid", "1:1", "htb", "rate", forward, "burst", "30k", "cburst", "30k") + runTC("qdisc", "add", "dev", t.NIC.ST, "root", "handle", "1:0", "htb", "default", "30") + runTC("class", "add", "dev", t.NIC.ST, "parent", "1:0", "classid", "1:1", "htb", "rate", forward, "burst", "30k", "cburst", "30k") if jitter { - runTC("qdisc", "add", "dev", Trunks.NIC.ST, "parent", "1:1", "handle", "2:0", "netem", "delay", delay, offset, "distribution", "normal", "limit", qlenForward) + runTC("qdisc", "add", "dev", t.NIC.ST, "parent", "1:1", "handle", "2:0", "netem", "delay", delay, offset, "distribution", "normal", "limit", qlenForward) } else { - runTC("qdisc", "add", "dev", Trunks.NIC.ST, "parent", "1:1", "handle", "2:0", "netem", "delay", delay, "limit", qlenForward) + runTC("qdisc", "add", "dev", t.NIC.ST, "parent", "1:1", "handle", "2:0", "netem", "delay", delay, "limit", qlenForward) } - runTC("filter", "add", "dev", Trunks.NIC.ST, "protocol", "ip", "parent", "1:0", "prio", "1", "handle", "20", "fw", "flowid", "1:1") + runTC("filter", "add", "dev", t.NIC.ST, "protocol", "ip", "parent", "1:0", "prio", "1", "handle", "20", "fw", "flowid", "1:1") } else { log.Println("Running with QoS") - forward := fmt.Sprintf("%dmbit", int64(math.Round(Trunks.Bandwidth.Forward))) + forward := fmt.Sprintf("%dmbit", int64(math.Round(t.Bandwidth.Forward))) forwardVoIP := fmt.Sprintf("%dmbit", 2) - forwardRest := fmt.Sprintf("%dmbit", int64(math.Round(Trunks.Bandwidth.Forward))-1) - retun := fmt.Sprintf("%dmbit", int64(math.Round(Trunks.Bandwidth.Return))) + forwardRest := fmt.Sprintf("%dmbit", int64(math.Round(t.Bandwidth.Forward))-1) + retun := fmt.Sprintf("%dmbit", int64(math.Round(t.Bandwidth.Return))) returnVoIP := fmt.Sprintf("%dmbit", 2) - returnRest := fmt.Sprintf("%dmbit", int64(math.Round(Trunks.Bandwidth.Return))-1) - delay := fmt.Sprintf("%dms", int64(math.Round(Trunks.Delay.Value/2))) - offset := fmt.Sprintf("%dms", int64(math.Round(Trunks.Delay.Offset/2))) - jitter := Trunks.Delay.Offset > 1 + returnRest := fmt.Sprintf("%dmbit", int64(math.Round(t.Bandwidth.Return))-1) + delay := fmt.Sprintf("%dms", int64(math.Round(t.Delay.Value/2))) + offset := fmt.Sprintf("%dms", int64(math.Round(t.Delay.Offset/2))) + jitter := t.Delay.Offset > 1 // qlen formula: 1.5 * bandwidth[bits/s] * latency[s] / mtu[bits] - qlenForwardVoIP := fmt.Sprintf("%d", int64(math.Round(1.5 * (2 * 1000000) * (Trunks.Delay.Value / (2 * 1000)) / (8 * 1500)))) - qlenForwardRest := fmt.Sprintf("%d", int64(math.Round(1.5 * ((Trunks.Bandwidth.Forward - 1) * 1000000) * (Trunks.Delay.Value / (2 * 1000)) / (8 * 1500)))) - qlenReturnVoIP := fmt.Sprintf("%d", int64(math.Round(1.5 * (Trunks.Bandwidth.Return * 1000000) * (Trunks.Delay.Value / (2 * 1000)) / (8 * 1500)))) - qlenReturnRest := fmt.Sprintf("%d", int64(math.Round(1.5 * (Trunks.Bandwidth.Return * 1000000) * (Trunks.Delay.Value / (2 * 1000)) / (8 * 1500)))) + qlenForwardVoIP := fmt.Sprintf("%d", int64(math.Round(1.5*(2*1000000)*(t.Delay.Value/(2*1000))/(8*1500)))) + qlenForwardRest := fmt.Sprintf("%d", int64(math.Round(1.5*((t.Bandwidth.Forward-1)*1000000)*(t.Delay.Value/(2*1000))/(8*1500)))) + qlenReturnVoIP := fmt.Sprintf("%d", int64(math.Round(1.5*(t.Bandwidth.Return*1000000)*(t.Delay.Value/(2*1000))/(8*1500)))) + qlenReturnRest := fmt.Sprintf("%d", int64(math.Round(1.5*(t.Bandwidth.Return*1000000)*(t.Delay.Value/(2*1000))/(8*1500)))) log.Println("Configure IPTABLES") - runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", Trunks.NIC.ST, "-j", "MARK", "--set-mark", "10") - runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", Trunks.NIC.ST, "-m", "dscp", "--dscp", "0x2c", "-j", "MARK", "--set-mark", "11") - runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", Trunks.NIC.ST, "-m", "dscp", "--dscp", "0x2e", "-j", "MARK", "--set-mark", "11") + runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", t.NIC.ST, "-j", "MARK", "--set-mark", "10") + runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", t.NIC.ST, "-m", "dscp", "--dscp", "0x2c", "-j", "MARK", "--set-mark", "11") + runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", t.NIC.ST, "-m", "dscp", "--dscp", "0x2e", "-j", "MARK", "--set-mark", "11") - runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", Trunks.NIC.GW, "-j", "MARK", "--set-mark", "20") - runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", Trunks.NIC.GW, "-m", "dscp", "--dscp", "0x2c", "-j", "MARK", "--set-mark", "21") - runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", Trunks.NIC.GW, "-m", "dscp", "--dscp", "0x2e", "-j", "MARK", "--set-mark", "21") + runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", t.NIC.GW, "-j", "MARK", "--set-mark", "20") + runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", t.NIC.GW, "-m", "dscp", "--dscp", "0x2c", "-j", "MARK", "--set-mark", "21") + runIPtables("-t", "mangle", "-A", "PREROUTING", "-i", t.NIC.GW, "-m", "dscp", "--dscp", "0x2e", "-j", "MARK", "--set-mark", "21") log.Println("Configure TC") // Qdisc configuration - runTC("qdisc", "add", "dev", Trunks.NIC.GW, "root", "handle", "1:0", "htb", "default", "20") - runTC("class", "add", "dev", Trunks.NIC.GW, "parent", "1:0", "classid", "1:1", "htb", "rate", retun, "burst", "30k", "cburst", "30k") - runTC("class", "add", "dev", Trunks.NIC.GW, "parent", "1:1", "classid", "1:10", "htb", "rate", returnVoIP, "prio", "0", "burst", "3k", "cburst", "3k") + runTC("qdisc", "add", "dev", t.NIC.GW, "root", "handle", "1:0", "htb", "default", "20") + runTC("class", "add", "dev", t.NIC.GW, "parent", "1:0", "classid", "1:1", "htb", "rate", retun, "burst", "30k", "cburst", "30k") + runTC("class", "add", "dev", t.NIC.GW, "parent", "1:1", "classid", "1:10", "htb", "rate", returnVoIP, "prio", "0", "burst", "3k", "cburst", "3k") if jitter { - runTC("qdisc", "add", "dev", Trunks.NIC.GW, "parent", "1:10", "handle", "110:", "netem", "delay", delay, offset, "distribution", "normal", "limit", qlenReturnVoIP) + runTC("qdisc", "add", "dev", t.NIC.GW, "parent", "1:10", "handle", "110:", "netem", "delay", delay, offset, "distribution", "normal", "limit", qlenReturnVoIP) } else { - runTC("qdisc", "add", "dev", Trunks.NIC.GW, "parent", "1:10", "handle", "110:", "netem", "delay", delay, "limit", qlenReturnVoIP) + runTC("qdisc", "add", "dev", t.NIC.GW, "parent", "1:10", "handle", "110:", "netem", "delay", delay, "limit", qlenReturnVoIP) } - runTC("class", "add", "dev", Trunks.NIC.GW, "parent", "1:1", "classid", "1:20", "htb", "rate", returnRest, "prio", "1", "burst", "30k", "cburst", "30k") + runTC("class", "add", "dev", t.NIC.GW, "parent", "1:1", "classid", "1:20", "htb", "rate", returnRest, "prio", "1", "burst", "30k", "cburst", "30k") if jitter { - runTC("qdisc", "add", "dev", Trunks.NIC.GW, "parent", "1:20", "handle", "120:", "netem", "delay", delay, offset, "distribution", "normal", "limit", qlenReturnRest) + runTC("qdisc", "add", "dev", t.NIC.GW, "parent", "1:20", "handle", "120:", "netem", "delay", delay, offset, "distribution", "normal", "limit", qlenReturnRest) } else { - runTC("qdisc", "add", "dev", Trunks.NIC.GW, "parent", "1:20", "handle", "120:", "netem", "delay", delay, "limit", qlenReturnRest) + runTC("qdisc", "add", "dev", t.NIC.GW, "parent", "1:20", "handle", "120:", "netem", "delay", delay, "limit", qlenReturnRest) } // Filters - runTC("filter", "add", "dev", Trunks.NIC.GW, "protocol", "ip", "parent", "1:0", "prio", "0", "handle", "11", "fw", "flowid", "1:10") - runTC("filter", "add", "dev", Trunks.NIC.GW, "protocol", "ip", "parent", "1:0", "prio", "1", "handle", "10", "fw", "flowid", "1:20") + runTC("filter", "add", "dev", t.NIC.GW, "protocol", "ip", "parent", "1:0", "prio", "0", "handle", "11", "fw", "flowid", "1:10") + runTC("filter", "add", "dev", t.NIC.GW, "protocol", "ip", "parent", "1:0", "prio", "1", "handle", "10", "fw", "flowid", "1:20") // Qdisc configuration - runTC("qdisc", "add", "dev", Trunks.NIC.ST, "root", "handle", "1:0", "htb", "default", "20") - runTC("class", "add", "dev", Trunks.NIC.ST, "parent", "1:0", "classid", "1:1", "htb", "rate", forward, "burst", "30k", "cburst", "30k") - runTC("class", "add", "dev", Trunks.NIC.ST, "parent", "1:0", "classid", "1:10", "htb", "rate", forwardVoIP, "prio", "0", "burst", "3k", "cburst", "3k") + runTC("qdisc", "add", "dev", t.NIC.ST, "root", "handle", "1:0", "htb", "default", "20") + runTC("class", "add", "dev", t.NIC.ST, "parent", "1:0", "classid", "1:1", "htb", "rate", forward, "burst", "30k", "cburst", "30k") + runTC("class", "add", "dev", t.NIC.ST, "parent", "1:0", "classid", "1:10", "htb", "rate", forwardVoIP, "prio", "0", "burst", "3k", "cburst", "3k") if jitter { - runTC("qdisc", "add", "dev", Trunks.NIC.ST, "parent", "1:10", "handle", "110:", "netem", "delay", delay, offset, "distribution", "normal", "limit", qlenForwardVoIP) + runTC("qdisc", "add", "dev", t.NIC.ST, "parent", "1:10", "handle", "110:", "netem", "delay", delay, offset, "distribution", "normal", "limit", qlenForwardVoIP) } else { - runTC("qdisc", "add", "dev", Trunks.NIC.ST, "parent", "1:10", "handle", "110:", "netem", "delay", delay, "limit", qlenForwardVoIP) + runTC("qdisc", "add", "dev", t.NIC.ST, "parent", "1:10", "handle", "110:", "netem", "delay", delay, "limit", qlenForwardVoIP) } - runTC("class", "add", "dev", Trunks.NIC.ST, "parent", "1:0", "classid", "1:20", "htb", "rate", forwardRest, "prio", "1", "burst", "30k", "cburst", "30k") + runTC("class", "add", "dev", t.NIC.ST, "parent", "1:0", "classid", "1:20", "htb", "rate", forwardRest, "prio", "1", "burst", "30k", "cburst", "30k") if jitter { - runTC("qdisc", "add", "dev", Trunks.NIC.ST, "parent", "1:20", "handle", "120:", "netem", "delay", delay, offset, "distribution", "normal", "limit", qlenForwardRest) + runTC("qdisc", "add", "dev", t.NIC.ST, "parent", "1:20", "handle", "120:", "netem", "delay", delay, offset, "distribution", "normal", "limit", qlenForwardRest) } else { - runTC("qdisc", "add", "dev", Trunks.NIC.ST, "parent", "1:20", "handle", "120:", "netem", "delay", "limit", qlenForwardRest) + runTC("qdisc", "add", "dev", t.NIC.ST, "parent", "1:20", "handle", "120:", "netem", "delay", "limit", qlenForwardRest) } - // Filters - runTC("filter", "add", "dev", Trunks.NIC.ST, "protocol", "ip", "parent", "1:0", "prio", "0", "handle", "21", "fw", "flowid", "1:10") - runTC("filter", "add", "dev", Trunks.NIC.ST, "protocol", "ip", "parent", "1:0", "prio", "1", "handle", "20", "fw", "flowid", "1:20") + runTC("filter", "add", "dev", t.NIC.ST, "protocol", "ip", "parent", "1:0", "prio", "0", "handle", "21", "fw", "flowid", "1:10") + runTC("filter", "add", "dev", t.NIC.ST, "protocol", "ip", "parent", "1:0", "prio", "1", "handle", "20", "fw", "flowid", "1:20") } - if acm { - log.Println("Starting Trunks with ACM") + if t.ACMEnabled { + log.Println("Trunks started with ACM") scheduler := gocron.NewScheduler(time.UTC) - scheduler.Every(1).Seconds().Do(RunACM, Trunks.QoS) + scheduler.Every(1).Seconds().Do(t.RunACM, t.QoS) scheduler.StartBlocking() } else { log.Println("Trunks started without ACM") From 4a5d91552f4da31e9e8ff5030f32369e99458b1a Mon Sep 17 00:00:00 2001 From: Louis Royer Date: Thu, 21 Sep 2023 18:00:53 +0200 Subject: [PATCH 4/7] Check kernel version and disable offset delay if this would crash Closes https://github.com/shynuu/trunks/issues/6 --- main.go | 9 ++++++++- runtime/config.go | 1 + runtime/init.go | 3 ++- runtime/trunks.go | 24 ++++++++++++++++++++++-- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 0004a3b..52e8402 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ func main() { var flush bool = false var acm bool = false var qos bool = false + var disable_kernel_version_check = false var logs string app := &cli.App{ @@ -54,9 +55,15 @@ func main() { Destination: &qos, DefaultText: "not activated", }, + &cli.BoolFlag{ + Name: "disable-kernel-version-check", + Usage: "Disable check for bugged kernel versions", + Destination: &disable_kernel_version_check, + DefaultText: "kernel version check enabled", + }, }, Action: func(c *cli.Context) error { - trunksConfig, err := trunks.InitTrunks(config, qos, logs, acm) + trunksConfig, err := trunks.InitTrunks(config, qos, logs, acm, disable_kernel_version_check) if err != nil { fmt.Println("Init error, exiting...") os.Exit(1) diff --git a/runtime/config.go b/runtime/config.go index cb08fd0..72514f9 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -53,6 +53,7 @@ type TrunksConfig struct { ACMList []*ACM `yaml:"acm"` QoS bool Logs string + KernelVersionCheck bool ACMEnabled bool ACMCounter int ACMIndex int diff --git a/runtime/init.go b/runtime/init.go index 3d3256c..6623111 100644 --- a/runtime/init.go +++ b/runtime/init.go @@ -91,7 +91,7 @@ func (t *TrunksConfig) FindInterfaces() error { } // InitTrunks initialize the trunks module -func InitTrunks(file string, qos bool, logs string, acm bool) (*TrunksConfig, error) { +func InitTrunks(file string, qos bool, logs string, acm bool, disable_kernel_version_check bool) (*TrunksConfig, error) { t, err := ParseConf(file) if err != nil { return nil, err @@ -106,5 +106,6 @@ func InitTrunks(file string, qos bool, logs string, acm bool) (*TrunksConfig, er t.QoS = qos t.Logs = logs t.ACMEnabled = acm + t.KernelVersionCheck = !disable_kernel_version_check return t, nil } diff --git a/runtime/trunks.go b/runtime/trunks.go index d8e0602..3472567 100644 --- a/runtime/trunks.go +++ b/runtime/trunks.go @@ -64,6 +64,26 @@ func (t *TrunksConfig) FlushTables() error { return err } +func (t *TrunksConfig) isKernelVersionBugged() bool { + if t.KernelVersionCheck { + return false + } + // See https://github.com/shynuu/trunks/issues/6 + cmd := exec.Command("uname", "--kernel-version") + out, err := cmd.Output() + if err != nil { + errLog := fmt.Sprintf("Error running %s: %s", cmd.Args[0], err) + log.Println(errLog) + return false + } + // See https://gist.github.com/louisroyer/90636c07dc4b205b813a56de718d9d09 + if strings.Contains(string(out), "Debian 6.1.38-") { + log.Println("Warning: offset delay will be disabled because you are using Debian with Linux 6.1.38 which is known to crash with this settting. See https://github.com/shynuu/trunks/issues/6 for details.") + return true + } + return false +} + // Run the Trunk link func (t *TrunksConfig) Run() { @@ -76,7 +96,7 @@ func (t *TrunksConfig) Run() { retun := fmt.Sprintf("%dmbit", int64(math.Round(t.Bandwidth.Return))) delay := fmt.Sprintf("%dms", int64(math.Round(t.Delay.Value/2))) offset := fmt.Sprintf("%dms", int64(math.Round(t.Delay.Offset/2))) - jitter := t.Delay.Offset > 1 + jitter := t.Delay.Offset > 1 && t.isKernelVersionBugged() // qlen formula: 1.5 * bandwidth[bits/s] * latency[s] / mtu[bits] qlenForward := fmt.Sprintf("%d", int64(math.Round(1.5*(t.Bandwidth.Forward*1000000)*(t.Delay.Value/(2*1000))/(8*1500)))) @@ -117,7 +137,7 @@ func (t *TrunksConfig) Run() { returnRest := fmt.Sprintf("%dmbit", int64(math.Round(t.Bandwidth.Return))-1) delay := fmt.Sprintf("%dms", int64(math.Round(t.Delay.Value/2))) offset := fmt.Sprintf("%dms", int64(math.Round(t.Delay.Offset/2))) - jitter := t.Delay.Offset > 1 + jitter := t.Delay.Offset > 1 && t.isKernelVersionBugged() // qlen formula: 1.5 * bandwidth[bits/s] * latency[s] / mtu[bits] qlenForwardVoIP := fmt.Sprintf("%d", int64(math.Round(1.5*(2*1000000)*(t.Delay.Value/(2*1000))/(8*1500)))) From a15ca40951d564a35d7319507834472df80edd0a Mon Sep 17 00:00:00 2001 From: Louis Royer Date: Fri, 15 Sep 2023 14:33:17 +0200 Subject: [PATCH 5/7] Add known issues section in README --- README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2a84e9b..8079db9 100644 --- a/README.md +++ b/README.md @@ -71,11 +71,13 @@ COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: - --config FILE Load configuration from FILE (default: not set) - --flush Flush IPTABLES table mangle and clear all TC rules (default: false) - --acm Activate the ACM simulation (default: not activated) - --qos Process traffic using QoS (default: not activated) - --help, -h show help (default: false) + --config FILE Load configuration from FILE (default: not set) + --logs value Log path for the log file (default: not set) + --flush Flush IPTABLES table mangle and clear all TC rules (default: false) + --acm Activate the ACM simulation (default: not activated) + --qos Process traffic using QoS (default: not activated) + --disable-kernel-version-check Disable check for bugged kernel versions (default: kernel version check enabled) + --help, -h show help (default: false) ``` @@ -149,4 +151,7 @@ cd container docker-compose up -d ``` -You still must configure the routes inside the client and server containers. \ No newline at end of file +You still must configure the routes inside the client and server containers. + +## Known issues +Some versions of the kernel are known to crash when offset delay is enabled. As a protection, offset delay is by default disabled if you are running on affected versions. You can bypass protection by using the flag `--disable-kernel-version-check`, but this is NOT recommended. See [this issue](https://github.com/shynuu/trunks/issues/6) for details. From 8f56e02161ac381b440210683674369f4fefa970 Mon Sep 17 00:00:00 2001 From: Louis Royer Date: Fri, 15 Sep 2023 14:28:35 +0200 Subject: [PATCH 6/7] Enable bash completions --- bash-completion/completions/trunks | 21 +++++++++++++++++++++ main.go | 5 +++-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100755 bash-completion/completions/trunks diff --git a/bash-completion/completions/trunks b/bash-completion/completions/trunks new file mode 100755 index 0000000..f0f6241 --- /dev/null +++ b/bash-completion/completions/trunks @@ -0,0 +1,21 @@ +#! /bin/bash + +: ${PROG:=$(basename ${BASH_SOURCE})} + +_cli_bash_autocomplete() { + if [[ "${COMP_WORDS[0]}" != "source" ]]; then + local cur opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + if [[ "$cur" == "-"* ]]; then + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) + else + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) + fi + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi +} + +complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG +unset PROG diff --git a/main.go b/main.go index 52e8402..b4ffb9f 100644 --- a/main.go +++ b/main.go @@ -18,8 +18,9 @@ func main() { var logs string app := &cli.App{ - Name: "trunks", - Usage: "a simple DVB-S2/DVB-RCS2 simulator", + Name: "trunks", + Usage: "a simple DVB-S2/DVB-RCS2 simulator", + EnableBashCompletion: true, Authors: []*cli.Author{ {Name: "Youssouf Drif"}, }, From 485ed9e613d573ee01c675cbadf5c7bf573fc876 Mon Sep 17 00:00:00 2001 From: Louis Royer Date: Fri, 15 Sep 2023 14:41:22 +0200 Subject: [PATCH 7/7] Replace long sleep by a select{}, add signal handling - `select {}` is the idiomatic way of doing nothing - Signal handling will avoid waiting 10s after a `docker compose down` if you have not set `init: true` --- main.go | 12 +++++++++++- runtime/trunks.go | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index b4ffb9f..a82bc6a 100644 --- a/main.go +++ b/main.go @@ -4,11 +4,21 @@ import ( "fmt" "log" "os" + "os/signal" + "syscall" trunks "github.com/shynuu/trunks/runtime" "github.com/urfave/cli/v2" ) +// Initialize signals handling +func initSignals() { + cancelChan := make(chan os.Signal, 1) + signal.Notify(cancelChan, syscall.SIGTERM, syscall.SIGINT) + func(_ os.Signal) {}(<-cancelChan) + os.Exit(0) +} + func main() { var config string var flush bool = false @@ -82,7 +92,7 @@ func main() { return nil }, } - + go initSignals() err := app.Run(os.Args) if err != nil { log.Fatal(err) diff --git a/runtime/trunks.go b/runtime/trunks.go index 3472567..6813542 100644 --- a/runtime/trunks.go +++ b/runtime/trunks.go @@ -203,7 +203,7 @@ func (t *TrunksConfig) Run() { scheduler.StartBlocking() } else { log.Println("Trunks started without ACM") - time.Sleep(time.Duration(1<<63 - 1)) + select {} } }