fea: add qos support for client

This commit is contained in:
teddyzhu15
2024-03-28 11:45:49 +08:00
parent 933f708e02
commit 10867d5921
16 changed files with 858 additions and 27 deletions

View File

@@ -29,6 +29,7 @@ func Commands(app *api.App) {
app.Before = Before app.Before = Before
User{}.Commands(app) User{}.Commands(app)
ACL{}.Commands(app) ACL{}.Commands(app)
Qos{}.Commands(app)
Device{}.Commands(app) Device{}.Commands(app)
Lease{}.Commands(app) Lease{}.Commands(app)
Config{}.Commands(app) Config{}.Commands(app)

View File

@@ -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. // Check ACL configurations.
out.Info("%15s: %s", "check", "acl") out.Info("%15s: %s", "check", "acl")
pattern = filepath.Join(dir, "switch", "acl", "*.json") pattern = filepath.Join(dir, "switch", "acl", "*.json")

142
cmd/api/v5/qos.go Normal file
View File

@@ -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,
},
},
}
}

View File

@@ -0,0 +1,9 @@
{
"name": "example",
"qos":{
"hi@example": {
"inSpeed": 125000,
"outSpeed":125000
}
}
}

View File

@@ -47,6 +47,14 @@ type ZTruster interface {
ListKnock(name string, call func(obj schema.KnockRule)) 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 { type Networker interface {
String() string String() string
ID() string ID() string
@@ -59,6 +67,7 @@ type Networker interface {
Reload(v Switcher) Reload(v Switcher)
Provider() string Provider() string
ZTruster() ZTruster ZTruster() ZTruster
Qoser() Qoser
IfAddr() string IfAddr() string
ACLer() ACLer ACLer() ACLer
} }

108
pkg/api/qos.go Normal file
View File

@@ -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")
}

View File

@@ -23,6 +23,7 @@ func Add(router *mux.Router, switcher Switcher) {
Log{}.Router(router) Log{}.Router(router)
OpenAPI{}.Router(router) OpenAPI{}.Router(router)
ZTrust{}.Router(router) ZTrust{}.Router(router)
QosApi{}.Router(router)
Output{}.Router(router) Output{}.Router(router)
ACL{}.Router(router) ACL{}.Router(router)
} }

13
pkg/cache/qos.go vendored Normal file
View File

@@ -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),
}

View File

@@ -10,6 +10,10 @@ func GetAcl(name string) *ACL {
return switcher.GetACL(name) return switcher.GetACL(name)
} }
func GetQos(name string) *Qos {
return switcher.GetQos(name)
}
func Update(obj *Switch) { func Update(obj *Switch) {
switcher = obj switcher = obj
} }

View File

@@ -24,6 +24,7 @@ type Network struct {
Dhcp string `json:"dhcp,omitempty"` Dhcp string `json:"dhcp,omitempty"`
Outputs []Output `json:"outputs"` Outputs []Output `json:"outputs"`
ZTrust string `json:"ztrust"` ZTrust string `json:"ztrust"`
Qos string `json:"qos,omitempty"`
Namespace string `json:"namespace"` Namespace string `json:"namespace"`
} }

23
pkg/config/qos.go Normal file
View File

@@ -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() {
}

View File

