diff --git a/cmd/api/v5/cmd.go b/cmd/api/v5/cmd.go index 0acd4bb..a82729b 100755 --- a/cmd/api/v5/cmd.go +++ b/cmd/api/v5/cmd.go @@ -29,6 +29,7 @@ func Commands(app *api.App) { app.Before = Before User{}.Commands(app) ACL{}.Commands(app) + Qos{}.Commands(app) Device{}.Commands(app) Lease{}.Commands(app) Config{}.Commands(app) diff --git a/cmd/api/v5/config.go b/cmd/api/v5/config.go index 2dea597..1923d4d 100755 --- a/cmd/api/v5/config.go +++ b/cmd/api/v5/config.go @@ -91,6 +91,20 @@ func (u Config) Check(c *cli.Context) error { } } } + + out.Info("%15s: %s", "check", "qos") + pattern = filepath.Join(dir, "switch", "qos", "*.json") + if files, err := filepath.Glob(pattern); err == nil { + for _, file := range files { + obj := &config.Qos{} + if err := libol.UnmarshalLoad(obj, file); err != nil { + out.Warn("%15s: %s", filepath.Base(file), err) + } else { + out.Info("%15s: %s", filepath.Base(file), "success") + } + } + } + // Check ACL configurations. out.Info("%15s: %s", "check", "acl") pattern = filepath.Join(dir, "switch", "acl", "*.json") diff --git a/cmd/api/v5/qos.go b/cmd/api/v5/qos.go new file mode 100644 index 0000000..c4d3cb5 --- /dev/null +++ b/cmd/api/v5/qos.go @@ -0,0 +1,142 @@ +package v5 + +import ( + "github.com/luscis/openlan/cmd/api" + "github.com/luscis/openlan/pkg/schema" + "github.com/urfave/cli/v2" +) + +type Qos struct { + Cmd +} + +func (q Qos) Commands(app *api.App) { + rule := QosRule{} + app.Command(&cli.Command{ + Name: "qos", + Usage: "qos for client in network", + Flags: []cli.Flag{ + &cli.StringFlag{Name: "name", Aliases: []string{"n"}}, + }, + Subcommands: []*cli.Command{ + rule.Commands(), + }, + }) +} + +type QosRule struct { + Cmd +} + +func (qr QosRule) Url(prefix, name string) string { + return prefix + "/api/network/" + name + "/qos" +} + +func (qr QosRule) Add(c *cli.Context) error { + name := c.String("name") + url := qr.Url(c.String("url"), name) + + rule := &schema.Qos{ + Name: c.String("clientname"), + InSpeed: c.Int64("inspeed"), + OutSpeed: c.Int64("outspeed"), + } + + clt := qr.NewHttp(c.String("token")) + if err := clt.PostJSON(url, rule, nil); err != nil { + return err + } + + return nil +} + +func (qr QosRule) Remove(c *cli.Context) error { + name := c.String("name") + url := qr.Url(c.String("url"), name) + + rule := &schema.Qos{ + Name: c.String("clientname"), + } + + clt := qr.NewHttp(c.String("token")) + if err := clt.DeleteJSON(url, rule, nil); err != nil { + return err + } + + return nil +} + +func (qr QosRule) Tmpl() string { + return `# total {{ len . }} +{{ps -15 "Name"}} {{ps -15 "Device"}} {{ps -15 "ip"}} {{ps -8 "InSpeed"}} {{ps -8 "OutSpeed"}} +{{- range . }} +{{ps -15 .Name}} {{ps -15 .Device}} {{ps -15 .Ip}} {{pi -8 .InSpeed}} {{pi -8 .OutSpeed}} +{{- end }} +` +} + +func (qr QosRule) List(c *cli.Context) error { + name := c.String("name") + + url := qr.Url(c.String("url"), name) + clt := qr.NewHttp(c.String("token")) + + var items []schema.Qos + if err := clt.GetJSON(url, &items); err != nil { + return err + } + + return qr.Out(items, c.String("format"), qr.Tmpl()) +} + +func (qr QosRule) Save(c *cli.Context) error { + name := c.String("name") + url := qr.Url(c.String("url"), name) + + clt := qr.NewHttp(c.String("token")) + if err := clt.PutJSON(url, nil, nil); err != nil { + return err + } + + return nil +} + +func (qr QosRule) Commands() *cli.Command { + return &cli.Command{ + Name: "rule", + Usage: "Access Control Qos Rule", + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "Add a new qos rule for client", + Flags: []cli.Flag{ + &cli.StringFlag{Name: "clientname", Aliases: []string{"cn"}}, + &cli.StringFlag{Name: "inspeed", Aliases: []string{"is"}}, + &cli.StringFlag{Name: "outspeed", Aliases: []string{"os"}}, + }, + Action: qr.Add, + }, + { + Name: "remove", + Usage: "remove a qos rule", + Aliases: []string{"rm"}, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "clientname", Aliases: []string{"cn"}}, + }, + Action: qr.Remove, + }, + { + Name: "list", + Usage: "Display all qos rules", + Aliases: []string{"ls"}, + Action: qr.List, + }, + { + Name: "save", + Usage: "Save all qos rules", + Aliases: []string{"sa"}, + Action: qr.Save, + }, + }, + } +} diff --git a/dist/rootfs/etc/openlan/switch/qos/example.json.example b/dist/rootfs/etc/openlan/switch/qos/example.json.example new file mode 100644 index 0000000..3bbb95d --- /dev/null +++ b/dist/rootfs/etc/openlan/switch/qos/example.json.example @@ -0,0 +1,9 @@ +{ + "name": "example", + "qos":{ + "hi@example": { + "inSpeed": 125000, + "outSpeed":125000 + } + } +} \ No newline at end of file diff --git a/pkg/api/api.go b/pkg/api/api.go index b56af21..451b996 100755 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -47,6 +47,14 @@ type ZTruster interface { ListKnock(name string, call func(obj schema.KnockRule)) } +type Qoser interface { + AddQosUser(name string, inSpeed int64, outSpeed int64) error + UpdateQosUser(name string, inSpeed int64, outSpeed int64) error + DelQosUser(name string) error + ListQosUsers(call func(obj schema.Qos)) + Save() +} + type Networker interface { String() string ID() string @@ -59,6 +67,7 @@ type Networker interface { Reload(v Switcher) Provider() string ZTruster() ZTruster + Qoser() Qoser IfAddr() string ACLer() ACLer } diff --git a/pkg/api/qos.go b/pkg/api/qos.go new file mode 100644 index 0000000..f780ffd --- /dev/null +++ b/pkg/api/qos.go @@ -0,0 +1,108 @@ +package api + +import ( + "github.com/gorilla/mux" + "github.com/luscis/openlan/pkg/schema" + "net/http" +) + +type QosApi struct { +} + +func (h QosApi) Router(router *mux.Router) { + router.HandleFunc("/api/network/{id}/qos", h.List).Methods("GET") + router.HandleFunc("/api/network/{id}/qos", h.Add).Methods("POST") + router.HandleFunc("/api/network/{id}/qos", h.Del).Methods("DELETE") + router.HandleFunc("/api/network/{id}/qos", h.Save).Methods("PUT") +} + +func (h QosApi) List(w http.ResponseWriter, r *http.Request) { + + qosList := make([]schema.Qos, 0, 1024) + vars := mux.Vars(r) + id := vars["id"] + + worker := GetWorker(id) + if worker == nil { + http.Error(w, "Network not found", http.StatusInternalServerError) + return + } + + var qos = worker.Qoser() + qos.ListQosUsers(func(obj schema.Qos) { + qosList = append(qosList, obj) + }) + + ResponseJson(w, qosList) +} + +func (h QosApi) Add(w http.ResponseWriter, r *http.Request) { + + qos := &schema.Qos{} + if err := GetData(r, qos); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + vars := mux.Vars(r) + id := vars["id"] + + worker := GetWorker(id) + if worker == nil { + http.Error(w, "Network not found", http.StatusInternalServerError) + return + } + + if qos != nil { + if err := worker.Qoser().AddQosUser(qos.Name, qos.InSpeed, qos.OutSpeed); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + ResponseJson(w, true) + } else { + http.Error(w, vars["id"], http.StatusNotFound) + } +} + +func (h QosApi) Del(w http.ResponseWriter, r *http.Request) { + + qos := &schema.Qos{} + if err := GetData(r, qos); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + vars := mux.Vars(r) + id := vars["id"] + + worker := GetWorker(id) + if worker == nil { + http.Error(w, "Network not found", http.StatusInternalServerError) + return + } + + if qos != nil { + if err := worker.Qoser().DelQosUser(qos.Name); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + ResponseJson(w, true) + } else { + http.Error(w, vars["id"], http.StatusNotFound) + } +} + +func (h QosApi) Save(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id := vars["id"] + + worker := GetWorker(id) + if worker == nil { + http.Error(w, "Network not found", http.StatusInternalServerError) + return + } + qos := worker.Qoser() + qos.Save() + + ResponseJson(w, "success") +} diff --git a/pkg/api/url.go b/pkg/api/url.go index 6316ce0..b4d0b76 100755 --- a/pkg/api/url.go +++ b/pkg/api/url.go @@ -23,6 +23,7 @@ func Add(router *mux.Router, switcher Switcher) { Log{}.Router(router) OpenAPI{}.Router(router) ZTrust{}.Router(router) + QosApi{}.Router(router) Output{}.Router(router) ACL{}.Router(router) } diff --git a/pkg/cache/qos.go b/pkg/cache/qos.go new file mode 100644 index 0000000..6281f42 --- /dev/null +++ b/pkg/cache/qos.go @@ -0,0 +1,13 @@ +package cache + +import ( + "github.com/luscis/openlan/pkg/libol" +) + +type qos struct { + QosConfig *libol.SafeStrMap +} + +var pos = &qos{ + QosConfig: libol.NewSafeStrMap(1024), +} diff --git a/pkg/config/config.go b/pkg/config/config.go index a62b6ec..599f5d9 100755 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -10,6 +10,10 @@ func GetAcl(name string) *ACL { return switcher.GetACL(name) } +func GetQos(name string) *Qos { + return switcher.GetQos(name) +} + func Update(obj *Switch) { switcher = obj } diff --git a/pkg/config/network.go b/pkg/config/network.go index 132b161..f3ed247 100755 --- a/pkg/config/network.go +++ b/pkg/config/network.go @@ -24,6 +24,7 @@ type Network struct { Dhcp string `json:"dhcp,omitempty"` Outputs []Output `json:"outputs"` ZTrust string `json:"ztrust"` + Qos string `json:"qos,omitempty"` Namespace string `json:"namespace"` } diff --git a/pkg/config/qos.go b/pkg/config/qos.go new file mode 100644 index 0000000..aabcd72 --- /dev/null +++ b/pkg/config/qos.go @@ -0,0 +1,23 @@ +package config + +import "github.com/luscis/openlan/pkg/libol" + +type Qos struct { + File string `json:"file"` + Name string `json:"name"` + Config map[string]*QosLimit `json:"qos,omitempty"` +} + +func (q *Qos) Save() { + if err := libol.MarshalSave(q, q.File, true); err != nil { + libol.Error("Switch.Save.Qos %s %s", q.Name, err) + } +} + +type QosLimit struct { + InSpeed int64 `json:"inSpeed,omitempty"` + OutSpeed int64 `json:"outSpeed,omitempty"` +} + +func (ql *QosLimit) Correct() { +} diff --git a/pkg/config/switch.go b/pkg/config/switch.go index 4b8f69e..fa30deb 100755 --- a/pkg/config/switch.go +++ b/pkg/config/switch.go @@ -62,6 +62,7 @@ type Switch struct { Crypt *Crypt `json:"crypt,omitempty"` Network []*Network `json:"network,omitempty"` Acl map[string]*ACL `json:"acl,omitempty"` + Qos map[string]*Qos `json:"qos,omitempty"` FireWall []FlowRule `json:"firewall,omitempty"` Queue Queue `json:"queue"` PassFile string `json:"password"` @@ -75,6 +76,7 @@ type Switch struct { func NewSwitch() *Switch { s := &Switch{ Acl: make(map[string]*ACL, 32), + Qos: make(map[string]*Qos, 1024), } s.Parse() s.Initialize() @@ -100,6 +102,7 @@ func (s *Switch) Initialize() { func (s *Switch) LoadExt() { s.LoadAcl() + s.LoadQos() s.LoadNetwork() } @@ -206,6 +209,33 @@ func (s *Switch) LoadNetwork() { } } +func (s *Switch) LoadQos() { + files, err := filepath.Glob(s.Dir("qos", "*.json")) + if err != nil { + libol.Error("Switch.LoadQos %s", err) + } + + for _, k := range files { + obj := &Qos{ + File: k, + } + if err := libol.UnmarshalLoad(obj, k); err != nil { + libol.Error("Switch.LoadQos %s", err) + continue + } + + s.Qos[obj.Name] = obj + } + for _, obj := range s.Qos { + for _, rule := range obj.Config { + rule.Correct() + } + if obj.File == "" { + obj.File = s.Dir("acl", obj.Name+".json") + } + } +} + func (s *Switch) LoadAcl() { files, err := filepath.Glob(s.Dir("acl", "*.json")) if err != nil { @@ -231,14 +261,22 @@ func (s *Switch) Load() error { func (s *Switch) Save() { tmp := *s tmp.Acl = nil + tmp.Qos = nil tmp.Network = nil if err := libol.MarshalSave(&tmp, tmp.File, true); err != nil { libol.Error("Switch.Save %s", err) } s.SaveAcl() + s.SaveQos() s.SaveNetwork() } +func (s *Switch) SaveQos() { + for _, obj := range s.Qos { + obj.Save() + } +} + func (s *Switch) SaveAcl() { for _, obj := range s.Acl { obj.Save() @@ -269,3 +307,7 @@ func (s *Switch) GetNetwork(name string) *Network { func (s *Switch) GetACL(name string) *ACL { return s.Acl[name] } + +func (s *Switch) GetQos(name string) *Qos { + return s.Qos[name] +} diff --git a/pkg/network/iptables.go b/pkg/network/iptables.go index 77aa380..9aa646e 100755 --- a/pkg/network/iptables.go +++ b/pkg/network/iptables.go @@ -28,33 +28,35 @@ const ( ) type IPRule struct { - Table string - Chain string - Source string - SrcSet string - ToSource string - NoSource string - NoSrcSet string - Dest string - DestSet string - ToDest string - NoDest string - NoDestSet string - Proto string - DstPort string - SrcPort string - Input string - Output string - Comment string - Jump string - SetMss int - Mark uint32 - SetMark uint32 - Zone uint32 - Order string - Match string - CtState string - TcpFlag []string + Table string + Chain string + Source string + SrcSet string + ToSource string + NoSource string + NoSrcSet string + Dest string + DestSet string + ToDest string + NoDest string + NoDestSet string + Proto string + DstPort string + SrcPort string + Input string + Output string + Comment string + Jump string + Limit string + LimitBurst string + SetMss int + Mark uint32 + SetMark uint32 + Zone uint32 + Order string + Match string + CtState string + TcpFlag []string } type IPRules []IPRule @@ -131,6 +133,13 @@ func (ru IPRule) Args() []string { args = append(args, "-m", "comment", "--comment", ru.Comment) } + if ru.Limit != "" { + args = append(args, "-m", "limit", "--limit", ru.Limit) + } + if ru.LimitBurst != "" { + args = append(args, "--limit-burst", ru.LimitBurst) + } + if ru.Jump != "" { jump := strings.ToUpper(ru.Jump) if jump == "ACCEPT" || jump == "DROP" { diff --git a/pkg/schema/qos.go b/pkg/schema/qos.go new file mode 100644 index 0000000..7f1c9df --- /dev/null +++ b/pkg/schema/qos.go @@ -0,0 +1,9 @@ +package schema + +type Qos struct { + Name string `json:"name"` + Device string `json:"device"` + Ip string `json:"ip"` + InSpeed int64 `json:"inSpeed"` + OutSpeed int64 `json:"outSpeed"` +} diff --git a/pkg/switch/network.go b/pkg/switch/network.go index 9b001b8..5f0f1c9 100755 --- a/pkg/switch/network.go +++ b/pkg/switch/network.go @@ -53,6 +53,7 @@ type WorkerImpl struct { setV *cn.IPSet vpn *OpenVPN ztrust *ZTrust + qos *QosCtrl vrf *cn.VRF table int br cn.Bridger @@ -126,6 +127,9 @@ func (w *WorkerImpl) Initialize() { w.ztrust.Initialize() } + w.qos = NewQosCtrl(cfg.Name) + w.qos.Initialize() + if cfg.Dhcp == "enable" { name := cfg.Bridge.Name if w.br != nil { @@ -399,6 +403,21 @@ func (w *WorkerImpl) Start(v api.Switcher) { Comment: "Goto Zero Trust", }) } + + if !(w.qos == nil) { + w.qos.Start() + + fire.Mangle.In.AddRule(cn.IPRule{ + Input: vpn.Device, + Jump: w.qos.ChainIn(), + Comment: "Goto Qos ChainIn", + }) + fire.Mangle.Out.AddRule(cn.IPRule{ + Output: vpn.Device, + Jump: w.qos.ChainOut(), + Comment: "Goto Qos ChainOut", + }) + } } fire.Start() @@ -486,7 +505,12 @@ func (w *WorkerImpl) Stop() { if !(w.ztrust == nil) { w.ztrust.Stop() } + if !(w.qos == nil) { + w.qos.Stop() + } + w.vpn.Stop() + } if !(w.dhcp == nil) { @@ -781,6 +805,10 @@ func (w *WorkerImpl) ZTruster() api.ZTruster { return w.ztrust } +func (w *WorkerImpl) Qoser() api.Qoser { + return w.qos +} + func (w *WorkerImpl) IfAddr() string { return strings.SplitN(w.cfg.Bridge.Address, "/", 2)[0] } diff --git a/pkg/switch/qos.go b/pkg/switch/qos.go new file mode 100644 index 0000000..93ccbda --- /dev/null +++ b/pkg/switch/qos.go @@ -0,0 +1,418 @@ +package cswitch + +import ( + "github.com/luscis/openlan/pkg/cache" + "github.com/luscis/openlan/pkg/config" + "github.com/luscis/openlan/pkg/libol" + cn "github.com/luscis/openlan/pkg/network" + "github.com/luscis/openlan/pkg/schema" + "strconv" + "strings" + "sync" + "time" +) + +//125000 ~ 1Mb/s + +type QosUser struct { + QosChainName string + InSpeed int64 // bits + OutSpeed int64 // bits + Name string + Ip string + Device string + qosChainIn *cn.FireWallChain + qosChainOut *cn.FireWallChain + out *libol.SubLogger +} + +func (qr *QosUser) RuleName(dir string) string { + nameParts := strings.Split(qr.Name, "@") + return "Qos_" + qr.QosChainName + "-" + dir + "-" + nameParts[0] +} + +func (qr *QosUser) InLimitPacket() string { + //bytes / mtu + return strconv.Itoa(int(qr.InSpeed / 1500)) +} + +func (qr *QosUser) OutLimitPacket() string { + //bytes / mtu + return strconv.Itoa(int(qr.OutSpeed / 1500)) +} + +func (qr *QosUser) InLimitStr() string { + //bytes / mtu + return qr.InLimitPacket() + "/s" +} +func (qr *QosUser) OutLimitStr() string { + //bytes / mtu + return qr.OutLimitPacket() + "/s" +} + +func (qr *QosUser) InLimitRule() cn.IPRule { + return cn.IPRule{ + Limit: qr.InLimitStr(), + //LimitBurst: qr.InLimitPacket(), + Comment: "Qos Limit In " + qr.Name, + Jump: "ACCEPT", + } +} + +func (qr *QosUser) OutLimitRule() cn.IPRule { + return cn.IPRule{ + Limit: qr.OutLimitStr(), + Comment: "Qos Limit Out " + qr.Name, + Jump: "ACCEPT", + } +} + +func (qr *QosUser) BuildChainOut(chain *cn.FireWallChain) { + if qr.OutSpeed > 0 { + qr.qosChainOut = cn.NewFireWallChain(qr.RuleName("out"), cn.TMangle, "") + qr.qosChainOut.AddRule(qr.OutLimitRule()) + qr.qosChainOut.AddRule(cn.IPRule{ + Comment: "Qos Default Drop", + Jump: "DROP", + }) + qr.qosChainOut.Install() + + qr.BuildChainOutJump(chain) + } +} + +func (qr *QosUser) BuildChainOutJump(chain *cn.FireWallChain) { + if qr.Ip != "" { + if err := chain.AddRuleX(cn.IPRule{ + Comment: "Qos Jump", + Jump: qr.RuleName("out"), + Dest: qr.Ip, + }); err != nil { + qr.out.Warn("Qos.Add Out Rule: %s", err) + } + } +} + +func (qr *QosUser) ClearChainOutJump(chain *cn.FireWallChain) { + if err := chain.DelRuleX(cn.IPRule{ + Comment: "Qos Jump", + Jump: qr.RuleName("out"), + Dest: qr.Ip, + }); err != nil { + qr.out.Warn("Qos.Del Out Rule: %s", err) + } +} + +func (qr *QosUser) BuildChainIn(chain *cn.FireWallChain) { + if qr.InSpeed > 0 { + qr.qosChainIn = cn.NewFireWallChain(qr.RuleName("in"), cn.TMangle, "") + qr.qosChainIn.AddRule(qr.InLimitRule()) + qr.qosChainIn.AddRule(cn.IPRule{ + Comment: "Qos Default Drop", + Jump: "DROP", + }) + qr.qosChainIn.Install() + + qr.BuildChainInJump(chain) + } +} + +func (qr *QosUser) BuildChainInJump(chain *cn.FireWallChain) { + if qr.Ip != "" { + if err := chain.AddRuleX(cn.IPRule{ + Comment: "Qos Jump", + Jump: qr.RuleName("in"), + Source: qr.Ip, + }); err != nil { + qr.out.Warn("Qos.Add In Rule: %s", err) + } + } +} + +func (qr *QosUser) ClearChainInJump(chain *cn.FireWallChain) { + if err := chain.DelRuleX(cn.IPRule{ + Comment: "Qos Jump", + Jump: qr.RuleName("in"), + Source: qr.Ip, + }); err != nil { + qr.out.Warn("Qos.Del In Rule: %s", err) + } +} + +func (qr *QosUser) Start(chainIn *cn.FireWallChain, chainOut *cn.FireWallChain) { + qr.BuildChainIn(chainIn) + qr.BuildChainOut(chainOut) +} + +func (qr *QosUser) ReBuild(chainIn *cn.FireWallChain, chainOut *cn.FireWallChain) { + qr.Clear(chainIn, chainOut) + qr.Start(chainIn, chainOut) +} + +func (qr *QosUser) ClearChainIn(chain *cn.FireWallChain) { + if qr.qosChainIn != nil { + qr.ClearChainInJump(chain) + qr.qosChainIn.Cancel() + qr.qosChainIn = nil + } +} +func (qr *QosUser) ClearChainOut(chain *cn.FireWallChain) { + if qr.qosChainOut != nil { + qr.ClearChainOutJump(chain) + qr.qosChainOut.Cancel() + qr.qosChainOut = nil + } +} +func (qr *QosUser) Clear(chainIn *cn.FireWallChain, chainOut *cn.FireWallChain) { + qr.ClearChainIn(chainIn) + qr.ClearChainOut(chainOut) +} + +func (qr *QosUser) Update(chainIn *cn.FireWallChain, chainOut *cn.FireWallChain, inSpeed int64, outSpeed int64, device string, ip string) { + + ipChange := false + if qr.Ip != ip { + ipChange = true + qr.Ip = ip + } + + if ipChange { + qr.ClearChainInJump(chainIn) + qr.ClearChainOutJump(chainOut) + qr.BuildChainOutJump(chainOut) + qr.BuildChainInJump(chainIn) + } + + if qr.InSpeed != inSpeed { + qr.InSpeed = inSpeed + qr.ClearChainIn(chainIn) + qr.BuildChainIn(chainIn) + } + + if qr.OutSpeed != outSpeed { + qr.OutSpeed = outSpeed + qr.ClearChainOut(chainOut) + qr.BuildChainOut(chainOut) + } + +} + +type QosCtrl struct { + Name string + Rules map[string]*QosUser + chainIn *cn.FireWallChain + chainOut *cn.FireWallChain + out *libol.SubLogger + lock sync.Mutex +} + +func NewQosCtrl(name string) *QosCtrl { + return &QosCtrl{ + Name: name, + Rules: make(map[string]*QosUser, 1024), + out: libol.NewSubLogger("Qos"), + } +} + +func (q *QosCtrl) ChainIn() string { + return "Qos_" + q.Name + "-in" +} + +func (q *QosCtrl) ChainOut() string { + return "Qos_" + q.Name + "-out" +} + +func (q *QosCtrl) Initialize() { + //q.Start() + q.chainIn = cn.NewFireWallChain(q.ChainIn(), cn.TMangle, "") + q.chainOut = cn.NewFireWallChain(q.ChainOut(), cn.TMangle, "") + + qosCfg := config.GetQos(q.Name) + + if qosCfg != nil && len(qosCfg.Config) > 0 { + for name, limit := range qosCfg.Config { + qr := &QosUser{ + QosChainName: q.Name, + Name: name, + InSpeed: limit.InSpeed, + OutSpeed: limit.OutSpeed, + Ip: "", + out: libol.NewSubLogger("Qos_" + name), + } + q.Rules[name] = qr + } + + } +} + +func (q *QosCtrl) Start() { + q.out.Info("Qos.Start") + q.chainIn.Install() + q.chainOut.Install() + + if len(q.Rules) > 0 { + for _, rule := range q.Rules { + rule.Start(q.chainIn, q.chainOut) + } + } + + libol.Go(q.Update) +} + +func (q *QosCtrl) Stop() { + q.out.Info("Qos.Stop") + if len(q.Rules) != 0 { + for _, rule := range q.Rules { + rule.Clear(q.chainIn, q.chainOut) + } + } + + q.chainIn.Cancel() + q.chainOut.Cancel() +} + +func (q *QosCtrl) DelUserRule(name string) { + q.lock.Lock() + defer q.lock.Unlock() + if rule, ok := q.Rules[name]; ok { + rule.Clear(q.chainIn, q.chainOut) + delete(q.Rules, name) + } +} + +func (q *QosCtrl) FindClient(name string) *schema.VPNClient { + for n := range cache.Network.List() { + if n == nil { + break + } + for client := range cache.VPNClient.List(n.Name) { + if client == nil { + break + } + if client.Name == name { + return client + } + } + } + + return nil +} + +func (q *QosCtrl) AddOrUpdateQosUser(name string, inSpeed int64, outSpeed int64) { + q.lock.Lock() + defer q.lock.Unlock() + client := q.FindClient(name) + device := "" + var ip = "" + if client != nil { + device = client.Device + ip = client.Address + } + + if rule, ok := q.Rules[name]; ok { + + rule.Update(q.chainIn, q.chainOut, inSpeed, outSpeed, device, ip) + } else { + + rule = &QosUser{ + QosChainName: q.Name, + Name: name, + InSpeed: inSpeed, + OutSpeed: outSpeed, + Ip: ip, + out: libol.NewSubLogger("Qos_" + name), + } + rule.Start(q.chainIn, q.chainOut) + + q.Rules[name] = rule + } +} + +func (q *QosCtrl) ClientUpdate() { + clients := make([]schema.VPNClient, 0, 1024) + for n := range cache.Network.List() { + if n == nil { + break + } + for client := range cache.VPNClient.List(n.Name) { + if client == nil { + break + } + clients = append(clients, *client) + } + } + for _, rule := range q.Rules { + var existClient *schema.VPNClient + for _, client := range clients { + if client.Name == rule.Name { + existClient = &client + break + } + } + if existClient != nil { + rule.Update(q.chainIn, q.chainOut, rule.InSpeed, rule.OutSpeed, existClient.Device, existClient.Address) + } else { + if rule.Ip != "" { + rule.ClearChainInJump(q.chainIn) + rule.ClearChainOutJump(q.chainOut) + rule.Ip = "" + } + } + } + +} + +func (q *QosCtrl) Update() { + + for { + q.ClientUpdate() + + time.Sleep(time.Second * 5) + } + +} + +func (q *QosCtrl) Save() { + cfg := config.GetQos(q.Name) + cfg.Config = make(map[string]*config.QosLimit, 1024) + for _, rule := range q.Rules { + ql := &config.QosLimit{ + InSpeed: rule.InSpeed, + OutSpeed: rule.OutSpeed, + } + cfg.Config[rule.Name] = ql + } + cfg.Save() +} + +func (q *QosCtrl) AddQosUser(name string, inSpeed int64, outSpeed int64) error { + + q.AddOrUpdateQosUser(name, inSpeed, outSpeed) + + return nil +} +func (q *QosCtrl) UpdateQosUser(name string, inSpeed int64, outSpeed int64) error { + + q.AddOrUpdateQosUser(name, inSpeed, outSpeed) + + return nil +} +func (q *QosCtrl) DelQosUser(name string) error { + + q.DelUserRule(name) + return nil +} + +func (q *QosCtrl) ListQosUsers(call func(obj schema.Qos)) { + + for _, rule := range q.Rules { + obj := schema.Qos{ + Name: rule.Name, + Device: rule.Device, + InSpeed: rule.InSpeed, + OutSpeed: rule.OutSpeed, + Ip: rule.Ip, + } + call(obj) + } +}