@@ -62,6 +62,7 @@ type Switch struct {
Crypt *Crypt `json:"crypt,omitempty"` Crypt *Crypt `json:"crypt,omitempty"`
Network []*Network `json:"network,omitempty"` Network []*Network `json:"network,omitempty"`
Acl map[string]*ACL `json:"acl,omitempty"` Acl map[string]*ACL `json:"acl,omitempty"`
Qos map[string]*Qos `json:"qos,omitempty"`
FireWall []FlowRule `json:"firewall,omitempty"` FireWall []FlowRule `json:"firewall,omitempty"`
Queue Queue `json:"queue"` Queue Queue `json:"queue"`
PassFile string `json:"password"` PassFile string `json:"password"`
@@ -75,6 +76,7 @@ type Switch struct {
func NewSwitch() *Switch { func NewSwitch() *Switch {
s := &Switch{ s := &Switch{
Acl: make(map[string]*ACL, 32), Acl: make(map[string]*ACL, 32),
Qos: make(map[string]*Qos, 1024),
} }
s.Parse() s.Parse()
s.Initialize() s.Initialize()
@@ -100,6 +102,7 @@ func (s *Switch) Initialize() {
func (s *Switch) LoadExt() { func (s *Switch) LoadExt() {
s.LoadAcl() s.LoadAcl()
s.LoadQos()
s.LoadNetwork() 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() { func (s *Switch) LoadAcl() {
files, err := filepath.Glob(s.Dir("acl", "*.json")) files, err := filepath.Glob(s.Dir("acl", "*.json"))
if err != nil { if err != nil {
@@ -231,14 +261,22 @@ func (s *Switch) Load() error {
func (s *Switch) Save() { func (s *Switch) Save() {
tmp := *s tmp := *s
tmp.Acl = nil tmp.Acl = nil
tmp.Qos = nil
tmp.Network = nil tmp.Network = nil
if err := libol.MarshalSave(&tmp, tmp.File, true); err != nil { if err := libol.MarshalSave(&tmp, tmp.File, true); err != nil {
libol.Error("Switch.Save %s", err) libol.Error("Switch.Save %s", err)
} }
s.SaveAcl() s.SaveAcl()
s.SaveQos()
s.SaveNetwork() s.SaveNetwork()
} }
func (s *Switch) SaveQos() {
for _, obj := range s.Qos {
obj.Save()
}
}
func (s *Switch) SaveAcl() { func (s *Switch) SaveAcl() {
for _, obj := range s.Acl { for _, obj := range s.Acl {
obj.Save() obj.Save()
@@ -269,3 +307,7 @@ func (s *Switch) GetNetwork(name string) *Network {
func (s *Switch) GetACL(name string) *ACL { func (s *Switch) GetACL(name string) *ACL {
return s.Acl[name] return s.Acl[name]
} }
func (s *Switch) GetQos(name string) *Qos {
return s.Qos[name]
}

View File

@@ -28,33 +28,35 @@ const (
) )
type IPRule struct { type IPRule struct {
Table string Table string
Chain string Chain string
Source string Source string
SrcSet string SrcSet string
ToSource string ToSource string
NoSource string NoSource string
NoSrcSet string NoSrcSet string
Dest string Dest string
DestSet string DestSet string
ToDest string ToDest string
NoDest string NoDest string
NoDestSet string NoDestSet string
Proto string Proto string
DstPort string DstPort string
SrcPort string SrcPort string
Input string Input string
Output string Output string
Comment string Comment string
Jump string Jump string
SetMss int Limit string
Mark uint32 LimitBurst string
SetMark uint32 SetMss int
Zone uint32 Mark uint32
Order string SetMark uint32
Match string Zone uint32
CtState string Order string
TcpFlag []string Match string
CtState string
TcpFlag []string
} }
type IPRules []IPRule type IPRules []IPRule
@@ -131,6 +133,13 @@ func (ru IPRule) Args() []string {
args = append(args, "-m", "comment", "--comment", ru.Comment) 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 != "" { if ru.Jump != "" {
jump := strings.ToUpper(ru.Jump) jump := strings.ToUpper(ru.Jump)
if jump == "ACCEPT" || jump == "DROP" { if jump == "ACCEPT" || jump == "DROP" {

9
pkg/schema/qos.go Normal file
View File

@@ -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"`
}

View File

@@ -53,6 +53,7 @@ type WorkerImpl struct {
setV *cn.IPSet setV *cn.IPSet
vpn *OpenVPN vpn *OpenVPN
ztrust *ZTrust ztrust *ZTrust
qos *QosCtrl
vrf *cn.VRF vrf *cn.VRF
table int table int
br cn.Bridger br cn.Bridger
@@ -126,6 +127,9 @@ func (w *WorkerImpl) Initialize() {
w.ztrust.Initialize() w.ztrust.Initialize()
} }
w.qos = NewQosCtrl(cfg.Name)
w.qos.Initialize()
if cfg.Dhcp == "enable" { if cfg.Dhcp == "enable" {
name := cfg.Bridge.Name name := cfg.Bridge.Name
if w.br != nil { if w.br != nil {
@@ -399,6 +403,21 @@ func (w *WorkerImpl) Start(v api.Switcher) {
Comment: "Goto Zero Trust", 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() fire.Start()
@@ -486,7 +505,12 @@ func (w *WorkerImpl) Stop() {
if !(w.ztrust == nil) { if !(w.ztrust == nil) {
w.ztrust.Stop() w.ztrust.Stop()
} }
if !(w.qos == nil) {
w.qos.Stop()
}
w.vpn.Stop() w.vpn.Stop()
} }
if !(w.dhcp == nil) { if !(w.dhcp == nil) {
@@ -781,6 +805,10 @@ func (w *WorkerImpl) ZTruster() api.ZTruster {
return w.ztrust return w.ztrust
} }
func (w *WorkerImpl) Qoser() api.Qoser {
return w.qos
}
func (w *WorkerImpl) IfAddr() string { func (w *WorkerImpl) IfAddr() string {
return strings.SplitN(w.cfg.Bridge.Address, "/", 2)[0] return strings.SplitN(w.cfg.Bridge.Address, "/", 2)[0]
} }

418
pkg/switch/qos.go Normal file
View File

@@ -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)
}
}